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 if (super) 644 super->fSubmenus++; 645 else if (fSuper) 646 fSuper->fSubmenus--; 647 fSubmenu->fSuper = super; 648 } 649 650 fSuper = super; 651 } 652 653 654 void 655 BMenuItem::Select(bool selected) 656 { 657 if (fSelected == selected) 658 return; 659 660 if (Submenu() || IsEnabled()) { 661 fSelected = selected; 662 Highlight(selected); 663 } 664 } 665 666 667 void 668 BMenuItem::_DrawMarkSymbol(rgb_color bgColor) 669 { 670 fSuper->PushState(); 671 672 BRect r(fBounds); 673 float leftMargin; 674 fSuper->GetItemMargins(&leftMargin, NULL, NULL, NULL); 675 r.right = r.left + leftMargin - 3; 676 r.left += 1; 677 678 BPoint center(floorf((r.left + r.right) / 2.0), 679 floorf((r.top + r.bottom) / 2.0)); 680 681 float size = min_c(r.Height() - 2, r.Width()); 682 r.top = floorf(center.y - size / 2 + 0.5); 683 r.bottom = floorf(center.y + size / 2 + 0.5); 684 r.left = floorf(center.x - size / 2 + 0.5); 685 r.right = floorf(center.x + size / 2 + 0.5); 686 687 fSuper->SetHighColor(tint_color(bgColor, kLightBGTint)); 688 fSuper->FillRoundRect(r, 2, 2); 689 690 BShape arrowShape; 691 center.x += 0.5; 692 center.y += 0.5; 693 size *= 0.3; 694 arrowShape.MoveTo(BPoint(center.x - size, center.y - size * 0.25)); 695 arrowShape.LineTo(BPoint(center.x - size * 0.25, center.y + size)); 696 arrowShape.LineTo(BPoint(center.x + size, center.y - size)); 697 698 fSuper->SetDrawingMode(B_OP_OVER); 699 fSuper->SetHighColor(tint_color(bgColor, B_DARKEN_MAX_TINT)); 700 fSuper->SetPenSize(2.0); 701 // NOTE: StrokeShape() offsets the shape by the current pen position, 702 // it is not documented in the BeBook, but it is true! 703 fSuper->MovePenTo(B_ORIGIN); 704 fSuper->StrokeShape(&arrowShape); 705 706 fSuper->PopState(); 707 } 708 709 710 void 711 BMenuItem::_DrawShortcutSymbol() 712 { 713 BMenu *menu = Menu(); 714 BFont font; 715 menu->GetFont(&font); 716 BPoint where = ContentLocation(); 717 where.x = fBounds.right - font.Size(); 718 if (menu->fSubmenus) 719 where.x -= fBounds.Height() - 4; 720 721 switch (fShortcutChar) { 722 case B_DOWN_ARROW: 723 case B_UP_ARROW: 724 case B_LEFT_ARROW: 725 case B_RIGHT_ARROW: 726 case B_ENTER: 727 _DrawControlChar(fShortcutChar, where + BPoint(0, fSuper->fAscent)); 728 break; 729 730 default: 731 fSuper->DrawChar(fShortcutChar, where + BPoint(0, fSuper->fAscent)); 732 break; 733 } 734 735 where.y += (fBounds.Height() - 11) / 2 - 1; 736 where.x -= 4; 737 738 if (fModifiers & B_COMMAND_KEY) { 739 BRect rect(0,0,16,10); 740 BBitmap control(rect, B_CMAP8); 741 742 if (BMenu::sAltAsCommandKey) 743 control.ImportBits(kAltBits, sizeof(kAltBits), 17, 0, B_CMAP8); 744 else 745 control.ImportBits(kCtrlBits, sizeof(kCtrlBits), 17, 0, B_CMAP8); 746 747 where.x -= rect.Width() + 1; 748 fSuper->DrawBitmap(&control, where); 749 } 750 751 if (fModifiers & B_CONTROL_KEY) { 752 BRect rect(0,0,16,10); 753 BBitmap control(rect, B_CMAP8); 754 755 if (BMenu::sAltAsCommandKey) 756 control.ImportBits(kCtrlBits, sizeof(kCtrlBits), 17, 0, B_CMAP8); 757 else 758 control.ImportBits(kAltBits, sizeof(kAltBits), 17, 0, B_CMAP8); 759 where.x -= rect.Width() + 1; 760 fSuper->DrawBitmap(&control, where); 761 } 762 763 if (fModifiers & B_SHIFT_KEY) { 764 BRect rect(0,0,21,10); 765 BBitmap shift(rect, B_CMAP8); 766 shift.ImportBits(kShiftBits, sizeof(kShiftBits), 22, 0, B_CMAP8); 767 where.x -= rect.Width() + 1; 768 fSuper->DrawBitmap(&shift, where); 769 } 770 } 771 772 773 void 774 BMenuItem::_DrawSubmenuSymbol(rgb_color bgColor) 775 { 776 fSuper->PushState(); 777 778 BRect r(fBounds); 779 float rightMargin; 780 fSuper->GetItemMargins(NULL, NULL, &rightMargin, NULL); 781 r.left = r.right - rightMargin + 3; 782 r.right -= 1; 783 784 BPoint center(floorf((r.left + r.right) / 2.0), 785 floorf((r.top + r.bottom) / 2.0)); 786 787 float size = min_c(r.Height() - 2, r.Width()); 788 r.top = floorf(center.y - size / 2 + 0.5); 789 r.bottom = floorf(center.y + size / 2 + 0.5); 790 r.left = floorf(center.x - size / 2 + 0.5); 791 r.right = floorf(center.x + size / 2 + 0.5); 792 793 fSuper->SetHighColor(tint_color(bgColor, kLightBGTint)); 794 fSuper->FillRoundRect(r, 2, 2); 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->SetHighColor(tint_color(bgColor, B_DARKEN_MAX_TINT)); 807 fSuper->SetPenSize(ceilf(size * 0.4)); 808 // NOTE: StrokeShape() offsets the shape by the current pen position, 809 // it is not documented in the BeBook, but it is true! 810 fSuper->MovePenTo(B_ORIGIN); 811 fSuper->StrokeShape(&arrowShape); 812 813 fSuper->PopState(); 814 } 815 816 817 void 818 BMenuItem::_DrawControlChar(char shortcut, BPoint where) 819 { 820 // TODO: If needed, take another font for the control characters 821 // (or have font overlays in the app_server!) 822 const char* symbol = " "; 823 824 switch (shortcut) { 825 case B_DOWN_ARROW: 826 symbol = "\xe2\x86\x93"; 827 break; 828 case B_UP_ARROW: 829 symbol = "\xe2\x86\x91"; 830 break; 831 case B_LEFT_ARROW: 832 symbol = "\xe2\x86\x90"; 833 break; 834 case B_RIGHT_ARROW: 835 symbol = "\xe2\x86\x92"; 836 break; 837 case B_ENTER: 838 symbol = "\xe2\x86\xb5"; 839 break; 840 } 841 842 fSuper->DrawString(symbol, where); 843 } 844 845 846 void 847 BMenuItem::SetAutomaticTrigger(int32 index, uint32 trigger) 848 { 849 fTriggerIndex = index; 850 fTrigger = trigger; 851 } 852