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