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