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