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