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