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