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