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