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