1 /* 2 * Copyright 2001-2015 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stefano Ceccherini, stefano.ceccherini@gmail.com 7 * Marc Flerackers, mflerackers@androme.be 8 * Bill Hayden, haydentech@users.sourceforge.net 9 * Olivier Milla 10 * John Scipione, jscipione@gmail.com 11 */ 12 13 14 #include <ctype.h> 15 #include <stdlib.h> 16 #include <string.h> 17 18 #include <Bitmap.h> 19 #include <ControlLook.h> 20 #include <MenuItem.h> 21 #include <Shape.h> 22 #include <String.h> 23 #include <Window.h> 24 25 #include <MenuPrivate.h> 26 27 #include "utf8_functions.h" 28 29 30 const float kLightBGTint 31 = (B_LIGHTEN_1_TINT + B_LIGHTEN_1_TINT + B_NO_TINT) / 3.0; 32 33 // map control key shortcuts to drawable Unicode characters 34 // cf. http://unicode.org/charts/PDF/U2190.pdf 35 const char* kUTF8ControlMap[] = { 36 NULL, 37 "\xe2\x86\xb8", /* B_HOME U+21B8 */ 38 NULL, NULL, 39 NULL, /* B_END */ 40 NULL, /* B_INSERT */ 41 NULL, NULL, 42 NULL, /* B_BACKSPACE */ 43 "\xe2\x86\xb9", /* B_TAB U+21B9 */ 44 "\xe2\x86\xb5", /* B_ENTER, U+21B5 */ 45 //"\xe2\x8f\x8e", /* B_ENTER, U+23CE it's the official one */ 46 NULL, /* B_PAGE_UP */ 47 NULL, /* B_PAGE_DOWN */ 48 NULL, NULL, NULL, 49 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 50 NULL, NULL, NULL, NULL, 51 "\xe2\x86\x90", /* B_LEFT_ARROW */ 52 "\xe2\x86\x92", /* B_RIGHT_ARROW */ 53 "\xe2\x86\x91", /* B_UP_ARROW */ 54 "\xe2\x86\x93", /* B_DOWN_ARROW */ 55 }; 56 57 58 using BPrivate::MenuPrivate; 59 60 BMenuItem::BMenuItem(const char* label, BMessage* message, char shortcut, 61 uint32 modifiers) 62 { 63 _InitData(); 64 if (label != NULL) 65 fLabel = strdup(label); 66 67 SetMessage(message); 68 69 fShortcutChar = shortcut; 70 71 if (shortcut != 0) 72 fModifiers = modifiers | B_COMMAND_KEY; 73 else 74 fModifiers = 0; 75 } 76 77 78 BMenuItem::BMenuItem(BMenu* menu, BMessage* message) 79 { 80 _InitData(); 81 SetMessage(message); 82 _InitMenuData(menu); 83 } 84 85 86 BMenuItem::BMenuItem(BMessage* data) 87 { 88 _InitData(); 89 90 if (data->HasString("_label")) { 91 const char* string; 92 93 data->FindString("_label", &string); 94 SetLabel(string); 95 } 96 97 bool disable; 98 if (data->FindBool("_disable", &disable) == B_OK) 99 SetEnabled(!disable); 100 101 bool marked; 102 if (data->FindBool("_marked", &marked) == B_OK) 103 SetMarked(marked); 104 105 int32 userTrigger; 106 if (data->FindInt32("_user_trig", &userTrigger) == B_OK) 107 SetTrigger(userTrigger); 108 109 if (data->HasInt32("_shortcut")) { 110 int32 shortcut, mods; 111 112 data->FindInt32("_shortcut", &shortcut); 113 data->FindInt32("_mods", &mods); 114 115 SetShortcut(shortcut, mods); 116 } 117 118 if (data->HasMessage("_msg")) { 119 BMessage* message = new BMessage; 120 data->FindMessage("_msg", message); 121 SetMessage(message); 122 } 123 124 BMessage subMessage; 125 if (data->FindMessage("_submenu", &subMessage) == B_OK) { 126 BArchivable* object = instantiate_object(&subMessage); 127 if (object != NULL) { 128 BMenu* menu = dynamic_cast<BMenu*>(object); 129 if (menu != NULL) 130 _InitMenuData(menu); 131 } 132 } 133 } 134 135 136 BArchivable* 137 BMenuItem::Instantiate(BMessage* data) 138 { 139 if (validate_instantiation(data, "BMenuItem")) 140 return new BMenuItem(data); 141 142 return NULL; 143 } 144 145 146 status_t 147 BMenuItem::Archive(BMessage* data, bool deep) const 148 { 149 status_t status = BArchivable::Archive(data, deep); 150 151 if (status == B_OK && fLabel) 152 status = data->AddString("_label", Label()); 153 154 if (status == B_OK && !IsEnabled()) 155 status = data->AddBool("_disable", true); 156 157 if (status == B_OK && IsMarked()) 158 status = data->AddBool("_marked", true); 159 160 if (status == B_OK && fUserTrigger) 161 status = data->AddInt32("_user_trig", fUserTrigger); 162 163 if (status == B_OK && fShortcutChar) { 164 status = data->AddInt32("_shortcut", fShortcutChar); 165 if (status == B_OK) 166 status = data->AddInt32("_mods", fModifiers); 167 } 168 169 if (status == B_OK && Message() != NULL) 170 status = data->AddMessage("_msg", Message()); 171 172 if (status == B_OK && deep && fSubmenu) { 173 BMessage submenu; 174 if (fSubmenu->Archive(&submenu, true) == B_OK) 175 status = data->AddMessage("_submenu", &submenu); 176 } 177 178 return status; 179 } 180 181 182 BMenuItem::~BMenuItem() 183 { 184 if (fSuper != NULL) 185 fSuper->RemoveItem(this); 186 187 free(fLabel); 188 delete fSubmenu; 189 } 190 191 192 void 193 BMenuItem::SetLabel(const char* string) 194 { 195 if (fLabel != NULL) { 196 free(fLabel); 197 fLabel = NULL; 198 } 199 200 if (string != NULL) 201 fLabel = strdup(string); 202 203 if (fSuper != NULL) { 204 fSuper->InvalidateLayout(); 205 206 if (fSuper->LockLooper()) { 207 fSuper->Invalidate(); 208 fSuper->UnlockLooper(); 209 } 210 } 211 } 212 213 214 void 215 BMenuItem::SetEnabled(bool enable) 216 { 217 if (fEnabled == enable) 218 return; 219 220 fEnabled = enable; 221 222 if (fSubmenu != NULL) 223 fSubmenu->SetEnabled(enable); 224 225 BMenu* menu = fSuper; 226 if (menu != NULL && menu->LockLooper()) { 227 menu->Invalidate(fBounds); 228 menu->UnlockLooper(); 229 } 230 } 231 232 233 void 234 BMenuItem::SetMarked(bool mark) 235 { 236 fMark = mark; 237 238 if (mark && fSuper != NULL) { 239 MenuPrivate priv(fSuper); 240 priv.ItemMarked(this); 241 } 242 } 243 244 245 void 246 BMenuItem::SetTrigger(char trigger) 247 { 248 fUserTrigger = trigger; 249 250 // try uppercase letters first 251 252 const char* pos = strchr(Label(), toupper(trigger)); 253 trigger = tolower(trigger); 254 255 if (pos == NULL) { 256 // take lowercase, too 257 pos = strchr(Label(), trigger); 258 } 259 260 if (pos != NULL) { 261 fTriggerIndex = UTF8CountChars(Label(), pos - Label()); 262 fTrigger = trigger; 263 } else { 264 fTrigger = 0; 265 fTriggerIndex = -1; 266 } 267 268 if (fSuper != NULL) 269 fSuper->InvalidateLayout(); 270 } 271 272 273 void 274 BMenuItem::SetShortcut(char shortcut, uint32 modifiers) 275 { 276 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0 277 && fWindow != NULL) { 278 fWindow->RemoveShortcut(fShortcutChar, fModifiers); 279 } 280 281 fShortcutChar = shortcut; 282 283 if (shortcut != 0) 284 fModifiers = modifiers | B_COMMAND_KEY; 285 else 286 fModifiers = 0; 287 288 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow) 289 fWindow->AddShortcut(fShortcutChar, fModifiers, this); 290 291 if (fSuper != NULL) { 292 fSuper->InvalidateLayout(); 293 294 if (fSuper->LockLooper()) { 295 fSuper->Invalidate(); 296 fSuper->UnlockLooper(); 297 } 298 } 299 } 300 301 302 const char* 303 BMenuItem::Label() const 304 { 305 return fLabel; 306 } 307 308 309 bool 310 BMenuItem::IsEnabled() const 311 { 312 if (fSubmenu) 313 return fSubmenu->IsEnabled(); 314 315 if (!fEnabled) 316 return false; 317 318 return fSuper != NULL ? fSuper->IsEnabled() : true; 319 } 320 321 322 bool 323 BMenuItem::IsMarked() const 324 { 325 return fMark; 326 } 327 328 329 char 330 BMenuItem::Trigger() const 331 { 332 return fUserTrigger; 333 } 334 335 336 char 337 BMenuItem::Shortcut(uint32* modifiers) const 338 { 339 if (modifiers) 340 *modifiers = fModifiers; 341 342 return fShortcutChar; 343 } 344 345 346 BMenu* 347 BMenuItem::Submenu() const 348 { 349 return fSubmenu; 350 } 351 352 353 BMenu* 354 BMenuItem::Menu() const 355 { 356 return fSuper; 357 } 358 359 360 BRect 361 BMenuItem::Frame() const 362 { 363 return fBounds; 364 } 365 366 367 void 368 BMenuItem::GetContentSize(float* _width, float* _height) 369 { 370 // TODO: Get rid of this. BMenu should handle this 371 // automatically. Maybe it's not even needed, since our 372 // BFont::Height() caches the value locally 373 MenuPrivate(fSuper).CacheFontInfo(); 374 375 fCachedWidth = fSuper->StringWidth(fLabel); 376 377 if (_width) 378 *_width = (float)ceil(fCachedWidth); 379 if (_height) 380 *_height = MenuPrivate(fSuper).FontHeight(); 381 } 382 383 384 void 385 BMenuItem::TruncateLabel(float maxWidth, char* newLabel) 386 { 387 BFont font; 388 fSuper->GetFont(&font); 389 390 BString string(fLabel); 391 392 font.TruncateString(&string, B_TRUNCATE_MIDDLE, maxWidth); 393 394 string.CopyInto(newLabel, 0, string.Length()); 395 newLabel[string.Length()] = '\0'; 396 } 397 398 399 void 400 BMenuItem::DrawContent() 401 { 402 MenuPrivate menuPrivate(fSuper); 403 menuPrivate.CacheFontInfo(); 404 405 fSuper->MovePenBy(0, menuPrivate.Ascent()); 406 BPoint lineStart = fSuper->PenLocation(); 407 408 fSuper->SetDrawingMode(B_OP_OVER); 409 410 float labelWidth; 411 float labelHeight; 412 GetContentSize(&labelWidth, &labelHeight); 413 414 const BRect& padding = menuPrivate.Padding(); 415 float maxContentWidth = fSuper->MaxContentWidth(); 416 float frameWidth = maxContentWidth > 0 ? maxContentWidth 417 : fSuper->Frame().Width() - padding.left - padding.right; 418 419 if (roundf(frameWidth) >= roundf(labelWidth)) 420 fSuper->DrawString(fLabel); 421 else { 422 // truncate label to fit 423 char* truncatedLabel = new char[strlen(fLabel) + 4]; 424 TruncateLabel(frameWidth, truncatedLabel); 425 fSuper->DrawString(truncatedLabel); 426 delete[] truncatedLabel; 427 } 428 429 if (fSuper->AreTriggersEnabled() && fTriggerIndex != -1) { 430 float escapements[fTriggerIndex + 1]; 431 BFont font; 432 fSuper->GetFont(&font); 433 434 font.GetEscapements(fLabel, fTriggerIndex + 1, escapements); 435 436 for (int32 i = 0; i < fTriggerIndex; i++) 437 lineStart.x += escapements[i] * font.Size(); 438 439 lineStart.x--; 440 lineStart.y++; 441 442 BPoint lineEnd(lineStart); 443 lineEnd.x += escapements[fTriggerIndex] * font.Size(); 444 445 fSuper->StrokeLine(lineStart, lineEnd); 446 } 447 } 448 449 450 void 451 BMenuItem::Draw() 452 { 453 const color_which lowColor = fSuper->LowUIColor(); 454 const color_which highColor = fSuper->HighUIColor(); 455 456 bool enabled = IsEnabled(); 457 bool selected = IsSelected(); 458 bool activated = selected && (enabled || Submenu() != NULL); 459 460 // set low color 461 if (activated) { 462 fSuper->SetLowColor(ui_color(B_MENU_SELECTED_BACKGROUND_COLOR)); 463 // fill in the background 464 BRect rect(Frame()); 465 be_control_look->DrawMenuItemBackground(fSuper, rect, Frame(), 466 fSuper->LowColor(), BControlLook::B_ACTIVATED); 467 } else 468 fSuper->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR)); 469 470 // set high color 471 if (activated && enabled) 472 fSuper->SetHighColor(ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR)); 473 else if (enabled) 474 fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR)); 475 else { 476 rgb_color bgColor = fSuper->LowColor(); 477 if (bgColor.red + bgColor.green + bgColor.blue > 128 * 3) 478 fSuper->SetHighColor(tint_color(bgColor, B_DISABLED_LABEL_TINT)); 479 else 480 fSuper->SetHighColor(tint_color(bgColor, B_LIGHTEN_2_TINT)); 481 } 482 483 // draw content 484 fSuper->MovePenTo(ContentLocation()); 485 DrawContent(); 486 487 // draw extra symbols 488 const menu_layout layout = MenuPrivate(fSuper).Layout(); 489 if (layout == B_ITEMS_IN_COLUMN) { 490 if (IsMarked()) 491 _DrawMarkSymbol(); 492 493 if (fShortcutChar) 494 _DrawShortcutSymbol(); 495 496 if (Submenu() != NULL) 497 _DrawSubmenuSymbol(); 498 } 499 500 // restore the parent menu's low color and high color 501 fSuper->SetLowUIColor(lowColor); 502 fSuper->SetHighUIColor(highColor); 503 } 504 505 506 void 507 BMenuItem::Highlight(bool highlight) 508 { 509 fSuper->Invalidate(Frame()); 510 } 511 512 513 bool 514 BMenuItem::IsSelected() const 515 { 516 return fSelected; 517 } 518 519 520 BPoint 521 BMenuItem::ContentLocation() const 522 { 523 const BRect& padding = MenuPrivate(fSuper).Padding(); 524 525 return BPoint(fBounds.left + padding.left, fBounds.top + padding.top); 526 } 527 528 529 void BMenuItem::_ReservedMenuItem1() {} 530 void BMenuItem::_ReservedMenuItem2() {} 531 void BMenuItem::_ReservedMenuItem3() {} 532 void BMenuItem::_ReservedMenuItem4() {} 533 534 535 BMenuItem::BMenuItem(const BMenuItem &) 536 { 537 } 538 539 540 BMenuItem& 541 BMenuItem::operator=(const BMenuItem &) 542 { 543 return *this; 544 } 545 546 547 void 548 BMenuItem::_InitData() 549 { 550 fLabel = NULL; 551 fSubmenu = NULL; 552 fWindow = NULL; 553 fSuper = NULL; 554 fModifiers = 0; 555 fCachedWidth = 0; 556 fTriggerIndex = -1; 557 fUserTrigger = 0; 558 fTrigger = 0; 559 fShortcutChar = 0; 560 fMark = false; 561 fEnabled = true; 562 fSelected = false; 563 } 564 565 566 void 567 BMenuItem::_InitMenuData(BMenu* menu) 568 { 569 fSubmenu = menu; 570 571 MenuPrivate(fSubmenu).SetSuperItem(this); 572 573 BMenuItem* item = menu->FindMarked(); 574 575 if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL) 576 SetLabel(item->Label()); 577 else 578 SetLabel(menu->Name()); 579 } 580 581 582 void 583 BMenuItem::Install(BWindow* window) 584 { 585 if (fSubmenu != NULL) 586 MenuPrivate(fSubmenu).Install(window); 587 588 fWindow = window; 589 590 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow) 591 window->AddShortcut(fShortcutChar, fModifiers, this); 592 593 if (!Messenger().IsValid()) 594 SetTarget(window); 595 } 596 597 598 status_t 599 BMenuItem::Invoke(BMessage* message) 600 { 601 if (!IsEnabled()) 602 return B_ERROR; 603 604 if (fSuper->IsRadioMode()) 605 SetMarked(true); 606 607 bool notify = false; 608 uint32 kind = InvokeKind(¬ify); 609 610 BMessage clone(kind); 611 status_t err = B_BAD_VALUE; 612 613 if (message == NULL && !notify) 614 message = Message(); 615 616 if (message == NULL) { 617 if (!fSuper->IsWatched()) 618 return err; 619 } else 620 clone = *message; 621 622 clone.AddInt32("index", fSuper->IndexOf(this)); 623 clone.AddInt64("when", (int64)system_time()); 624 clone.AddPointer("source", this); 625 clone.AddMessenger("be:sender", BMessenger(fSuper)); 626 627 if (message != NULL) 628 err = BInvoker::Invoke(&clone); 629 630 // TODO: assynchronous messaging 631 // SendNotices(kind, &clone); 632 633 return err; 634 } 635 636 637 void 638 BMenuItem::Uninstall() 639 { 640 if (fSubmenu != NULL) 641 MenuPrivate(fSubmenu).Uninstall(); 642 643 if (Target() == fWindow) 644 SetTarget(BMessenger()); 645 646 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0 647 && fWindow != NULL) { 648 fWindow->RemoveShortcut(fShortcutChar, fModifiers); 649 } 650 651 fWindow = NULL; 652 } 653 654 655 void 656 BMenuItem::SetSuper(BMenu* super) 657 { 658 if (fSuper != NULL && super != NULL) { 659 debugger("Error - can't add menu or menu item to more than 1 container" 660 " (either menu or menubar)."); 661 } 662 663 if (fSubmenu != NULL) 664 MenuPrivate(fSubmenu).SetSuper(super); 665 666 fSuper = super; 667 } 668 669 670 void 671 BMenuItem::Select(bool selected) 672 { 673 if (fSelected == selected) 674 return; 675 676 if (Submenu() != NULL || IsEnabled()) { 677 fSelected = selected; 678 Highlight(selected); 679 } 680 } 681 682 683 void 684 BMenuItem::_DrawMarkSymbol() 685 { 686 fSuper->PushState(); 687 688 BRect r(fBounds); 689 float leftMargin; 690 MenuPrivate(fSuper).GetItemMargins(&leftMargin, NULL, NULL, NULL); 691 float gap = leftMargin / 4; 692 r.right = r.left + leftMargin - gap; 693 r.left += gap / 3; 694 695 BPoint center(floorf((r.left + r.right) / 2.0), 696 floorf((r.top + r.bottom) / 2.0)); 697 698 float size = min_c(r.Height() - 2, r.Width()); 699 r.top = floorf(center.y - size / 2 + 0.5); 700 r.bottom = floorf(center.y + size / 2 + 0.5); 701 r.left = floorf(center.x - size / 2 + 0.5); 702 r.right = floorf(center.x + size / 2 + 0.5); 703 704 BShape arrowShape; 705 center.x += 0.5; 706 center.y += 0.5; 707 size *= 0.3; 708 arrowShape.MoveTo(BPoint(center.x - size, center.y - size * 0.25)); 709 arrowShape.LineTo(BPoint(center.x - size * 0.25, center.y + size)); 710 arrowShape.LineTo(BPoint(center.x + size, center.y - size)); 711 712 fSuper->SetDrawingMode(B_OP_OVER); 713 fSuper->SetPenSize(2.0); 714 // NOTE: StrokeShape() offsets the shape by the current pen position, 715 // it is not documented in the BeBook, but it is true! 716 fSuper->MovePenTo(B_ORIGIN); 717 fSuper->StrokeShape(&arrowShape); 718 719 fSuper->PopState(); 720 } 721 722 723 void 724 BMenuItem::_DrawShortcutSymbol() 725 { 726 BMenu* menu = fSuper; 727 BFont font; 728 menu->GetFont(&font); 729 BPoint where = ContentLocation(); 730 where.x = fBounds.right - font.Size(); 731 732 if (fSubmenu) 733 where.x -= fBounds.Height() - 3; 734 735 const float ascent = MenuPrivate(fSuper).Ascent(); 736 if (fShortcutChar < B_SPACE && kUTF8ControlMap[(int)fShortcutChar]) 737 _DrawControlChar(fShortcutChar, where + BPoint(0, ascent)); 738 else 739 fSuper->DrawChar(fShortcutChar, where + BPoint(0, ascent)); 740 741 where.y += (fBounds.Height() - 11) / 2 - 1; 742 where.x -= 4; 743 744 // TODO: It would be nice to draw these taking into account the text (low) 745 // color. 746 if ((fModifiers & B_COMMAND_KEY) != 0) { 747 const BBitmap* command = MenuPrivate::MenuItemCommand(); 748 const BRect &rect = command->Bounds(); 749 where.x -= rect.Width() + 1; 750 fSuper->DrawBitmap(command, where); 751 } 752 753 if ((fModifiers & B_CONTROL_KEY) != 0) { 754 const BBitmap* control = MenuPrivate::MenuItemControl(); 755 const BRect &rect = control->Bounds(); 756 where.x -= rect.Width() + 1; 757 fSuper->DrawBitmap(control, where); 758 } 759 760 if ((fModifiers & B_OPTION_KEY) != 0) { 761 const BBitmap* option = MenuPrivate::MenuItemOption(); 762 const BRect &rect = option->Bounds(); 763 where.x -= rect.Width() + 1; 764 fSuper->DrawBitmap(option, where); 765 } 766 767 if ((fModifiers & B_SHIFT_KEY) != 0) { 768 const BBitmap* shift = MenuPrivate::MenuItemShift(); 769 const BRect &rect = shift->Bounds(); 770 where.x -= rect.Width() + 1; 771 fSuper->DrawBitmap(shift, where); 772 } 773 } 774 775 776 void 777 BMenuItem::_DrawSubmenuSymbol() 778 { 779 fSuper->PushState(); 780 781 BRect r(fBounds); 782 float rightMargin; 783 MenuPrivate(fSuper).GetItemMargins(NULL, NULL, &rightMargin, NULL); 784 r.left = r.right - rightMargin + 3; 785 r.right -= 1; 786 787 BPoint center(floorf((r.left + r.right) / 2.0), 788 floorf((r.top + r.bottom) / 2.0)); 789 790 float size = min_c(r.Height() - 2, r.Width()); 791 r.top = floorf(center.y - size / 2 + 0.5); 792 r.bottom = floorf(center.y + size / 2 + 0.5); 793 r.left = floorf(center.x - size / 2 + 0.5); 794 r.right = floorf(center.x + size / 2 + 0.5); 795 796 BShape arrowShape; 797 center.x += 0.5; 798 center.y += 0.5; 799 size *= 0.25; 800 float hSize = size * 0.7; 801 arrowShape.MoveTo(BPoint(center.x - hSize, center.y - size)); 802 arrowShape.LineTo(BPoint(center.x + hSize, center.y)); 803 arrowShape.LineTo(BPoint(center.x - hSize, center.y + size)); 804 805 fSuper->SetDrawingMode(B_OP_OVER); 806 fSuper->SetPenSize(ceilf(size * 0.4)); 807 // NOTE: StrokeShape() offsets the shape by the current pen position, 808 // it is not documented in the BeBook, but it is true! 809 fSuper->MovePenTo(B_ORIGIN); 810 fSuper->StrokeShape(&arrowShape); 811 812 fSuper->PopState(); 813 } 814 815 816 void 817 BMenuItem::_DrawControlChar(char shortcut, BPoint where) 818 { 819 // TODO: If needed, take another font for the control characters 820 // (or have font overlays in the app_server!) 821 const char* symbol = " "; 822 if (kUTF8ControlMap[(int)fShortcutChar]) 823 symbol = kUTF8ControlMap[(int)fShortcutChar]; 824 825 fSuper->DrawString(symbol, where); 826 } 827 828 829 void 830 BMenuItem::SetAutomaticTrigger(int32 index, uint32 trigger) 831 { 832 fTriggerIndex = index; 833 fTrigger = trigger; 834 } 835