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, layout, 51 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_INVALIDATE_LAYOUT: 442 { 443 perform_data_invalidate_layout* data 444 = (perform_data_invalidate_layout*)_data; 445 BMenuBar::InvalidateLayout(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", B_DISPLAY_PRIORITY, 504 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 561 BWindow* window = Window(); 562 fState = MENU_STATE_TRACKING; 563 564 BPoint where; 565 uint32 buttons; 566 if (window->Lock()) { 567 if (startIndex != -1) { 568 be_app->ObscureCursor(); 569 _SelectItem(ItemAt(startIndex), true, false); 570 } 571 GetMouse(&where, &buttons); 572 window->Unlock(); 573 } 574 575 while (fState != MENU_STATE_CLOSED) { 576 bigtime_t snoozeAmount = 40000; 577 if (Window() == NULL || !window->Lock()) 578 break; 579 580 BMenuItem* menuItem = NULL; 581 if (dynamic_cast<_BMCMenuBar_*>(this)) 582 menuItem = ItemAt(0); 583 else 584 menuItem = _HitTestItems(where, B_ORIGIN); 585 if (_OverSubmenu(fSelected, ConvertToScreen(where)) 586 || fState == MENU_STATE_KEY_TO_SUBMENU) { 587 // call _Track() from the selected sub-menu when the mouse cursor 588 // is over its window 589 BMenu* menu = fSelected->Submenu(); 590 window->Unlock(); 591 snoozeAmount = 30000; 592 bool wasSticky = _IsStickyMode(); 593 menu->_SetStickyMode(wasSticky); 594 int localAction; 595 fChosenItem = menu->_Track(&localAction); 596 597 // The mouse could have meen moved since the last time we 598 // checked its position, or buttons might have been pressed. 599 // Unfortunately our child menus don't tell 600 // us the new position. 601 // TODO: Maybe have a shared struct between all menus 602 // where to store the current mouse position ? 603 // (Or just use the BView mouse hooks) 604 BPoint newWhere; 605 if (window->Lock()) { 606 GetMouse(&newWhere, &buttons); 607 window->Unlock(); 608 } 609 610 // This code is needed to make menus 611 // that are children of BMenuFields "sticky" (see ticket #953) 612 if (localAction == MENU_STATE_CLOSED) { 613 if (fExtraRect != NULL && fExtraRect->Contains(where) 614 // 9 = 3 pixels ^ 2 (since point_distance() returns the 615 // square of the distance) 616 && point_distance(newWhere, where) < 9) { 617 _SetStickyMode(true); 618 fExtraRect = NULL; 619 } else 620 fState = MENU_STATE_CLOSED; 621 } 622 if (!window->Lock()) 623 break; 624 } else if (menuItem != NULL) { 625 if (menuItem->Submenu() != NULL && menuItem != fSelected) { 626 if (menuItem->Submenu()->Window() == NULL) { 627 // open the menu if it's not opened yet 628 _SelectItem(menuItem); 629 } else { 630 // Menu was already opened, close it and bail 631 _SelectItem(NULL); 632 fState = MENU_STATE_CLOSED; 633 fChosenItem = NULL; 634 } 635 } else { 636 // No submenu, just select the item 637 _SelectItem(menuItem); 638 } 639 } else if (menuItem == NULL && fSelected != NULL 640 && !_IsStickyMode() && Bounds().Contains(where)) { 641 _SelectItem(NULL); 642 fState = MENU_STATE_TRACKING; 643 } 644 645 window->Unlock(); 646 647 if (fState != MENU_STATE_CLOSED) { 648 // If user doesn't move the mouse, loop here, 649 // so we don't interfere with keyboard menu navigation 650 BPoint newLocation = where; 651 uint32 newButtons = buttons; 652 do { 653 snooze(snoozeAmount); 654 if (!LockLooper()) 655 break; 656 GetMouse(&newLocation, &newButtons, true); 657 UnlockLooper(); 658 } while (newLocation == where && newButtons == buttons 659 && fState == MENU_STATE_TRACKING); 660 661 where = newLocation; 662 buttons = newButtons; 663 664 if (buttons != 0 && _IsStickyMode()) { 665 if (menuItem == NULL 666 || (menuItem->Submenu() && menuItem->Submenu()->Window())) { 667 // clicked outside menu bar or on item with already 668 // open sub menu 669 fState = MENU_STATE_CLOSED; 670 } else 671 _SetStickyMode(false); 672 } else if (buttons == 0 && !_IsStickyMode()) { 673 if ((fSelected != NULL && fSelected->Submenu() == NULL) 674 || menuItem == NULL) { 675 fChosenItem = fSelected; 676 fState = MENU_STATE_CLOSED; 677 } else 678 _SetStickyMode(true); 679 } 680 } 681 } 682 683 if (window->Lock()) { 684 if (fSelected != NULL) 685 _SelectItem(NULL); 686 687 if (fChosenItem != NULL) 688 fChosenItem->Invoke(); 689 _RestoreFocus(); 690 window->Unlock(); 691 } 692 693 if (_IsStickyMode()) 694 _SetStickyMode(false); 695 696 _DeleteMenuWindow(); 697 698 if (action != NULL) 699 *action = fState; 700 701 return fChosenItem; 702 703 } 704 705 706 void 707 BMenuBar::_StealFocus() 708 { 709 // We already stole the focus, don't do anything 710 if (fPrevFocusToken != -1) 711 return; 712 713 BWindow* window = Window(); 714 if (window != NULL && window->Lock()) { 715 BView* focus = window->CurrentFocus(); 716 if (focus != NULL && focus != this) 717 fPrevFocusToken = _get_object_token_(focus); 718 MakeFocus(); 719 window->Unlock(); 720 } 721 } 722 723 724 void 725 BMenuBar::_RestoreFocus() 726 { 727 BWindow* window = Window(); 728 if (window != NULL && window->Lock()) { 729 BHandler* handler = NULL; 730 if (fPrevFocusToken != -1 731 && gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN, 732 (void**)&handler) == B_OK) { 733 BView* view = dynamic_cast<BView*>(handler); 734 if (view != NULL && view->Window() == window) 735 view->MakeFocus(); 736 737 } else if (IsFocus()) 738 MakeFocus(false); 739 740 fPrevFocusToken = -1; 741 window->Unlock(); 742 } 743 } 744 745 746 void 747 BMenuBar::_InitData(menu_layout layout) 748 { 749 fLastBounds = new BRect(Bounds()); 750 SetItemMargins(8, 2, 8, 2); 751 _SetIgnoreHidden(true); 752 } 753