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