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 // We are called from the window's thread, 389 // so let's call MenusBeginning() directly 390 window->MenusBeginning(); 391 392 fMenuSem = create_sem(0, "window close sem"); 393 _set_menu_sem_(window, fMenuSem); 394 395 fTrackingPID = spawn_thread(_TrackTask, "menu_tracking", B_DISPLAY_PRIORITY, NULL); 396 if (fTrackingPID >= 0) { 397 menubar_data data; 398 data.menuBar = this; 399 data.menuIndex = menuIndex; 400 data.sticky = sticky; 401 data.showMenu = showMenu; 402 data.useRect = specialRect != NULL; 403 if (data.useRect) 404 data.rect = *specialRect; 405 406 resume_thread(fTrackingPID); 407 send_data(fTrackingPID, 0, &data, sizeof(data)); 408 409 } else { 410 fTracking = false; 411 _set_menu_sem_(window, B_NO_MORE_SEMS); 412 delete_sem(fMenuSem); 413 } 414 } 415 416 417 /* static */ 418 int32 419 BMenuBar::_TrackTask(void *arg) 420 { 421 menubar_data data; 422 thread_id id; 423 receive_data(&id, &data, sizeof(data)); 424 425 BMenuBar *menuBar = data.menuBar; 426 if (data.useRect) 427 menuBar->fExtraRect = &data.rect; 428 menuBar->_SetStickyMode(data.sticky); 429 430 int32 action; 431 menuBar->_Track(&action, data.menuIndex, data.showMenu); 432 433 menuBar->fTracking = false; 434 menuBar->fExtraRect = NULL; 435 436 // We aren't the BWindow thread, so don't call MenusEnded() directly 437 BWindow *window = menuBar->Window(); 438 window->PostMessage(_MENUS_DONE_); 439 440 _set_menu_sem_(window, B_BAD_SEM_ID); 441 delete_sem(menuBar->fMenuSem); 442 menuBar->fMenuSem = B_BAD_SEM_ID; 443 444 return 0; 445 } 446 447 448 BMenuItem * 449 BMenuBar::_Track(int32 *action, int32 startIndex, bool showMenu) 450 { 451 // TODO: Cleanup, merge some "if" blocks if possible 452 fChosenItem = NULL; 453 454 BWindow *window = Window(); 455 fState = MENU_STATE_TRACKING; 456 457 BPoint where; 458 uint32 buttons; 459 if (window->Lock()) { 460 if (startIndex != -1) { 461 be_app->ObscureCursor(); 462 _SelectItem(ItemAt(startIndex), true, true); 463 } 464 GetMouse(&where, &buttons); 465 window->Unlock(); 466 } 467 468 while (fState != MENU_STATE_CLOSED) { 469 bigtime_t snoozeAmount = 40000; 470 if (Window() == NULL || !window->Lock()) 471 break; 472 473 BMenuItem *menuItem = _HitTestItems(where, B_ORIGIN); 474 if (menuItem != NULL) { 475 // Select item if: 476 // - no previous selection 477 // - nonsticky mode and different selection, 478 // - clicked in sticky mode 479 if (fSelected == NULL 480 || (!_IsStickyMode() && menuItem != fSelected) 481 || (buttons != 0 && _IsStickyMode())) { 482 if (menuItem->Submenu() != NULL) { 483 if (menuItem->Submenu()->Window() == NULL) { 484 // open the menu if it's not opened yet 485 _SelectItem(menuItem); 486 if (_IsStickyMode()) 487 _SetStickyMode(false); 488 } else { 489 // Menu was already opened, close it and bail 490 _SelectItem(NULL); 491 fState = MENU_STATE_CLOSED; 492 fChosenItem = NULL; 493 } 494 } else { 495 // No submenu, just select the item 496 _SelectItem(menuItem); 497 } 498 } 499 } else if (_OverSubmenu(fSelected, ConvertToScreen(where))) { 500 // call _Track() from the selected sub-menu when the mouse cursor 501 // is over its window 502 BMenu *menu = fSelected->Submenu(); 503 window->Unlock(); 504 snoozeAmount = 30000; 505 bool wasSticky = _IsStickyMode(); 506 menu->_SetStickyMode(wasSticky); 507 int localAction; 508 fChosenItem = menu->_Track(&localAction); 509 510 // The mouse could have meen moved since the last time we 511 // checked its position, or buttons might have been pressed. 512 // Unfortunately our child menus don't tell 513 // us the new position. 514 // TODO: Maybe have a shared struct between all menus 515 // where to store the current mouse position ? 516 // (Or just use the BView mouse hooks) 517 BPoint newWhere; 518 if (window->Lock()) { 519 GetMouse(&newWhere, &buttons); 520 window->Unlock(); 521 } 522 523 // This code is needed to make menus 524 // that are children of BMenuFields "sticky" (see ticket #953) 525 if (localAction == MENU_STATE_CLOSED) { 526 if (fExtraRect != NULL && fExtraRect->Contains(where) 527 // 9 = 3 pixels ^ 2 (since point_distance() returns the square of the distance) 528 && point_distance(newWhere, where) < 9) { 529 _SetStickyMode(true); 530 fExtraRect = NULL; 531 } else 532 fState = MENU_STATE_CLOSED; 533 } 534 if (!window->Lock()) 535 break; 536 } else if (menuItem == NULL && fSelected != NULL 537 && !_IsStickyMode() && Bounds().Contains(where)) { 538 _SelectItem(NULL); 539 fState = MENU_STATE_TRACKING; 540 } 541 542 window->Unlock(); 543 544 if (fState != MENU_STATE_CLOSED) { 545 if (snoozeAmount > 0) 546 snooze(snoozeAmount); 547 548 if (window->Lock()) { 549 GetMouse(&where, &buttons, true); 550 window->Unlock(); 551 } 552 553 if ((buttons != 0 && _IsStickyMode() && menuItem == NULL)) 554 fState = MENU_STATE_CLOSED; 555 else if (buttons == 0 && !_IsStickyMode()) { 556 if ((fSelected != NULL && fSelected->Submenu() == NULL) 557 || menuItem == NULL) { 558 fChosenItem = fSelected; 559 fState = MENU_STATE_CLOSED; 560 } else 561 _SetStickyMode(true); 562 } 563 } 564 } 565 566 if (window->Lock()) { 567 if (fSelected != NULL) 568 _SelectItem(NULL); 569 570 if (fChosenItem != NULL) 571 fChosenItem->Invoke(); 572 _RestoreFocus(); 573 window->Unlock(); 574 } 575 576 if (_IsStickyMode()) 577 _SetStickyMode(false); 578 579 _DeleteMenuWindow(); 580 581 if (action != NULL) 582 *action = fState; 583 584 return fChosenItem; 585 } 586 587 588 void 589 BMenuBar::_StealFocus() 590 { 591 // We already stole the focus, don't do anything 592 if (fPrevFocusToken != -1) 593 return; 594 595 BWindow *window = Window(); 596 if (window != NULL && window->Lock()) { 597 BView *focus = window->CurrentFocus(); 598 if (focus != NULL && focus != this) 599 fPrevFocusToken = _get_object_token_(focus); 600 MakeFocus(); 601 window->Unlock(); 602 } 603 } 604 605 606 void 607 BMenuBar::_RestoreFocus() 608 { 609 BWindow *window = Window(); 610 if (window != NULL && window->Lock()) { 611 BHandler *handler = NULL; 612 if (fPrevFocusToken != -1 613 && gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN, (void **)&handler) == B_OK) { 614 BView *view = dynamic_cast<BView *>(handler); 615 if (view != NULL && view->Window() == window) 616 view->MakeFocus(); 617 618 } else if (IsFocus()) 619 MakeFocus(false); 620 621 fPrevFocusToken = -1; 622 window->Unlock(); 623 } 624 } 625 626 627 void 628 BMenuBar::_InitData(menu_layout layout) 629 { 630 fLastBounds = new BRect(Bounds()); 631 SetItemMargins(8, 2, 8, 2); 632 _SetIgnoreHidden(true); 633 } 634