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