1 /* 2 * Copyright 2001-2015, 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* name, uint32 resizingMode, 48 menu_layout layout, bool resizeToFit) 49 : 50 BMenu(frame, name, resizingMode, 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* name, menu_layout layout, uint32 flags) 64 : 65 BMenu(BRect(), name, 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* archive) 80 : 81 BMenu(archive), 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 (archive->FindInt32("_border", &border) == B_OK) 92 SetBorder((menu_bar_border)border); 93 94 menu_layout layout = B_ITEMS_IN_COLUMN; 95 archive->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 BRect rect(Bounds()); 283 rgb_color base = LowColor(); 284 uint32 flags = 0; 285 286 be_control_look->DrawBorder(this, rect, updateRect, base, 287 B_PLAIN_BORDER, flags, BControlLook::B_BOTTOM_BORDER); 288 289 be_control_look->DrawMenuBarBackground(this, rect, updateRect, base, 290 0, fBorders); 291 292 DrawItems(updateRect); 293 } 294 295 296 // #pragma mark - 297 298 299 void 300 BMenuBar::MessageReceived(BMessage* message) 301 { 302 BMenu::MessageReceived(message); 303 } 304 305 306 void 307 BMenuBar::MouseDown(BPoint where) 308 { 309 if (fTracking) 310 return; 311 312 uint32 buttons; 313 GetMouse(&where, &buttons); 314 315 BWindow* window = Window(); 316 if (!window->IsActive() || !window->IsFront()) { 317 if ((mouse_mode() == B_FOCUS_FOLLOWS_MOUSE) 318 || ((mouse_mode() == B_CLICK_TO_FOCUS_MOUSE) 319 && ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0))) { 320 // right-click to bring-to-front and send-to-back 321 // (might cause some regressions in FFM) 322 window->Activate(); 323 window->UpdateIfNeeded(); 324 } 325 } 326 327 StartMenuBar(-1, false, false); 328 } 329 330 331 void 332 BMenuBar::MouseUp(BPoint where) 333 { 334 BView::MouseUp(where); 335 } 336 337 338 // #pragma mark - 339 340 341 BHandler* 342 BMenuBar::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier, 343 int32 form, const char* property) 344 { 345 return BMenu::ResolveSpecifier(msg, index, specifier, form, property); 346 } 347 348 349 status_t 350 BMenuBar::GetSupportedSuites(BMessage* data) 351 { 352 return BMenu::GetSupportedSuites(data); 353 } 354 355 356 // #pragma mark - 357 358 359 void 360 BMenuBar::SetBorder(menu_bar_border border) 361 { 362 fBorder = border; 363 } 364 365 366 menu_bar_border 367 BMenuBar::Border() const 368 { 369 return fBorder; 370 } 371 372 373 void 374 BMenuBar::SetBorders(uint32 borders) 375 { 376 fBorders = borders; 377 } 378 379 380 uint32 381 BMenuBar::Borders() const 382 { 383 return fBorders; 384 } 385 386 387 // #pragma mark - 388 389 390 status_t 391 BMenuBar::Perform(perform_code code, void* _data) 392 { 393 switch (code) { 394 case PERFORM_CODE_MIN_SIZE: 395 ((perform_data_min_size*)_data)->return_value 396 = BMenuBar::MinSize(); 397 return B_OK; 398 399 case PERFORM_CODE_MAX_SIZE: 400 ((perform_data_max_size*)_data)->return_value 401 = BMenuBar::MaxSize(); 402 return B_OK; 403 404 case PERFORM_CODE_PREFERRED_SIZE: 405 ((perform_data_preferred_size*)_data)->return_value 406 = BMenuBar::PreferredSize(); 407 return B_OK; 408 409 case PERFORM_CODE_LAYOUT_ALIGNMENT: 410 ((perform_data_layout_alignment*)_data)->return_value 411 = BMenuBar::LayoutAlignment(); 412 return B_OK; 413 414 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 415 ((perform_data_has_height_for_width*)_data)->return_value 416 = BMenuBar::HasHeightForWidth(); 417 return B_OK; 418 419 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 420 { 421 perform_data_get_height_for_width* data 422 = (perform_data_get_height_for_width*)_data; 423 BMenuBar::GetHeightForWidth(data->width, &data->min, &data->max, 424 &data->preferred); 425 return B_OK; 426 } 427 428 case PERFORM_CODE_SET_LAYOUT: 429 { 430 perform_data_set_layout* data = (perform_data_set_layout*)_data; 431 BMenuBar::SetLayout(data->layout); 432 return B_OK; 433 } 434 435 case PERFORM_CODE_LAYOUT_INVALIDATED: 436 { 437 perform_data_layout_invalidated* data 438 = (perform_data_layout_invalidated*)_data; 439 BMenuBar::LayoutInvalidated(data->descendants); 440 return B_OK; 441 } 442 443 case PERFORM_CODE_DO_LAYOUT: 444 { 445 BMenuBar::DoLayout(); 446 return B_OK; 447 } 448 } 449 450 return BMenu::Perform(code, _data); 451 } 452 453 454 // #pragma mark - 455 456 457 void BMenuBar::_ReservedMenuBar1() {} 458 void BMenuBar::_ReservedMenuBar2() {} 459 void BMenuBar::_ReservedMenuBar3() {} 460 void BMenuBar::_ReservedMenuBar4() {} 461 462 463 BMenuBar& 464 BMenuBar::operator=(const BMenuBar &) 465 { 466 return *this; 467 } 468 469 470 // #pragma mark - 471 472 473 void 474 BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu, 475 BRect* specialRect) 476 { 477 if (fTracking) 478 return; 479 480 BWindow* window = Window(); 481 if (window == NULL) 482 debugger("MenuBar must be added to a window before it can be used."); 483 484 BAutolock lock(window); 485 if (!lock.IsLocked()) 486 return; 487 488 fPrevFocusToken = -1; 489 fTracking = true; 490 491 // We are called from the window's thread, 492 // so let's call MenusBeginning() directly 493 window->MenusBeginning(); 494 495 fMenuSem = create_sem(0, "window close sem"); 496 _set_menu_sem_(window, fMenuSem); 497 498 fTrackingPID = spawn_thread(_TrackTask, "menu_tracking", 499 B_DISPLAY_PRIORITY, NULL); 500 if (fTrackingPID >= 0) { 501 menubar_data data; 502 data.menuBar = this; 503 data.menuIndex = menuIndex; 504 data.sticky = sticky; 505 data.showMenu = showMenu; 506 data.useRect = specialRect != NULL; 507 if (data.useRect) 508 data.rect = *specialRect; 509 510 resume_thread(fTrackingPID); 511 send_data(fTrackingPID, 0, &data, sizeof(data)); 512 } else { 513 fTracking = false; 514 _set_menu_sem_(window, B_NO_MORE_SEMS); 515 delete_sem(fMenuSem); 516 } 517 } 518 519 520 /*static*/ int32 521 BMenuBar::_TrackTask(void* arg) 522 { 523 menubar_data data; 524 thread_id id; 525 receive_data(&id, &data, sizeof(data)); 526 527 BMenuBar* menuBar = data.menuBar; 528 if (data.useRect) 529 menuBar->fExtraRect = &data.rect; 530 menuBar->_SetStickyMode(data.sticky); 531 532 int32 action; 533 menuBar->_Track(&action, data.menuIndex, data.showMenu); 534 535 menuBar->fTracking = false; 536 menuBar->fExtraRect = NULL; 537 538 // We aren't the BWindow thread, so don't call MenusEnded() directly 539 BWindow* window = menuBar->Window(); 540 window->PostMessage(_MENUS_DONE_); 541 542 _set_menu_sem_(window, B_BAD_SEM_ID); 543 delete_sem(menuBar->fMenuSem); 544 menuBar->fMenuSem = B_BAD_SEM_ID; 545 546 return 0; 547 } 548 549 550 BMenuItem* 551 BMenuBar::_Track(int32* action, int32 startIndex, bool showMenu) 552 { 553 // TODO: Cleanup, merge some "if" blocks if possible 554 BMenuItem* item = NULL; 555 fState = MENU_STATE_TRACKING; 556 fChosenItem = NULL; 557 // we will use this for keyboard selection 558 559 BPoint where; 560 uint32 buttons; 561 if (LockLooper()) { 562 if (startIndex != -1) { 563 be_app->ObscureCursor(); 564 _SelectItem(ItemAt(startIndex), true, false); 565 } 566 GetMouse(&where, &buttons); 567 UnlockLooper(); 568 } 569 570 while (fState != MENU_STATE_CLOSED) { 571 bigtime_t snoozeAmount = 40000; 572 if (!LockLooper()) 573 break; 574 575 item = dynamic_cast<_BMCMenuBar_*>(this) != NULL ? ItemAt(0) 576 : _HitTestItems(where, B_ORIGIN); 577 578 if (_OverSubmenu(fSelected, ConvertToScreen(where)) 579 || fState == MENU_STATE_KEY_TO_SUBMENU) { 580 // call _Track() from the selected sub-menu when the mouse cursor 581 // is over its window 582 BMenu* submenu = fSelected->Submenu(); 583 UnlockLooper(); 584 snoozeAmount = 30000; 585 submenu->_SetStickyMode(_IsStickyMode()); 586 int localAction; 587 fChosenItem = submenu->_Track(&localAction); 588 589 // The mouse could have meen moved since the last time we 590 // checked its position, or buttons might have been pressed. 591 // Unfortunately our child menus don't tell 592 // us the new position. 593 // TODO: Maybe have a shared struct between all menus 594 // where to store the current mouse position ? 595 // (Or just use the BView mouse hooks) 596 BPoint newWhere; 597 if (LockLooper()) { 598 GetMouse(&newWhere, &buttons); 599 UnlockLooper(); 600 } 601 602 // Needed to make BMenuField child menus "sticky" 603 // (see ticket #953) 604 if (localAction == MENU_STATE_CLOSED) { 605 if (fExtraRect != NULL && fExtraRect->Contains(where) 606 && point_distance(newWhere, where) < 9) { 607 // 9 = 3 pixels ^ 2 (since point_distance() returns the 608 // square of the distance) 609 _SetStickyMode(true); 610 fExtraRect = NULL; 611 } else 612 fState = MENU_STATE_CLOSED; 613 } 614 if (!LockLooper()) 615 break; 616 } else if (item != NULL) { 617 if (item->Submenu() != NULL && item != fSelected) { 618 if (item->Submenu()->Window() == NULL) { 619 // open the menu if it's not opened yet 620 _SelectItem(item); 621 } else { 622 // Menu was already opened, close it and bail 623 _SelectItem(NULL); 624 fState = MENU_STATE_CLOSED; 625 fChosenItem = NULL; 626 } 627 } else { 628 // No submenu, just select the item 629 _SelectItem(item); 630 } 631 } else if (item == NULL && fSelected != NULL 632 && !_IsStickyMode() && Bounds().Contains(where)) { 633 _SelectItem(NULL); 634 fState = MENU_STATE_TRACKING; 635 } 636 637 UnlockLooper(); 638 639 if (fState != MENU_STATE_CLOSED) { 640 BPoint newWhere = where; 641 uint32 newButtons = buttons; 642 643 do { 644 // If user doesn't move the mouse or change buttons loop 645 // here so that we don't interfere with keyboard menu 646 // navigation 647 snooze(snoozeAmount); 648 if (!LockLooper()) 649 break; 650 651 GetMouse(&newWhere, &newButtons); 652 UnlockLooper(); 653 } while (newWhere == where && newButtons == buttons 654 && fState == MENU_STATE_TRACKING); 655 656 if (newButtons != 0 && _IsStickyMode()) { 657 if (item == NULL || (item->Submenu() != NULL 658 && item->Submenu()->Window() != NULL)) { 659 // clicked outside the menu bar or on item with already 660 // open sub menu 661 fState = MENU_STATE_CLOSED; 662 } else 663 _SetStickyMode(false); 664 } else if (newButtons == 0 && !_IsStickyMode()) { 665 if ((fSelected != NULL && fSelected->Submenu() == NULL) 666 || item == NULL) { 667 // clicked on an item without a submenu or clicked and 668 // released the mouse button outside the menu bar 669 fChosenItem = fSelected; 670 fState = MENU_STATE_CLOSED; 671 } else 672 _SetStickyMode(true); 673 } 674 where = newWhere; 675 buttons = newButtons; 676 } 677 } 678 679 if (LockLooper()) { 680 if (fSelected != NULL) 681 _SelectItem(NULL); 682 683 if (fChosenItem != NULL) 684 fChosenItem->Invoke(); 685 686 _RestoreFocus(); 687 UnlockLooper(); 688 } 689 690 if (_IsStickyMode()) 691 _SetStickyMode(false); 692 693 _DeleteMenuWindow(); 694 695 if (action != NULL) 696 *action = fState; 697 698 return fChosenItem; 699 } 700 701 702 void 703 BMenuBar::_StealFocus() 704 { 705 // We already stole the focus, don't do anything 706 if (fPrevFocusToken != -1) 707 return; 708 709 BWindow* window = Window(); 710 if (window != NULL && window->Lock()) { 711 BView* focusView = window->CurrentFocus(); 712 if (focusView != NULL && focusView != this) 713 fPrevFocusToken = _get_object_token_(focusView); 714 MakeFocus(); 715 window->Unlock(); 716 } 717 } 718 719 720 void 721 BMenuBar::_RestoreFocus() 722 { 723 BWindow* window = Window(); 724 if (window != NULL && window->Lock()) { 725 BHandler* handler = NULL; 726 if (fPrevFocusToken != -1 727 && gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN, 728 (void**)&handler) == B_OK) { 729 BView* view = dynamic_cast<BView*>(handler); 730 if (view != NULL && view->Window() == window) 731 view->MakeFocus(); 732 } else if (IsFocus()) 733 MakeFocus(false); 734 735 fPrevFocusToken = -1; 736 window->Unlock(); 737 } 738 } 739 740 741 void 742 BMenuBar::_InitData(menu_layout layout) 743 { 744 fBorders = BControlLook::B_ALL_BORDERS; 745 fLastBounds = new BRect(Bounds()); 746 SetItemMargins(8.0f, 2.0f, 8.0f, 2.0f); 747 _SetIgnoreHidden(true); 748 SetLowUIColor(B_MENU_BACKGROUND_COLOR); 749 SetViewColor(B_TRANSPARENT_COLOR); 750 } 751