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