1 //------------------------------------------------------------------------------ 2 // Copyright (c) 2001-2005, Haiku, Inc. 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a 5 // copy of this software and associated documentation files (the "Software"), 6 // to deal in the Software without restriction, including without limitation 7 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 // and/or sell copies of the Software, and to permit persons to whom the 9 // Software is furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 // DEALINGS IN THE SOFTWARE. 21 // 22 // File Name: Menubar.cpp 23 // Authors: Marc Flerackers (mflerackers@androme.be) 24 // Stefano Ceccherini (burton666@libero.it) 25 // Description: BMenuBar is a menu that's at the root of a menu hierarchy. 26 //------------------------------------------------------------------------------ 27 // TODO: Finish this class 28 29 #include <Application.h> 30 #include <Autolock.h> 31 #include <MenuBar.h> 32 #include <MenuItem.h> 33 #include <Window.h> 34 35 #include <AppMisc.h> 36 #include <MenuPrivate.h> 37 #include <TokenSpace.h> 38 39 struct menubar_data 40 { 41 BMenuBar *menuBar; 42 int32 menuIndex; 43 44 bool sticky; 45 bool showMenu; 46 47 bool useRect; 48 BRect rect; 49 }; 50 51 52 BMenuBar::BMenuBar(BRect frame, const char *title, uint32 resizeMask, 53 menu_layout layout, bool resizeToFit) 54 : BMenu(frame, title, resizeMask, 55 B_WILL_DRAW | B_FRAME_EVENTS, layout, resizeToFit), 56 fBorder(B_BORDER_FRAME), 57 fTrackingPID(-1), 58 fPrevFocusToken(-1), 59 fMenuSem(-1), 60 fLastBounds(NULL), 61 fTracking(false) 62 { 63 InitData(layout); 64 } 65 66 67 BMenuBar::BMenuBar(BMessage *data) 68 : BMenu(data), 69 fBorder(B_BORDER_FRAME), 70 fTrackingPID(-1), 71 fPrevFocusToken(-1), 72 fMenuSem(-1), 73 fLastBounds(NULL), 74 fTracking(false) 75 { 76 int32 border; 77 78 if (data->FindInt32("_border", &border) == B_OK) 79 SetBorder((menu_bar_border)border); 80 81 // TODO: InitData() ?? 82 } 83 84 85 BMenuBar::~BMenuBar() 86 { 87 if (fTrackingPID >= 0) { 88 status_t dummy; 89 wait_for_thread(fTrackingPID, &dummy); 90 } 91 92 delete fLastBounds; 93 } 94 95 96 BArchivable * 97 BMenuBar::Instantiate(BMessage *data) 98 { 99 if (validate_instantiation(data, "BMenuBar")) 100 return new BMenuBar(data); 101 else 102 return NULL; 103 } 104 105 106 status_t 107 BMenuBar::Archive(BMessage *data, bool deep) const 108 { 109 status_t err = BMenu::Archive(data, deep); 110 111 if (err < B_OK) 112 return err; 113 114 if (Border() != B_BORDER_FRAME) 115 err = data->AddInt32("_border", Border()); 116 117 return err; 118 } 119 120 121 void 122 BMenuBar::SetBorder(menu_bar_border border) 123 { 124 fBorder = border; 125 } 126 127 128 menu_bar_border 129 BMenuBar::Border() const 130 { 131 return fBorder; 132 } 133 134 135 void 136 BMenuBar::Draw(BRect updateRect) 137 { 138 // TODO: implement additional border styles 139 if (IsEnabled()) { 140 rgb_color color = HighColor(); 141 142 BRect bounds(Bounds()); 143 // Restore the background of the previously selected menuitem 144 DrawBackground(bounds & updateRect); 145 146 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_LIGHTEN_2_TINT)); 147 StrokeLine(BPoint(0.0f, bounds.bottom - 2.0f), BPoint(0.0f, 0.0f)); 148 StrokeLine(BPoint(bounds.right, 0.0f)); 149 150 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT)); 151 StrokeLine(BPoint(1.0f, bounds.bottom - 1.0f), 152 BPoint(bounds.right, bounds.bottom - 1.0f)); 153 154 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT)); 155 StrokeLine(BPoint(0.0f, bounds.bottom), BPoint(bounds.right, bounds.bottom)); 156 StrokeLine(BPoint(bounds.right, 0.0f), BPoint(bounds.right, bounds.bottom)); 157 158 SetHighColor(color); 159 // revert to previous used color (cheap PushState()/PopState()) 160 161 DrawItems(updateRect); 162 } else { 163 LayoutItems(0); 164 Sync(); 165 Invalidate(); 166 } 167 } 168 169 170 void 171 BMenuBar::AttachedToWindow() 172 { 173 Install(Window()); 174 Window()->SetKeyMenuBar(this); 175 176 BMenu::AttachedToWindow(); 177 } 178 179 180 void 181 BMenuBar::DetachedFromWindow() 182 { 183 BMenu::DetachedFromWindow(); 184 } 185 186 187 void 188 BMenuBar::MessageReceived(BMessage *msg) 189 { 190 BMenu::MessageReceived(msg); 191 } 192 193 194 void 195 BMenuBar::MouseDown(BPoint where) 196 { 197 BWindow *window = Window(); 198 if (!window->IsActive() || !window->IsFront()) { 199 window->Activate(); 200 window->UpdateIfNeeded(); 201 } 202 203 StartMenuBar(-1, false, false); 204 } 205 206 207 void 208 BMenuBar::WindowActivated(bool state) 209 { 210 BView::WindowActivated(state); 211 } 212 213 214 void 215 BMenuBar::MouseUp(BPoint where) 216 { 217 BView::MouseUp(where); 218 } 219 220 221 void 222 BMenuBar::FrameMoved(BPoint newPosition) 223 { 224 BMenu::FrameMoved(newPosition); 225 } 226 227 228 void 229 BMenuBar::FrameResized(float newWidth, float newHeight) 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, 251 BMessage *specifier, int32 form, 252 const char *property) 253 { 254 return BMenu::ResolveSpecifier(msg, index, specifier, form, property); 255 } 256 257 258 status_t 259 BMenuBar::GetSupportedSuites(BMessage *data) 260 { 261 return BMenu::GetSupportedSuites(data); 262 } 263 264 265 void 266 BMenuBar::ResizeToPreferred() 267 { 268 BMenu::ResizeToPreferred(); 269 } 270 271 272 void 273 BMenuBar::GetPreferredSize(float *width, float *height) 274 { 275 BMenu::GetPreferredSize(width, height); 276 } 277 278 279 void 280 BMenuBar::MakeFocus(bool state) 281 { 282 BMenu::MakeFocus(state); 283 } 284 285 286 void 287 BMenuBar::AllAttached() 288 { 289 BMenu::AllAttached(); 290 } 291 292 293 void 294 BMenuBar::AllDetached() 295 { 296 BMenu::AllDetached(); 297 } 298 299 300 status_t 301 BMenuBar::Perform(perform_code d, void *arg) 302 { 303 return BMenu::Perform(d, arg); 304 } 305 306 307 void BMenuBar::_ReservedMenuBar1() {} 308 void BMenuBar::_ReservedMenuBar2() {} 309 void BMenuBar::_ReservedMenuBar3() {} 310 void BMenuBar::_ReservedMenuBar4() {} 311 312 313 BMenuBar & 314 BMenuBar::operator=(const BMenuBar &) 315 { 316 return *this; 317 } 318 319 320 void 321 BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu, 322 BRect *specialRect) 323 { 324 BWindow *window = Window(); 325 if (window == NULL) 326 debugger("MenuBar must be added to a window before it can be used."); 327 328 BAutolock lock(window); 329 if (!lock.IsLocked()) 330 return; 331 332 fPrevFocusToken = -1; 333 fTracking = true; 334 335 window->MenusBeginning(); 336 337 fMenuSem = create_sem(0, "window close sem"); 338 _set_menu_sem_(window, fMenuSem); 339 340 fTrackingPID = spawn_thread(TrackTask, "menu_tracking", B_NORMAL_PRIORITY, NULL); 341 if (fTrackingPID >= 0) { 342 menubar_data data; 343 data.menuBar = this; 344 data.menuIndex = menuIndex; 345 data.sticky = sticky; 346 data.showMenu = showMenu; 347 data.useRect = specialRect != NULL; 348 if (data.useRect) 349 data.rect = *specialRect; 350 351 resume_thread(fTrackingPID); 352 send_data(fTrackingPID, 0, &data, sizeof(data)); 353 354 } else { 355 _set_menu_sem_(window, B_NO_MORE_SEMS); 356 delete_sem(fMenuSem); 357 } 358 } 359 360 361 long 362 BMenuBar::TrackTask(void *arg) 363 { 364 menubar_data data; 365 thread_id id; 366 367 receive_data(&id, &data, sizeof(data)); 368 369 BMenuBar *menuBar = data.menuBar; 370 menuBar->SetStickyMode(data.sticky); 371 372 int32 action; 373 menuBar->Track(&action, data.menuIndex, data.showMenu); 374 375 menuBar->fTracking = false; 376 377 // Sends a _MENUS_DONE_ message to the BWindow. 378 // Weird: There is a _MENUS_DONE_ message but not a 379 // _MENUS_BEGINNING_ message, in fact the MenusBeginning() 380 // hook function is called directly. 381 BWindow *window = menuBar->Window(); 382 window->PostMessage(_MENUS_DONE_); 383 384 _set_menu_sem_(window, B_BAD_SEM_ID); 385 delete_sem(menuBar->fMenuSem); 386 menuBar->fMenuSem = B_BAD_SEM_ID; 387 388 return 0; 389 } 390 391 392 BMenuItem * 393 BMenuBar::Track(int32 *action, int32 startIndex, bool showMenu) 394 { 395 // TODO: This function is very incomplete and just partially working: 396 // For example, it doesn't respect the "sticky mode" setting. 397 BMenuItem *resultItem = NULL; 398 BWindow *window = Window(); 399 int localAction = MENU_ACT_NONE; 400 401 while (true) { 402 bigtime_t snoozeAmount = 30000; 403 if (window->LockWithTimeout(200000) < B_OK) 404 break; 405 406 BPoint where; 407 ulong buttons; 408 GetMouse(&where, &buttons); 409 410 BMenuItem *menuItem = HitTestItems(where, B_ORIGIN); 411 if (menuItem != NULL && menuItem != fSelected) { 412 // only select the item 413 SelectItem(menuItem, -1); 414 if (menuItem->Submenu() != NULL 415 && menuItem->Submenu()->Window() == NULL) { 416 // open the menu if it's not opened yet 417 SelectItem(menuItem); 418 } 419 } 420 421 if (fSelected != NULL) { 422 BMenu *menu = fSelected->Submenu(); 423 if (menu != NULL) { 424 window->Unlock(); 425 if (IsStickyPrefOn()) 426 menu->SetStickyMode(true); 427 snoozeAmount = 0; 428 resultItem = menu->_track(&localAction); 429 if (window->LockWithTimeout(200000) < B_OK) 430 break; 431 } 432 } 433 434 window->Unlock(); 435 436 if (localAction == MENU_ACT_CLOSE) 437 break; 438 439 if (snoozeAmount > 0) 440 snooze(snoozeAmount); 441 } 442 443 if (window->Lock()) { 444 if (fSelected != NULL) 445 SelectItem(NULL); 446 if (resultItem != NULL) 447 resultItem->Invoke(); 448 window->Unlock(); 449 } 450 451 if (action != NULL) 452 *action = static_cast<int32>(localAction); 453 454 return resultItem; 455 } 456 457 458 void 459 BMenuBar::StealFocus() 460 { 461 BWindow *window = Window(); 462 if (window != NULL && window->Lock()) { 463 BView *focus = window->CurrentFocus(); 464 if (focus != NULL) 465 fPrevFocusToken = _get_object_token_(focus); 466 MakeFocus(); 467 window->Unlock(); 468 } 469 } 470 471 472 void 473 BMenuBar::RestoreFocus() 474 { 475 BWindow *window = Window(); 476 if (window != NULL && window->Lock()) { 477 BHandler *handler = NULL; 478 if (BPrivate::gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN, 479 (void **)&handler, NULL) == B_OK) { 480 BView *view = dynamic_cast<BView *>(handler); 481 if (view != NULL && view->Window() == window) 482 view->MakeFocus(); 483 } 484 fPrevFocusToken = -1; 485 window->Unlock(); 486 } 487 } 488 489 490 void 491 BMenuBar::InitData(menu_layout layout) 492 { 493 fLastBounds = new BRect(Bounds()); 494 SetItemMargins(8, 2, 8, 2); 495 SetIgnoreHidden(true); 496 } 497