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