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