1 //------------------------------------------------------------------------------ 2 // Copyright (c) 2001-2005, Haiku 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a 5 // copy of this software and associated documentation files (the "Software"), 6 // to deal in the Software without restriction, including without limitation 7 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 // and/or sell copies of the Software, and to permit persons to whom the 9 // Software is furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 // DEALINGS IN THE SOFTWARE. 21 // 22 // File Name: MenuItem.cpp 23 // Authors Marc Flerackers (mflerackers@androme.be) 24 // Bill Hayden (haydentech@users.sourceforge.net) 25 // Stefano Ceccherini (burton666@libero.it) 26 // Olivier Milla 27 // Description: Display item for BMenu class 28 // 29 //------------------------------------------------------------------------------ 30 31 #include <string.h> 32 #include <stdlib.h> 33 34 #include <Bitmap.h> 35 #include <MenuItem.h> 36 #include <String.h> 37 #include <Window.h> 38 39 40 BMenuItem::BMenuItem(const char *label, BMessage *message, char shortcut, 41 uint32 modifiers) 42 { 43 InitData(); 44 if (label != NULL) 45 fLabel = strdup(label); 46 47 SetMessage(message); 48 49 fShortcutChar = shortcut; 50 51 if (shortcut != 0) 52 fModifiers = modifiers | B_COMMAND_KEY; 53 else 54 fModifiers = 0; 55 } 56 57 58 BMenuItem::BMenuItem(BMenu *menu, BMessage *message) 59 { 60 InitData(); 61 SetMessage(message); 62 InitMenuData(menu); 63 } 64 65 66 BMenuItem::BMenuItem(BMessage *data) 67 { 68 InitData(); 69 70 if (data->HasString("_label")) { 71 const char *string; 72 73 data->FindString("_label", &string); 74 SetLabel(string); 75 } 76 77 bool disable; 78 if (data->FindBool("_disable", &disable) == B_OK) 79 SetEnabled(!disable); 80 81 bool marked; 82 if (data->FindBool("_marked", &marked) == B_OK) 83 SetMarked(marked); 84 85 if (data->HasInt32("_user_trig")) { 86 int32 user_trig; 87 88 data->FindInt32("_user_trig", &user_trig); 89 90 SetTrigger(user_trig); 91 } 92 93 if (data->HasInt32("_shortcut")) { 94 int32 shortcut, mods; 95 96 data->FindInt32("_shortcut", &shortcut); 97 data->FindInt32("_mods", &mods); 98 99 SetShortcut(shortcut, mods); 100 } 101 102 if (data->HasMessage("_msg")) { 103 BMessage *msg = new BMessage; 104 105 data->FindMessage("_msg", msg); 106 SetMessage(msg); 107 } 108 109 BMessage subMessage; 110 if (data->FindMessage("_submenu", &subMessage) == B_OK) { 111 BArchivable *object = instantiate_object(&subMessage); 112 113 if (object != NULL) { 114 BMenu *menu = dynamic_cast<BMenu *>(object); 115 116 if (menu != NULL) 117 InitMenuData(menu); 118 } 119 } 120 } 121 122 123 BArchivable * 124 BMenuItem::Instantiate(BMessage *data) 125 { 126 if (validate_instantiation(data, "BMenuItem")) 127 return new BMenuItem(data); 128 else 129 return NULL; 130 } 131 132 133 status_t 134 BMenuItem::Archive(BMessage *data, bool deep) const 135 { 136 if (fLabel) 137 data->AddString("_label", Label()); 138 139 if (!IsEnabled()) 140 data->AddBool("_disable", true); 141 142 if (IsMarked()) 143 data->AddBool("_marked", true); 144 145 if (fUserTrigger) 146 data->AddInt32("_user_trig", fUserTrigger); 147 148 if (fShortcutChar) { 149 data->AddInt32("_shortcut", fShortcutChar); 150 data->AddInt32("_mods", fModifiers); 151 } 152 153 if (Message()) 154 data->AddMessage("_msg", Message()); 155 156 if (deep && fSubmenu) { 157 BMessage submenu; 158 159 if (fSubmenu->Archive(&submenu, true) == B_OK) 160 data->AddMessage("_submenu", &submenu); 161 } 162 163 return B_OK; 164 } 165 166 167 BMenuItem::~BMenuItem() 168 { 169 free(fLabel); 170 delete fSubmenu; 171 } 172 173 174 void 175 BMenuItem::SetLabel(const char *string) 176 { 177 if (fLabel != NULL) { 178 free(fLabel); 179 fLabel = NULL; 180 } 181 182 if (string != NULL) 183 fLabel = strdup(string); 184 185 if (fSuper != NULL) { 186 fSuper->InvalidateLayout(); 187 188 if (fSuper->LockLooper()) { 189 fSuper->Invalidate(); 190 fSuper->UnlockLooper(); 191 } 192 } 193 } 194 195 196 void 197 BMenuItem::SetEnabled(bool state) 198 { 199 if (fSubmenu != NULL) 200 fSubmenu->SetEnabled(state); 201 202 fEnabled = state; 203 204 BMenu *menu = Menu(); 205 if (menu != NULL && menu->LockLooper()) { 206 menu->Invalidate(fBounds); 207 menu->UnlockLooper(); 208 } 209 } 210 211 212 void 213 BMenuItem::SetMarked(bool state) 214 { 215 fMark = state; 216 217 if (state && Menu() != NULL) 218 Menu()->ItemMarked(this); 219 } 220 221 222 void 223 BMenuItem::SetTrigger(char ch) 224 { 225 fUserTrigger = ch; 226 227 if (strchr(fLabel, ch) != 0) 228 fSysTrigger = ch; 229 else 230 fSysTrigger = -1; 231 232 if (fSuper != NULL) 233 fSuper->InvalidateLayout(); 234 } 235 236 237 void 238 BMenuItem::SetShortcut(char ch, uint32 modifiers) 239 { 240 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow) 241 fWindow->RemoveShortcut(fShortcutChar, fModifiers); 242 243 fShortcutChar = ch; 244 245 if (ch != 0) 246 fModifiers = modifiers | B_COMMAND_KEY; 247 else 248 fModifiers = 0; 249 250 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow) 251 fWindow->AddShortcut(fShortcutChar, fModifiers, this); 252 253 if (fSuper) { 254 fSuper->InvalidateLayout(); 255 256 if (fSuper->LockLooper()) { 257 fSuper->Invalidate(); 258 fSuper->UnlockLooper(); 259 } 260 } 261 } 262 263 264 const char * 265 BMenuItem::Label() const 266 { 267 return fLabel; 268 } 269 270 271 bool 272 BMenuItem::IsEnabled() const 273 { 274 if (fSubmenu) 275 return fSubmenu->IsEnabled(); 276 277 if (!fEnabled) 278 return false; 279 280 return fSuper ? fSuper->IsEnabled() : true; 281 } 282 283 284 bool 285 BMenuItem::IsMarked() const 286 { 287 return fMark; 288 } 289 290 291 char 292 BMenuItem::Trigger() const 293 { 294 return fUserTrigger; 295 } 296 297 298 char 299 BMenuItem::Shortcut(uint32 *modifiers) const 300 { 301 if (modifiers) 302 *modifiers = fModifiers; 303 304 return fShortcutChar; 305 } 306 307 308 BMenu * 309 BMenuItem::Submenu() const 310 { 311 return fSubmenu; 312 } 313 314 315 BMenu * 316 BMenuItem::Menu() const 317 { 318 return fSuper; 319 } 320 321 322 BRect 323 BMenuItem::Frame() const 324 { 325 return fBounds; 326 } 327 328 329 void 330 BMenuItem::GetContentSize(float *width, float *height) 331 { 332 fSuper->CacheFontInfo(); 333 334 fCachedWidth = fSuper->StringWidth(fLabel); 335 336 if (width) 337 *width = (float)ceil(fCachedWidth); 338 if (height) 339 *height = fSuper->fFontHeight; 340 } 341 342 343 void 344 BMenuItem::TruncateLabel(float maxWidth, char *newLabel) 345 { 346 BFont font; 347 BString string(fLabel); 348 349 font.TruncateString(&string, B_TRUNCATE_MIDDLE, maxWidth); 350 351 string.CopyInto(newLabel, 0, string.Length()); 352 newLabel[string.Length()] = '\0'; 353 } 354 355 356 void 357 BMenuItem::DrawContent() 358 { 359 fSuper->MovePenBy(0, fSuper->fAscent); 360 BPoint lineStart = fSuper->PenLocation(); 361 362 float labelWidth, labelHeight; 363 GetContentSize(&labelWidth, &labelHeight); 364 365 // truncate if needed 366 // TODO: Actually, this is still never triggered 367 if (fBounds.Width() > labelWidth) 368 fSuper->DrawString(fLabel); 369 else { 370 char *truncatedLabel = new char[strlen(fLabel) + 4]; 371 TruncateLabel(fBounds.Width(), truncatedLabel); 372 fSuper->DrawString(truncatedLabel); 373 delete[] truncatedLabel; 374 } 375 376 if (fSuper->AreTriggersEnabled() && fTriggerIndex != -1) { 377 float escapements[fTriggerIndex + 1]; 378 BFont font; 379 fSuper->GetFont(&font); 380 381 font.GetEscapements(fLabel, fTriggerIndex + 1, escapements); 382 383 for (int32 i = 0; i < fTriggerIndex; i++) 384 lineStart.x += escapements[i] * font.Size(); 385 386 lineStart.x--; 387 lineStart.y++; 388 389 BPoint lineEnd(lineStart); 390 lineEnd.x += escapements[fTriggerIndex] * font.Size(); 391 392 fSuper->StrokeLine(lineStart, lineEnd); 393 } 394 } 395 396 397 void 398 BMenuItem::Draw() 399 { 400 // TODO: Cleanup 401 bool enabled = IsEnabled(); 402 403 fSuper->CacheFontInfo(); 404 405 if (IsSelected() && (enabled || Submenu()) /*&& fSuper->fRedrawAfterSticky*/) { 406 fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 407 B_DARKEN_2_TINT)); 408 fSuper->SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 409 B_DARKEN_2_TINT)); 410 fSuper->FillRect(Frame()); 411 } 412 413 if (IsEnabled()) 414 fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR)); 415 else if (IsSelected()) 416 fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 417 B_LIGHTEN_1_TINT)); 418 else 419 fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 420 B_DISABLED_LABEL_TINT)); 421 422 fSuper->MovePenTo(ContentLocation()); 423 424 DrawContent(); 425 426 if (fSuper->Layout() == B_ITEMS_IN_COLUMN) { 427 if (IsMarked()) 428 DrawMarkSymbol(); 429 430 if (fShortcutChar) 431 DrawShortcutSymbol(); 432 433 if (Submenu()) 434 DrawSubmenuSymbol(); 435 } 436 437 fSuper->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR)); 438 } 439 440 441 void 442 BMenuItem::Highlight(bool flag) 443 { 444 Menu()->Invalidate(Frame()); 445 } 446 447 448 bool 449 BMenuItem::IsSelected() const 450 { 451 return fSelected; 452 } 453 454 455 BPoint 456 BMenuItem::ContentLocation() const 457 { 458 return BPoint(fBounds.left + Menu()->fPad.left, 459 fBounds.top + Menu()->fPad.top); 460 } 461 462 463 void BMenuItem::_ReservedMenuItem1() {} 464 void BMenuItem::_ReservedMenuItem2() {} 465 void BMenuItem::_ReservedMenuItem3() {} 466 void BMenuItem::_ReservedMenuItem4() {} 467 468 469 BMenuItem::BMenuItem(const BMenuItem &) 470 { 471 } 472 473 474 BMenuItem & 475 BMenuItem::operator=(const BMenuItem &) 476 { 477 return *this; 478 } 479 480 481 void 482 BMenuItem::InitData() 483 { 484 fLabel = NULL; 485 fSubmenu = NULL; 486 fWindow = NULL; 487 fSuper = NULL; 488 fModifiers = 0; 489 fCachedWidth = 0; 490 fTriggerIndex = -1; 491 fUserTrigger = 0; 492 fSysTrigger = 0; 493 fShortcutChar = 0; 494 fMark = false; 495 fEnabled = true; 496 fSelected = false; 497 } 498 499 500 void 501 BMenuItem::InitMenuData(BMenu *menu) 502 { 503 fSubmenu = menu; 504 fSubmenu->fSuperitem = this; 505 506 BMenuItem *item = menu->FindMarked(); 507 508 if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL) 509 SetLabel(item->Label()); 510 else 511 SetLabel(menu->Name()); 512 } 513 514 515 void 516 BMenuItem::Install(BWindow *window) 517 { 518 if (fSubmenu) 519 fSubmenu->Install(window); 520 521 fWindow = window; 522 523 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow) 524 window->AddShortcut(fShortcutChar, fModifiers, this); 525 526 if (!Messenger().IsValid()) 527 SetTarget(window); 528 } 529 530 531 status_t 532 BMenuItem::Invoke(BMessage *message) 533 { 534 if (!IsEnabled()) 535 return B_ERROR; 536 537 if (fSuper->IsRadioMode()) 538 SetMarked(true); 539 540 bool notify = false; 541 uint32 kind = InvokeKind(¬ify); 542 543 BMessage clone(kind); 544 status_t err = B_BAD_VALUE; 545 546 if (!message && !notify) 547 message = Message(); 548 549 if (!message) { 550 if (!fSuper->IsWatched()) 551 return err; 552 } else 553 clone = *message; 554 555 clone.AddInt32("index", Menu()->IndexOf(this)); 556 clone.AddInt64("when", (int64)system_time()); 557 clone.AddPointer("source", this); 558 clone.AddMessenger("be:sender", BMessenger(fSuper)); 559 560 if (message) 561 err = BInvoker::Invoke(&clone); 562 563 // TODO: assynchronous messaging 564 // SendNotices(kind, &clone); 565 566 return err; 567 } 568 569 570 void 571 BMenuItem::Uninstall() 572 { 573 if (fSubmenu != NULL) 574 fSubmenu->Uninstall(); 575 576 if (Target() == fWindow) 577 SetTarget(BMessenger()); 578 579 // TODO: I'm not sure about B_COMMAND_KEY 580 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow != NULL) 581 fWindow->RemoveShortcut(fShortcutChar, fModifiers); 582 583 fWindow = NULL; 584 } 585 586 587 void 588 BMenuItem::SetSuper(BMenu *super) 589 { 590 if (fSuper != NULL && super != NULL) 591 debugger("Error - can't add menu or menu item to more than 1 container (either menu or menubar)."); 592 593 fSuper = super; 594 595 if (fSubmenu != NULL) 596 fSubmenu->fSuper = super; 597 } 598 599 600 void 601 BMenuItem::Select(bool on) 602 { 603 if (Submenu()) { 604 fSelected = on; 605 Highlight(on); 606 } else if (IsEnabled()) { 607 fSelected = on; 608 Highlight(on); 609 } 610 } 611 612 613 void 614 BMenuItem::DrawMarkSymbol() 615 { 616 fSuper->SetDrawingMode(B_OP_OVER); 617 618 fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 619 B_DARKEN_1_TINT)); 620 fSuper->StrokeLine(BPoint(fBounds.left + 6.0f, fBounds.bottom - 3.0f), 621 BPoint(fBounds.left + 10.0f, fBounds.bottom - 12.0f)); 622 fSuper->StrokeLine(BPoint(fBounds.left + 7.0f, fBounds.bottom - 3.0f), 623 BPoint(fBounds.left + 11.0f, fBounds.bottom - 12.0f)); 624 625 fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 626 B_DARKEN_4_TINT)); 627 628 fSuper->StrokeLine(BPoint(fBounds.left + 6.0f, fBounds.bottom - 4.0f), 629 BPoint(fBounds.left + 10.0f, fBounds.bottom - 13.0f)); 630 fSuper->StrokeLine(BPoint(fBounds.left + 5.0f, fBounds.bottom - 4.0f), 631 BPoint(fBounds.left + 9.0f, fBounds.bottom - 13.0f)); 632 fSuper->StrokeLine(BPoint(fBounds.left + 5.0f, fBounds.bottom - 3.0f), 633 BPoint(fBounds.left + 3.0f, fBounds.bottom - 9.0f)); 634 fSuper->StrokeLine(BPoint(fBounds.left + 4.0f, fBounds.bottom - 4.0f), 635 BPoint(fBounds.left + 2.0f, fBounds.bottom - 9.0f)); 636 637 fSuper->SetDrawingMode(B_OP_COPY); 638 } 639 640 641 void 642 BMenuItem::DrawShortcutSymbol() 643 { 644 BString shortcut(""); 645 646 if (fModifiers & B_CONTROL_KEY) 647 shortcut += "ctl+"; 648 649 shortcut += fShortcutChar; 650 651 fSuper->DrawString(shortcut.String(), ContentLocation() + 652 BPoint(fBounds.Width() - 14.0f - 32.0f, fBounds.Height() - 4.0f)); 653 } 654 655 656 void 657 BMenuItem::DrawSubmenuSymbol() 658 { 659 fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 660 B_LIGHTEN_MAX_TINT)); 661 fSuper->FillTriangle(BPoint(fBounds.right - 14.0f, fBounds.bottom - 4.0f), 662 BPoint(fBounds.right - 14.0f, fBounds.bottom - 12.0f), 663 BPoint(fBounds.right - 5.0f, fBounds.bottom - 8.0f)); 664 665 fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 666 B_DARKEN_2_TINT)); 667 fSuper->StrokeLine(BPoint(fBounds.right - 14.0f, fBounds.bottom - 5), 668 BPoint(fBounds.right - 9.0f, fBounds.bottom - 7)); 669 fSuper->StrokeLine(BPoint(fBounds.right - 7.0f, fBounds.bottom - 8), 670 BPoint(fBounds.right - 7.0f, fBounds.bottom - 8)); 671 672 fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 673 B_DARKEN_3_TINT)); 674 fSuper->StrokeTriangle(BPoint(fBounds.right - 14.0f, fBounds.bottom - 4.0f), 675 BPoint(fBounds.right - 14.0f, fBounds.bottom - 12.0f), 676 BPoint(fBounds.right - 5.0f, fBounds.bottom - 8.0f)); 677 678 fSuper->SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 679 B_LIGHTEN_1_TINT)); 680 fSuper->StrokeTriangle(BPoint(fBounds.right - 12.0f, fBounds.bottom - 7.0f), 681 BPoint(fBounds.right - 12.0f, fBounds.bottom - 9.0f), 682 BPoint(fBounds.right - 9.0f, fBounds.bottom - 8.0f)); 683 fSuper->FillTriangle(BPoint(fBounds.right - 12.0f, fBounds.bottom - 7.0f), 684 BPoint(fBounds.right - 12.0f, fBounds.bottom - 9.0f), 685 BPoint(fBounds.right - 9.0f, fBounds.bottom - 8.0f)); 686 } 687 688 689 void 690 BMenuItem::DrawControlChar(const char *control) 691 { 692 // TODO: Implement 693 } 694 695 696 void 697 BMenuItem::SetSysTrigger(char ch) 698 { 699 fSysTrigger = ch; 700 } 701