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