1 /* 2 * Copyright 2001-2009, Haiku Inc. All rights reserved. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Marc Flerackers (mflerackers@androme.be) 7 * Stefano Ceccherini (burton666@libero.it) 8 * Stephan Aßmus <superstippi@gmx.de> 9 */ 10 11 12 #include <MenuBar.h> 13 14 #include <math.h> 15 16 #include <Application.h> 17 #include <Autolock.h> 18 #include <ControlLook.h> 19 #include <LayoutUtils.h> 20 #include <MenuItem.h> 21 #include <Window.h> 22 23 #include <AppMisc.h> 24 #include <binary_compatibility/Interface.h> 25 #include <MenuPrivate.h> 26 #include <TokenSpace.h> 27 #include <InterfaceDefs.h> 28 29 #include "BMCPrivate.h" 30 31 32 using BPrivate::gDefaultTokens; 33 34 35 struct menubar_data { 36 BMenuBar* menuBar; 37 int32 menuIndex; 38 39 bool sticky; 40 bool showMenu; 41 42 bool useRect; 43 BRect rect; 44 }; 45 46 47 BMenuBar::BMenuBar(BRect frame, const char* title, uint32 resizeMask, 48 menu_layout layout, bool resizeToFit) 49 : 50 BMenu(frame, title, resizeMask, B_WILL_DRAW | B_FRAME_EVENTS 51 | B_FULL_UPDATE_ON_RESIZE, layout, resizeToFit), 52 fBorder(B_BORDER_FRAME), 53 fTrackingPID(-1), 54 fPrevFocusToken(-1), 55 fMenuSem(-1), 56 fLastBounds(NULL), 57 fTracking(false) 58 { 59 _InitData(layout); 60 } 61 62 63 BMenuBar::BMenuBar(const char* title, menu_layout layout, uint32 flags) 64 : 65 BMenu(BRect(), title, B_FOLLOW_NONE, 66 flags | B_WILL_DRAW | B_FRAME_EVENTS | B_SUPPORTS_LAYOUT, 67 layout, false), 68 fBorder(B_BORDER_FRAME), 69 fTrackingPID(-1), 70 fPrevFocusToken(-1), 71 fMenuSem(-1), 72 fLastBounds(NULL), 73 fTracking(false) 74 { 75 _InitData(layout); 76 } 77 78 79 BMenuBar::BMenuBar(BMessage* data) 80 : 81 BMenu(data), 82 fBorder(B_BORDER_FRAME), 83 fTrackingPID(-1), 84 fPrevFocusToken(-1), 85 fMenuSem(-1), 86 fLastBounds(NULL), 87 fTracking(false) 88 { 89 int32 border; 90 91 if (data->FindInt32("_border", &border) == B_OK) 92 SetBorder((menu_bar_border)border); 93 94 menu_layout layout = B_ITEMS_IN_COLUMN; 95 data->FindInt32("_layout", (int32*)&layout); 96 97 _InitData(layout); 98 } 99 100 101 BMenuBar::~BMenuBar() 102 { 103 if (fTracking) { 104 status_t dummy; 105 wait_for_thread(fTrackingPID, &dummy); 106 } 107 108 delete fLastBounds; 109 } 110 111 112 BArchivable* 113 BMenuBar::Instantiate(BMessage* data) 114 { 115 if (validate_instantiation(data, "BMenuBar")) 116 return new BMenuBar(data); 117 118 return NULL; 119 } 120 121 122 status_t 123 BMenuBar::Archive(BMessage* data, bool deep) const 124 { 125 status_t err = BMenu::Archive(data, deep); 126 127 if (err < B_OK) 128 return err; 129 130 if (Border() != B_BORDER_FRAME) 131 err = data->AddInt32("_border", Border()); 132 133 return err; 134 } 135 136 137 // #pragma mark - 138 139 140 void 141 BMenuBar::AttachedToWindow() 142 { 143 _Install(Window()); 144 Window()->SetKeyMenuBar(this); 145 146 BMenu::AttachedToWindow(); 147 148 *fLastBounds = Bounds(); 149 } 150 151 152 void 153 BMenuBar::DetachedFromWindow() 154 { 155 BMenu::DetachedFromWindow(); 156 } 157 158 159 void 160 BMenuBar::AllAttached() 161 { 162 BMenu::AllAttached(); 163 } 164 165 166 void 167 BMenuBar::AllDetached() 168 { 169 BMenu::AllDetached(); 170 } 171 172 173 void 174 BMenuBar::WindowActivated(bool state) 175 { 176 BView::WindowActivated(state); 177 } 178 179 180 void 181 BMenuBar::MakeFocus(bool state) 182 { 183 BMenu::MakeFocus(state); 184 } 185 186 187 // #pragma mark - 188 189 190 void 191 BMenuBar::ResizeToPreferred() 192 { 193 BMenu::ResizeToPreferred(); 194 } 195 196 197 void 198 BMenuBar::GetPreferredSize(float* width, float* height) 199 { 200 BMenu::GetPreferredSize(width, height); 201 } 202 203 204 BSize 205 BMenuBar::MinSize() 206 { 207 return BMenu::MinSize(); 208 } 209 210 211 BSize 212 BMenuBar::MaxSize() 213 { 214 BSize size = BMenu::MaxSize(); 215 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 216 BSize(B_SIZE_UNLIMITED, size.height)); 217 } 218 219 220 BSize 221 BMenuBar::PreferredSize() 222 { 223 return BMenu::PreferredSize(); 224 } 225 226 227 void 228 BMenuBar::FrameMoved(BPoint newPosition) 229 { 230 BMenu::FrameMoved(newPosition); 231 } 232 233 234 void 235 BMenuBar::FrameResized(float newWidth, float newHeight) 236 { 237 // invalidate right border 238 if (newWidth != fLastBounds->Width()) { 239 BRect rect(min_c(fLastBounds->right, newWidth), 0, 240 max_c(fLastBounds->right, newWidth), newHeight); 241 Invalidate(rect); 242 } 243 244 // invalidate bottom border 245 if (newHeight != fLastBounds->Height()) { 246 BRect rect(0, min_c(fLastBounds->bottom, newHeight) - 1, 247 newWidth, max_c(fLastBounds->bottom, newHeight)); 248 Invalidate(rect); 249 } 250 251 fLastBounds->Set(0, 0, newWidth, newHeight); 252 253 BMenu::FrameResized(newWidth, newHeight); 254 } 255 256 257 // #pragma mark - 258 259 260 void 261 BMenuBar::Show() 262 { 263 BView::Show(); 264 } 265 266 267 void 268 BMenuBar::Hide() 269 { 270 BView::Hide(); 271 } 272 273 274 void 275 BMenuBar::Draw(BRect updateRect) 276 { 277 if (_RelayoutIfNeeded()) { 278 Invalidate(); 279 return; 280 } 281 282 if (be_control_look != NULL) { 283 BRect rect(Bounds()); 284 rgb_color base = LowColor(); 285 uint32 flags = 0; 286 287 be_control_look->DrawBorder(this, rect, updateRect, base, 288 B_PLAIN_BORDER, flags, BControlLook::B_BOTTOM_BORDER); 289 290 be_control_look->DrawMenuBarBackground(this, rect, updateRect, base); 291 292 _DrawItems(updateRect); 293 return; 294 } 295 296 // TODO: implement additional border styles 297 rgb_color color = HighColor(); 298 299 BRect bounds(Bounds()); 300 // Restore the background of the previously selected menuitem 301 DrawBackground(bounds & updateRect); 302 303 rgb_color noTint = LowColor(); 304 305 SetHighColor(tint_color(noTint, B_LIGHTEN_2_TINT)); 306 StrokeLine(BPoint(0.0f, bounds.bottom - 2.0f), BPoint(0.0f, 0.0f)); 307 StrokeLine(BPoint(bounds.right, 0.0f)); 308 309 SetHighColor(tint_color(noTint, B_DARKEN_1_TINT)); 310 StrokeLine(BPoint(1.0f, bounds.bottom - 1.0f), 311 BPoint(bounds.right, bounds.bottom - 1.0f)); 312 313 SetHighColor(tint_color(noTint, B_DARKEN_2_TINT)); 314 StrokeLine(BPoint(0.0f, bounds.bottom), 315 BPoint(bounds.right, bounds.bottom)); 316 StrokeLine(BPoint(bounds.right, 0.0f), BPoint(bounds.right, bounds.bottom)); 317 318 SetHighColor(color); 319 // revert to previous used color (cheap PushState()/PopState()) 320 321 _DrawItems(updateRect); 322 } 323 324 325 // #pragma mark - 326 327 328 void 329 BMenuBar::MessageReceived(BMessage* msg) 330 { 331 BMenu::MessageReceived(msg); 332 } 333 334 335 void 336 BMenuBar::MouseDown(BPoint where) 337 { 338 if (fTracking) 339 return; 340 341 uint32 buttons; 342 GetMouse(&where, &buttons); 343 344 BWindow* window = Window(); 345 if (!window->IsActive() || !window->IsFront()) { 346 if ((mouse_mode() == B_FOCUS_FOLLOWS_MOUSE) 347 || ((mouse_mode() == B_CLICK_TO_FOCUS_MOUSE) 348 && ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0))) { 349 window->Activate(); 350 window->UpdateIfNeeded(); 351 } 352 } 353 354 StartMenuBar(-1, false, false); 355 } 356 357 358 void 359 BMenuBar::MouseUp(BPoint where) 360 { 361 BView::MouseUp(where); 362 } 363 364 365 // #pragma mark - 366 367 368 BHandler* 369 BMenuBar::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier, 370 int32 form, const char* property) 371 { 372 return BMenu::ResolveSpecifier(msg, index, specifier, form, property); 373 } 374 375 376 status_t 377 BMenuBar::GetSupportedSuites(BMessage* data) 378 { 379 return BMenu::GetSupportedSuites(data); 380 } 381 382 383 // #pragma mark - 384 385 386 void 387 BMenuBar::SetBorder(menu_bar_border border) 388 { 389 fBorder = border; 390 } 391 392 393 menu_bar_border 394 BMenuBar::Border() const 395 { 396 return fBorder; 397 } 398 399 400 // #pragma mark - 401 402 403 status_t 404 BMenuBar::Perform(perform_code code, void* _data) 405 { 406 switch (code) { 407 case PERFORM_CODE_MIN_SIZE: 408 ((perform_data_min_size*)_data)->return_value 409 = BMenuBar::MinSize(); 410 return B_OK; 411 case PERFORM_CODE_MAX_SIZE: 412 ((perform_data_max_size*)_data)->return_value 413 = BMenuBar::MaxSize(); 414 return B_OK; 415 case PERFORM_CODE_PREFERRED_SIZE: 416 ((perform_data_preferred_size*)_data)->return_value 417 = BMenuBar::PreferredSize(); 418 return B_OK; 419 case PERFORM_CODE_LAYOUT_ALIGNMENT: 420 ((perform_data_layout_alignment*)_data)->return_value 421 = BMenuBar::LayoutAlignment(); 422 return B_OK; 423 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 424 ((perform_data_has_height_for_width*)_data)->return_value 425 = BMenuBar::HasHeightForWidth(); 426 return B_OK; 427 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 428 { 429 perform_data_get_height_for_width* data 430 = (perform_data_get_height_for_width*)_data; 431 BMenuBar::GetHeightForWidth(data->width, &data->min, &data->max, 432 &data->preferred); 433 return B_OK; 434 } 435 case PERFORM_CODE_SET_LAYOUT: 436 { 437 perform_data_set_layout* data = (perform_data_set_layout*)_data; 438 BMenuBar::SetLayout(data->layout); 439 return B_OK; 440 } 441 case PERFORM_CODE_LAYOUT_INVALIDATED: 442 { 443 perform_data_layout_invalidated* data 444 = (perform_data_layout_invalidated*)_data; 445 BMenuBar::LayoutInvalidated(data->descendants); 446 return B_OK; 447 } 448 case PERFORM_CODE_DO_LAYOUT: 449 { 450 BMenuBar::DoLayout(); 451 return B_OK; 452 } 453 } 454 455 return BMenu::Perform(code, _data); 456 } 457 458 459 // #pragma mark - 460 461 462 void BMenuBar::_ReservedMenuBar1() {} 463 void BMenuBar::_ReservedMenuBar2() {} 464 void BMenuBar::_ReservedMenuBar3() {} 465 void BMenuBar::_ReservedMenuBar4() {} 466 467 468 BMenuBar & 469 BMenuBar::operator=(const BMenuBar &) 470 { 471 return *this; 472 } 473 474 475 // #pragma mark - 476 477 478 void 479 BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu, 480 BRect* specialRect) 481 { 482 if (fTracking) 483 return; 484 485 BWindow* window = Window(); 486 if (window == NULL) 487 debugger("MenuBar must be added to a window before it can be used."); 488 489 BAutolock lock(window); 490 if (!lock.IsLocked()) 491 return; 492 493 fPrevFocusToken = -1; 494 fTracking = true; 495 496 // We are called from the window's thread, 497 // so let's call MenusBeginning() directly 498 window->MenusBeginning(); 499 500 fMenuSem = create_sem(0, "window close sem"); 501 _set_menu_sem_(window, fMenuSem); 502 503 fTrackingPID = spawn_thread(_TrackTask, "menu_tracking", 504 B_DISPLAY_PRIORITY, NULL); 505 if (fTrackingPID >= 0) { 506 menubar_data data; 507 data.menuBar = this; 508 data.menuIndex = menuIndex; 509 data.sticky = sticky; 510 data.showMenu = showMenu; 511 data.useRect = specialRect != NULL; 512 if (data.useRect) 513 data.rect = *specialRect; 514 515 resume_thread(fTrackingPID); 516 send_data(fTrackingPID, 0, &data, sizeof(data)); 517 } else { 518 fTracking = false; 519 _set_menu_sem_(window, B_NO_MORE_SEMS); 520 delete_sem(fMenuSem); 521 } 522 } 523 524 525 /*static*/ int32 526 BMenuBar::_TrackTask(void* arg) 527 { 528 menubar_data data; 529 thread_id id; 530 receive_data(&id, &data, sizeof(data)); 531 532 BMenuBar* menuBar = data.menuBar; 533 if (data.useRect) 534 menuBar->fExtraRect = &data.rect; 535 menuBar->_SetStickyMode(data.sticky); 536 537 int32 action; 538 menuBar->_Track(&action, data.menuIndex, data.showMenu); 539 540 menuBar->fTracking = false; 541 menuBar->fExtraRect = NULL; 542 543 // We aren't the BWindow thread, so don't call MenusEnded() directly 544 BWindow* window = menuBar->Window(); 545 window->PostMessage(_MENUS_DONE_); 546 547 _set_menu_sem_(window, B_BAD_SEM_ID); 548 delete_sem(menuBar->fMenuSem); 549 menuBar->fMenuSem = B_BAD_SEM_ID; 550 551 return 0; 552 } 553 554 555 BMenuItem* 556 BMenuBar::_Track(int32* action, int32 startIndex, bool showMenu) 557 { 558 // TODO: Cleanup, merge some "if" blocks if possible 559 fChosenItem = NULL; 560 fState = MENU_STATE_TRACKING; 561 562 BPoint where; 563 uint32 buttons; 564 if (LockLooper()) { 565 if (startIndex != -1) { 566 be_app->ObscureCursor(); 567 _SelectItem(ItemAt(startIndex), true, false); 568 } 569 GetMouse(&where, &buttons); 570 UnlockLooper(); 571 } 572 573 while (fState != MENU_STATE_CLOSED) { 574 bigtime_t snoozeAmount = 40000; 575 if (!LockLooper()) 576 break; 577 578 BMenuItem* menuItem = NULL; 579 if (dynamic_cast<_BMCMenuBar_*>(this)) 580 menuItem = ItemAt(0); 581 else 582 menuItem = _HitTestItems(where, B_ORIGIN); 583 if (_OverSubmenu(fSelected, ConvertToScreen(where)) 584 || fState == MENU_STATE_KEY_TO_SUBMENU) { 585 // call _Track() from the selected sub-menu when the mouse cursor 586 // is over its window 587 BMenu* menu = fSelected->Submenu(); 588 UnlockLooper(); 589 snoozeAmount = 30000; 590 bool wasSticky = _IsStickyMode(); 591 menu->_SetStickyMode(wasSticky); 592 int localAction; 593 fChosenItem = menu->_Track(&localAction); 594 595 // The mouse could have meen moved since the last time we 596 // checked its position, or buttons might have been pressed. 597 // Unfortunately our child menus don't tell 598 // us the new position. 599 // TODO: Maybe have a shared struct between all menus 600 // where to store the current mouse position ? 601 // (Or just use the BView mouse hooks) 602 BPoint newWhere; 603 if (LockLooper()) { 604 GetMouse(&newWhere, &buttons); 605 UnlockLooper(); 606 } 607 608 // This code is needed to make menus 609 // that are children of BMenuFields "sticky" (see ticket #953) 610 if (localAction == MENU_STATE_CLOSED) { 611 if (fExtraRect != NULL && fExtraRect->Contains(where) 612 // 9 = 3 pixels ^ 2 (since point_distance() returns the 613 // square of the distance) 614 && point_distance(newWhere, where) < 9) { 615 _SetStickyMode(true); 616 fExtraRect = NULL; 617 } else 618 fState = MENU_STATE_CLOSED; 619 } 620 if (!LockLooper()) 621 break; 622 } else if (menuItem != NULL) { 623 if (menuItem->Submenu() != NULL && menuItem != fSelected) { 624 if (menuItem->Submenu()->Window() == NULL) { 625 // open the menu if it's not opened yet 626 _SelectItem(menuItem); 627 } else { 628 // Menu was already opened, close it and bail 629 _SelectItem(NULL); 630 fState = MENU_STATE_CLOSED; 631 fChosenItem = NULL; 632 } 633 } else { 634 // No submenu, just select the item 635 _SelectItem(menuItem); 636 } 637 } else if (menuItem == NULL && fSelected != NULL 638 && !_IsStickyMode() && Bounds().Contains(where)) { 639 _SelectItem(NULL); 640 fState = MENU_STATE_TRACKING; 641 } 642 643 UnlockLooper(); 644 645 if (fState != MENU_STATE_CLOSED) { 646 // If user doesn't move the mouse, loop here, 647 // so we don't interfere with keyboard menu navigation 648 BPoint newLocation = where; 649 uint32 newButtons = buttons; 650 do { 651 snooze(snoozeAmount); 652 if (!LockLooper()) 653 break; 654 GetMouse(&newLocation, &newButtons, true); 655 UnlockLooper(); 656 } while (newLocation == where && newButtons == buttons 657 && fState == MENU_STATE_TRACKING); 658 659 where = newLocation; 660 buttons = newButtons; 661 662 if (buttons != 0 && _IsStickyMode()) { 663 if (menuItem == NULL 664 || (menuItem->Submenu() && menuItem->Submenu()->Window())) { 665 // clicked outside menu bar or on item with already 666 // open sub menu 667 fState = MENU_STATE_CLOSED; 668 } else 669 _SetStickyMode(false); 670 } else if (buttons == 0 && !_IsStickyMode()) { 671 if ((fSelected != NULL && fSelected->Submenu() == NULL) 672 || menuItem == NULL) { 673 fChosenItem = fSelected; 674 fState = MENU_STATE_CLOSED; 675 } else 676 _SetStickyMode(true); 677 } 678 } 679 } 680 681 if (LockLooper()) { 682 if (fSelected != NULL) 683 _SelectItem(NULL); 684 685 if (fChosenItem != NULL) 686 fChosenItem->Invoke(); 687 _RestoreFocus(); 688 UnlockLooper(); 689 } 690 691 if (_IsStickyMode()) 692 _SetStickyMode(false); 693 694 _DeleteMenuWindow(); 695 696 if (action != NULL) 697 *action = fState; 698 699 return fChosenItem; 700 } 701 702 703 void 704 BMenuBar::_StealFocus() 705 { 706 // We already stole the focus, don't do anything 707 if (fPrevFocusToken != -1) 708 return; 709 710 BWindow* window = Window(); 711 if (window != NULL && window->Lock()) { 712 BView* focus = window->CurrentFocus(); 713 if (focus != NULL && focus != this) 714 fPrevFocusToken = _get_object_token_(focus); 715 MakeFocus(); 716 window->Unlock(); 717 } 718 } 719 720 721 void 722 BMenuBar::_RestoreFocus() 723 { 724 BWindow* window = Window(); 725 if (window != NULL && window->Lock()) { 726 BHandler* handler = NULL; 727 if (fPrevFocusToken != -1 728 && gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN, 729 (void**)&handler) == B_OK) { 730 BView* view = dynamic_cast<BView*>(handler); 731 if (view != NULL && view->Window() == window) 732 view->MakeFocus(); 733 734 } else if (IsFocus()) 735 MakeFocus(false); 736 737 fPrevFocusToken = -1; 738 window->Unlock(); 739 } 740 } 741 742 743 void 744 BMenuBar::_InitData(menu_layout layout) 745 { 746 fLastBounds = new BRect(Bounds()); 747 SetItemMargins(8, 2, 8, 2); 748 _SetIgnoreHidden(true); 749 } 750