1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered 30 trademarks of Be Incorporated in the United States and other countries. Other 31 brand product names are registered trademarks or trademarks of their respective 32 holders. 33 All rights reserved. 34 */ 35 36 37 #include "ExpandoMenuBar.h" 38 39 #include <strings.h> 40 41 #include <map> 42 43 #include <Autolock.h> 44 #include <Bitmap.h> 45 #include <Collator.h> 46 #include <ControlLook.h> 47 #include <Debug.h> 48 #include <MenuPrivate.h> 49 #include <MessengerPrivate.h> 50 #include <NodeInfo.h> 51 #include <Roster.h> 52 #include <Screen.h> 53 #include <Thread.h> 54 #include <Window.h> 55 56 #include "icons.h" 57 58 #include "BarApp.h" 59 #include "BarMenuBar.h" 60 #include "BarView.h" 61 #include "BarWindow.h" 62 #include "DeskbarMenu.h" 63 #include "DeskbarUtils.h" 64 #include "InlineScrollView.h" 65 #include "ResourceSet.h" 66 #include "ShowHideMenuItem.h" 67 #include "StatusView.h" 68 #include "TeamMenu.h" 69 #include "TeamMenuItem.h" 70 #include "WindowMenu.h" 71 #include "WindowMenuItem.h" 72 73 74 bool TExpandoMenuBar::sDoMonitor = false; 75 thread_id TExpandoMenuBar::sMonThread = B_ERROR; 76 BLocker TExpandoMenuBar::sMonLocker("expando monitor"); 77 78 typedef std::map<BString, TTeamMenuItem*> TeamMenuItemMap; 79 80 81 // #pragma mark - TExpandoMenuBar 82 83 84 TExpandoMenuBar::TExpandoMenuBar(menu_layout layout, TBarView* barView) 85 : 86 BMenuBar(BRect(0, 0, 0, 0), "ExpandoMenuBar", B_FOLLOW_NONE, layout), 87 fBarView(barView), 88 fOverflow(false), 89 fUnderflow(false), 90 fFirstBuild(true), 91 fPreviousDragTargetItem(NULL), 92 fLastMousedOverItem(NULL), 93 fLastClickedItem(NULL), 94 fLastClickTime(0) 95 { 96 SetItemMargins(0.0f, 0.0f, 0.0f, 0.0f); 97 SetFont(be_plain_font); 98 } 99 100 101 void 102 TExpandoMenuBar::AllAttached() 103 { 104 BMenuBar::AllAttached(); 105 106 SizeWindow(0); 107 } 108 109 110 void 111 TExpandoMenuBar::AttachedToWindow() 112 { 113 BMenuBar::AttachedToWindow(); 114 115 fTeamList.MakeEmpty(); 116 117 if (Vertical()) 118 StartMonitoringWindows(); 119 } 120 121 122 void 123 TExpandoMenuBar::DetachedFromWindow() 124 { 125 BMenuBar::DetachedFromWindow(); 126 127 StopMonitoringWindows(); 128 129 BMessenger self(this); 130 BMessage message(kUnsubscribe); 131 message.AddMessenger("messenger", self); 132 be_app->PostMessage(&message); 133 134 RemoveItems(0, CountItems(), true); 135 } 136 137 138 void 139 TExpandoMenuBar::MessageReceived(BMessage* message) 140 { 141 int32 index; 142 TTeamMenuItem* item; 143 144 switch (message->what) { 145 case B_SOME_APP_LAUNCHED: 146 { 147 BList* teams = NULL; 148 message->FindPointer("teams", (void**)&teams); 149 150 BBitmap* icon = NULL; 151 message->FindPointer("icon", (void**)&icon); 152 153 const char* signature = NULL; 154 message->FindString("sig", &signature); 155 156 uint32 flags = 0; 157 message->FindInt32("flags", ((int32*) &flags)); 158 159 const char* name = NULL; 160 message->FindString("name", &name); 161 162 AddTeam(teams, icon, strdup(name), strdup(signature)); 163 break; 164 } 165 166 case B_MOUSE_WHEEL_CHANGED: 167 { 168 float deltaY = 0; 169 message->FindFloat("be:wheel_delta_y", &deltaY); 170 if (deltaY == 0) 171 return; 172 173 TInlineScrollView* scrollView 174 = dynamic_cast<TInlineScrollView*>(Parent()); 175 if (scrollView == NULL || !scrollView->HasScrollers()) 176 return; 177 178 float largeStep; 179 float smallStep; 180 scrollView->GetSteps(&smallStep, &largeStep); 181 182 // pressing the option/command/control key scrolls faster 183 if (modifiers() & (B_OPTION_KEY | B_COMMAND_KEY | B_CONTROL_KEY)) 184 deltaY *= largeStep; 185 else 186 deltaY *= smallStep; 187 188 scrollView->ScrollBy(deltaY); 189 break; 190 } 191 192 case kAddTeam: 193 AddTeam(message->FindInt32("team"), message->FindString("sig")); 194 break; 195 196 case kRemoveTeam: 197 { 198 team_id team = -1; 199 message->FindInt32("team", &team); 200 201 RemoveTeam(team, true); 202 break; 203 } 204 205 case B_SOME_APP_QUIT: 206 { 207 team_id team = -1; 208 message->FindInt32("team", &team); 209 210 RemoveTeam(team, false); 211 break; 212 } 213 214 case kMinimizeTeam: 215 { 216 index = message->FindInt32("itemIndex"); 217 item = dynamic_cast<TTeamMenuItem*>(ItemAt(index)); 218 if (item == NULL) 219 break; 220 221 TShowHideMenuItem::TeamShowHideCommon(B_MINIMIZE_WINDOW, 222 item->Teams(), item->Menu()->ConvertToScreen(item->Frame()), 223 true); 224 break; 225 } 226 227 case kBringTeamToFront: 228 { 229 index = message->FindInt32("itemIndex"); 230 item = dynamic_cast<TTeamMenuItem*>(ItemAt(index)); 231 if (item == NULL) 232 break; 233 234 TShowHideMenuItem::TeamShowHideCommon(B_BRING_TO_FRONT, 235 item->Teams(), item->Menu()->ConvertToScreen(item->Frame()), 236 true); 237 break; 238 } 239 240 default: 241 BMenuBar::MessageReceived(message); 242 break; 243 } 244 } 245 246 247 void 248 TExpandoMenuBar::MouseDown(BPoint where) 249 { 250 if (fBarView == NULL || fBarView->Dragging()) 251 return BMenuBar::MouseDown(where); 252 253 BMessage* message = Window()->CurrentMessage(); 254 BMenuItem* item = ItemAtPoint(where); 255 fLastClickedItem = item; 256 if (message == NULL || item == NULL) 257 return BMenuBar::MouseDown(where); 258 259 int32 modifiers = 0; 260 int32 buttons = 0; 261 message->FindInt32("modifiers", &modifiers); 262 message->FindInt32("buttons", &buttons); 263 264 // close window item 265 if ((modifiers & B_SHIFT_KEY) != 0 266 && (buttons & B_TERTIARY_MOUSE_BUTTON) != 0) { 267 TWindowMenuItem* wItem = dynamic_cast<TWindowMenuItem*>(item); 268 if (wItem != NULL) { 269 // we have a window item 270 BMessenger messenger; 271 client_window_info* info = get_window_info(wItem->ID()); 272 if (info != NULL) { 273 BMessenger::Private(messenger).SetTo(info->team, 274 info->client_port, info->client_token); 275 messenger.SendMessage(B_QUIT_REQUESTED); 276 free(info); 277 return; 278 // absorb the message 279 } 280 } 281 } 282 283 // below depends on item being a team item 284 285 TTeamMenuItem* teamItem = dynamic_cast<TTeamMenuItem*>(item); 286 if (teamItem == NULL) 287 return BMenuBar::MouseDown(where); 288 289 // check for three finger salute, a.k.a. Vulcan Death Grip 290 if (teamItem->HandleMouseDown(where)) 291 return; 292 // absorb the message 293 294 // check if within expander bounds to expand window items 295 if (Vertical() && static_cast<TBarApp*>(be_app)->Settings()->superExpando 296 && teamItem->ExpanderBounds().Contains(where)) { 297 // start the animation here, finish on mouse up 298 teamItem->SetArrowDirection(BControlLook::B_RIGHT_DOWN_ARROW); 299 SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 300 Invalidate(teamItem->ExpanderBounds()); 301 return; 302 // absorb the message 303 } 304 305 // double-click on an item brings the team to front 306 int32 clicks; 307 bigtime_t clickSpeed = 0; 308 get_click_speed(&clickSpeed); 309 bigtime_t delta = system_time() - fLastClickTime; 310 if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1 311 && item == fLastClickedItem && delta <= clickSpeed) { 312 be_roster->ActivateApp((addr_t)teamItem->Teams()->ItemAt(0)); 313 // activate this team 314 return; 315 // absorb the message 316 } 317 318 // Update fLastClickTime only if we are not already triggering the 319 // double-click action. Otherwise the delay is renewed at every subsequent 320 // click and they keep triggering the double click action 321 fLastClickTime = system_time(); 322 323 BMenuBar::MouseDown(where); 324 } 325 326 327 void 328 TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage* message) 329 { 330 int32 buttons; 331 BMessage* currentMessage = Window()->CurrentMessage(); 332 if (currentMessage == NULL 333 || currentMessage->FindInt32("buttons", &buttons) != B_OK) { 334 buttons = 0; 335 } 336 337 if (message == NULL) { 338 // force a cleanup 339 _FinishedDrag(); 340 341 if (Vertical() && buttons != 0 342 && static_cast<TBarApp*>(be_app)->Settings()->superExpando) { 343 TTeamMenuItem* lastItem 344 = dynamic_cast<TTeamMenuItem*>(fLastClickedItem); 345 if (lastItem != NULL) { 346 if (lastItem->ExpanderBounds().Contains(where)) 347 lastItem->SetArrowDirection( 348 BControlLook::B_RIGHT_DOWN_ARROW); 349 else { 350 lastItem->SetArrowDirection(lastItem->IsExpanded() 351 ? BControlLook::B_DOWN_ARROW 352 : BControlLook::B_RIGHT_ARROW); 353 } 354 355 Invalidate(lastItem->ExpanderBounds()); 356 } 357 } 358 359 switch (code) { 360 case B_INSIDE_VIEW: 361 { 362 BMenuItem* menuItem; 363 TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem); 364 365 if (item == NULL || menuItem == NULL) { 366 // item is NULL, remove the tooltip and break out 367 fLastMousedOverItem = NULL; 368 SetToolTip((const char*)NULL); 369 break; 370 } 371 372 if (menuItem == fLastMousedOverItem) { 373 // already set the tooltip for this item, break out 374 break; 375 } 376 377 TWindowMenuItem* windowMenuItem 378 = dynamic_cast<TWindowMenuItem*>(menuItem); 379 if (windowMenuItem != NULL && fBarView != NULL && Vertical() 380 && fBarView->ExpandoState() && item->IsExpanded()) { 381 // expando mode window menu item 382 fLastMousedOverItem = menuItem; 383 if (strcasecmp(windowMenuItem->TruncatedLabel(), 384 windowMenuItem->Label()) > 0) { 385 // label is truncated, set tooltip 386 SetToolTip(windowMenuItem->Label()); 387 } else 388 SetToolTip((const char*)NULL); 389 390 break; 391 } 392 393 if (!dynamic_cast<TBarApp*>(be_app)->Settings()->hideLabels) { 394 // item has a visible label, set tool tip if truncated 395 fLastMousedOverItem = menuItem; 396 if (strcasecmp(item->TruncatedLabel(), item->Label()) > 0) { 397 // label is truncated, set tooltip 398 SetToolTip(item->Label()); 399 } else 400 SetToolTip((const char*)NULL); 401 402 break; 403 } 404 405 SetToolTip(item->Label()); 406 // new item, set the tooltip to the item label 407 fLastMousedOverItem = menuItem; 408 // save the current menuitem for the next MouseMoved() call 409 break; 410 } 411 } 412 413 BMenuBar::MouseMoved(where, code, message); 414 return; 415 } 416 417 if (buttons == 0) 418 return; 419 420 switch (code) { 421 case B_ENTERED_VIEW: 422 // fPreviousDragTargetItem should always be NULL here anyways. 423 if (fPreviousDragTargetItem != NULL) 424 _FinishedDrag(); 425 426 fBarView->CacheDragData(message); 427 fPreviousDragTargetItem = NULL; 428 break; 429 430 case B_OUTSIDE_VIEW: 431 // NOTE: Should not be here, but for the sake of defensive 432 // programming... fall-through 433 case B_EXITED_VIEW: 434 _FinishedDrag(); 435 break; 436 437 case B_INSIDE_VIEW: 438 if (fBarView != NULL && fBarView->Dragging()) { 439 TTeamMenuItem* item = NULL; 440 int32 itemCount = CountItems(); 441 for (int32 i = 0; i < itemCount; i++) { 442 BMenuItem* _item = ItemAt(i); 443 if (_item->Frame().Contains(where)) { 444 item = dynamic_cast<TTeamMenuItem*>(_item); 445 break; 446 } 447 } 448 if (item == fPreviousDragTargetItem) 449 break; 450 if (fPreviousDragTargetItem != NULL) 451 fPreviousDragTargetItem->SetOverrideSelected(false); 452 if (item != NULL) 453 item->SetOverrideSelected(true); 454 fPreviousDragTargetItem = item; 455 } 456 break; 457 } 458 } 459 460 461 void 462 TExpandoMenuBar::MouseUp(BPoint where) 463 { 464 if (fBarView != NULL && fBarView->Dragging()) { 465 _FinishedDrag(true); 466 return; 467 // absorb the message 468 } 469 470 if (fLastClickedItem == NULL) 471 return BMenuBar::MouseUp(where); 472 473 TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem); 474 fLastClickedItem = NULL; 475 476 if (Vertical() && static_cast<TBarApp*>(be_app)->Settings()->superExpando 477 && lastItem != NULL && lastItem->ExpanderBounds().Contains(where)) { 478 lastItem->ToggleExpandState(true); 479 lastItem->SetArrowDirection(lastItem->IsExpanded() 480 ? BControlLook::B_DOWN_ARROW 481 : BControlLook::B_RIGHT_ARROW); 482 483 Invalidate(lastItem->ExpanderBounds()); 484 } 485 486 BMenuBar::MouseUp(where); 487 } 488 489 490 void 491 TExpandoMenuBar::BuildItems() 492 { 493 BMessenger self(this); 494 TBarApp::Subscribe(self, &fTeamList); 495 496 desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings(); 497 498 float itemWidth = -1.0f; 499 if (Vertical() && (fBarView->ExpandoState() || fBarView->FullState())) { 500 itemWidth = settings->width; 501 } 502 SetMaxContentWidth(itemWidth); 503 504 TeamMenuItemMap items; 505 int32 itemCount = CountItems(); 506 BList itemList(itemCount); 507 for (int32 i = 0; i < itemCount; i++) { 508 BMenuItem* menuItem = RemoveItem((int32)0); 509 itemList.AddItem(menuItem); 510 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(menuItem); 511 if (item != NULL) 512 items[BString(item->Signature()).ToLower()] = item; 513 } 514 515 if (settings->sortRunningApps) 516 fTeamList.SortItems(TTeamMenu::CompareByName); 517 518 int32 teamCount = fTeamList.CountItems(); 519 for (int32 i = 0; i < teamCount; i++) { 520 BarTeamInfo* barInfo = (BarTeamInfo*)fTeamList.ItemAt(i); 521 TeamMenuItemMap::const_iterator iter 522 = items.find(BString(barInfo->sig).ToLower()); 523 if (iter == items.end()) { 524 // new team 525 TTeamMenuItem* item = new TTeamMenuItem(barInfo->teams, 526 barInfo->icon, barInfo->name, barInfo->sig, itemWidth); 527 528 if (settings->trackerAlwaysFirst 529 && strcasecmp(barInfo->sig, kTrackerSignature) == 0) { 530 AddItem(item, 0); 531 } else 532 AddItem(item); 533 534 if (fFirstBuild && Vertical() && settings->expandNewTeams) 535 item->ToggleExpandState(true); 536 } else { 537 // existing team, update info and add it 538 TTeamMenuItem* item = iter->second; 539 item->SetIcon(barInfo->icon); 540 item->SetOverrideWidth(itemWidth); 541 542 if (settings->trackerAlwaysFirst 543 && strcasecmp(barInfo->sig, kTrackerSignature) == 0) { 544 AddItem(item, 0); 545 } else 546 AddItem(item); 547 548 // add window items back 549 int32 index = itemList.IndexOf(item); 550 TWindowMenuItem* windowItem; 551 TWindowMenu* submenu = dynamic_cast<TWindowMenu*>(item->Submenu()); 552 bool hasWindowItems = false; 553 while ((windowItem = dynamic_cast<TWindowMenuItem*>( 554 (BMenuItem*)(itemList.ItemAt(++index)))) != NULL) { 555 if (Vertical()) 556 AddItem(windowItem); 557 else { 558 delete windowItem; 559 hasWindowItems = submenu != NULL; 560 } 561 } 562 563 // unexpand if turn off show team expander 564 if (Vertical() && !settings->superExpando && item->IsExpanded()) 565 item->ToggleExpandState(false); 566 567 if (hasWindowItems) { 568 // add (new) window items in submenu 569 submenu->SetExpanded(false, 0); 570 submenu->AttachedToWindow(); 571 } 572 } 573 } 574 575 if (CountItems() == 0) { 576 // If we're empty, BMenuBar::AttachedToWindow() resizes us to some 577 // weird value - we just override it again 578 ResizeTo(gMinimumWindowWidth, 0); 579 } else { 580 // first build isn't complete until we've gotten here with an item 581 fFirstBuild = false; 582 } 583 } 584 585 586 bool 587 TExpandoMenuBar::InDeskbarMenu(BPoint loc) const 588 { 589 TBarWindow* window = dynamic_cast<TBarWindow*>(Window()); 590 if (window != NULL) { 591 if (TDeskbarMenu* bemenu = window->DeskbarMenu()) { 592 bool inDeskbarMenu = false; 593 if (bemenu->LockLooper()) { 594 inDeskbarMenu = bemenu->Frame().Contains(loc); 595 bemenu->UnlockLooper(); 596 } 597 return inDeskbarMenu; 598 } 599 } 600 601 return false; 602 } 603 604 605 BMenuItem* 606 TExpandoMenuBar::ItemAtPoint(BPoint point) 607 { 608 int32 itemCount = CountItems(); 609 for (int32 index = 0; index < itemCount; index++) { 610 BMenuItem* item = ItemAt(index); 611 if (item != NULL && item->Frame().Contains(point)) 612 return item; 613 } 614 615 // no item found 616 return NULL; 617 } 618 619 620 /*! Returns the team menu item that belongs to the item under the 621 specified \a point. 622 If \a _item is given, it will return the exact menu item under 623 that point (which might be a window item when the expander is on). 624 */ 625 TTeamMenuItem* 626 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item) 627 { 628 TTeamMenuItem* lastApp = NULL; 629 int32 itemCount = CountItems(); 630 631 for (int32 index = 0; index < itemCount; index++) { 632 BMenuItem* item = ItemAt(index); 633 if (item != NULL && item->Frame().Contains(point)) { 634 if (dynamic_cast<TTeamMenuItem*>(item) != NULL) 635 lastApp = (TTeamMenuItem*)item; 636 637 if (_item != NULL) 638 *_item = item; 639 640 return lastApp; 641 } 642 } 643 644 // no item found 645 646 if (_item != NULL) 647 *_item = NULL; 648 649 return NULL; 650 } 651 652 653 void 654 TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name, 655 char* signature) 656 { 657 TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature); 658 659 desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings(); 660 if (settings != NULL && settings->trackerAlwaysFirst 661 && strcasecmp(signature, kTrackerSignature) == 0) { 662 AddItem(item, 0); 663 } else if (settings->sortRunningApps) { 664 TTeamMenuItem* teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(0)); 665 int32 firstApp = 0; 666 667 // if Tracker should always be the first item, we need to skip it 668 // when sorting in the current item 669 if (settings->trackerAlwaysFirst && teamItem != NULL 670 && strcasecmp(teamItem->Signature(), kTrackerSignature) == 0) { 671 firstApp++; 672 } 673 674 BCollator collator; 675 BLocale::Default()->GetCollator(&collator); 676 677 int32 i = firstApp; 678 int32 itemCount = CountItems(); 679 while (i < itemCount) { 680 teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 681 if (teamItem != NULL && collator.Compare(teamItem->Label(), name) 682 > 0) { 683 AddItem(item, i); 684 break; 685 } 686 i++; 687 } 688 // was the item added to the list yet? 689 if (i == itemCount) 690 AddItem(item); 691 } else 692 AddItem(item); 693 694 if (Vertical() && settings != NULL && settings->superExpando 695 && settings->expandNewTeams) { 696 item->ToggleExpandState(false); 697 } 698 699 SizeWindow(1); 700 Window()->UpdateIfNeeded(); 701 } 702 703 704 void 705 TExpandoMenuBar::AddTeam(team_id team, const char* signature) 706 { 707 int32 itemCount = CountItems(); 708 for (int32 i = 0; i < itemCount; i++) { 709 // Only add to team menu items 710 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 711 if (item != NULL && strcasecmp(item->Signature(), signature) == 0 712 && !(item->Teams()->HasItem((void*)(addr_t)team))) { 713 item->Teams()->AddItem((void*)(addr_t)team); 714 break; 715 } 716 } 717 } 718 719 720 void 721 TExpandoMenuBar::RemoveTeam(team_id team, bool partial) 722 { 723 TWindowMenuItem* windowItem = NULL; 724 725 for (int32 i = CountItems() - 1; i >= 0; i--) { 726 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 727 if (item != NULL && item->Teams()->HasItem((void*)(addr_t)team)) { 728 item->Teams()->RemoveItem(team); 729 if (partial) 730 return; 731 732 BAutolock locker(sMonLocker); 733 // make the update thread wait 734 RemoveItem(i); 735 if (item == fPreviousDragTargetItem) 736 fPreviousDragTargetItem = NULL; 737 738 if (item == fLastMousedOverItem) 739 fLastMousedOverItem = NULL; 740 741 if (item == fLastClickedItem) 742 fLastClickedItem = NULL; 743 744 delete item; 745 while ((windowItem = dynamic_cast<TWindowMenuItem*>( 746 ItemAt(i))) != NULL) { 747 // Also remove window items (if there are any) 748 RemoveItem(i); 749 if (windowItem == fLastMousedOverItem) 750 fLastMousedOverItem = NULL; 751 752 if (windowItem == fLastClickedItem) 753 fLastClickedItem = NULL; 754 755 delete windowItem; 756 } 757 SizeWindow(-1); 758 Window()->UpdateIfNeeded(); 759 return; 760 } 761 } 762 } 763 764 765 void 766 TExpandoMenuBar::CheckItemSizes(int32 delta, bool reset) 767 { 768 // horizontal only 769 if (fBarView == NULL || Vertical()) 770 return; 771 772 // minimum two items before size overrun can occur 773 int32 itemCount = CountItems(); 774 if (itemCount < 2) 775 return; 776 777 float minItemWidth = MinHorizontalItemWidth(); 778 float maxItemWidth = MaxHorizontalItemWidth(); 779 float maxMenuWidth = maxItemWidth * itemCount; 780 float maxWidth = MaxHorizontalWidth(); 781 bool tooWide = maxMenuWidth > maxWidth; 782 783 // start at max width 784 float newItemWidth = maxItemWidth; 785 786 if (delta < 0 && fOverflow) { 787 // removing an item, check if menu is still too wide 788 if (tooWide) 789 newItemWidth = floorf(maxWidth / itemCount); 790 else 791 newItemWidth = maxItemWidth; 792 } else if (tooWide) { 793 fOverflow = true; 794 newItemWidth = std::min(floorf(maxWidth / itemCount), maxItemWidth); 795 } 796 797 // see if we should grow items 798 fUnderflow = delta < 0 && newItemWidth < maxItemWidth; 799 800 if (fOverflow || fUnderflow || fFirstBuild || reset) { 801 // clip within limits 802 if (newItemWidth > maxItemWidth) 803 newItemWidth = maxItemWidth; 804 else if (newItemWidth < minItemWidth) 805 newItemWidth = minItemWidth; 806 807 SetMaxContentWidth(newItemWidth); 808 if (newItemWidth == maxItemWidth) 809 fOverflow = false; 810 811 for (int32 index = 0; ; index++) { 812 TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index); 813 if (item == NULL) 814 break; 815 816 item->SetOverrideWidth(newItemWidth); 817 } 818 819 InvalidateLayout(); 820 821 ResizeTo(newItemWidth * itemCount, Frame().Height()); 822 } 823 } 824 825 826 float 827 TExpandoMenuBar::MinHorizontalItemWidth() 828 { 829 const int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize(); 830 const float iconPadding = be_control_look->ComposeSpacing(kIconPadding); 831 float iconOnlyWidth = iconSize + iconPadding; 832 833 return static_cast<TBarApp*>(be_app)->Settings()->hideLabels 834 ? iconOnlyWidth 835 : (iconSize - kMinimumIconSize) + gMinimumWindowWidth 836 + (be_plain_font->Size() - 12) * 4; 837 } 838 839 840 float 841 TExpandoMenuBar::MaxHorizontalItemWidth() 842 { 843 const int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize(); 844 const float iconPadding = be_control_look->ComposeSpacing(kIconPadding); 845 float iconOnlyWidth = iconSize + iconPadding; 846 847 // hide labels 848 if (static_cast<TBarApp*>(be_app)->Settings()->hideLabels) 849 return iconOnlyWidth + iconPadding; // add an extra icon padding 850 851 // set max item width to 1.25x min item width 852 return floorf(MinHorizontalItemWidth() * 1.25); 853 } 854 855 856 menu_layout 857 TExpandoMenuBar::MenuLayout() const 858 { 859 return Layout(); 860 } 861 862 863 void 864 TExpandoMenuBar::SetMenuLayout(menu_layout layout) 865 { 866 BPrivate::MenuPrivate(this).SetLayout(layout); 867 InvalidateLayout(); 868 } 869 870 871 void 872 TExpandoMenuBar::Draw(BRect updateRect) 873 { 874 BMenu::Draw(updateRect); 875 } 876 877 878 void 879 TExpandoMenuBar::DrawBackground(BRect updateRect) 880 { 881 if (Vertical()) 882 return; 883 884 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 1.22)); 885 StrokeLine(Bounds().RightTop(), Bounds().RightBottom()); 886 } 887 888 889 /*! Some methods to help determine if we are showing too many apps 890 and need to add or remove in scroll arrows. 891 */ 892 bool 893 TExpandoMenuBar::CheckForSizeOverrun() 894 { 895 if (Vertical()) 896 return CheckForSizeOverrunVertical(); 897 else 898 return CheckForSizeOverrunHorizontal(); 899 } 900 901 902 bool 903 TExpandoMenuBar::CheckForSizeOverrunVertical() 904 { 905 if (Window() == NULL || !Vertical()) 906 return false; 907 908 return Window()->Frame().bottom > (BScreen(Window())).Frame().bottom; 909 910 } 911 912 913 bool 914 TExpandoMenuBar::CheckForSizeOverrunHorizontal() 915 { 916 if (fBarView == NULL || Vertical()) 917 return false; 918 919 // minimum two items before size overrun can occur 920 int32 itemCount = CountItems(); 921 if (itemCount < 2) 922 return false; 923 924 float minMenuWidth = MinHorizontalItemWidth() * itemCount; 925 float maxWidth = MaxHorizontalWidth(); 926 927 return minMenuWidth > maxWidth; 928 } 929 930 931 float 932 TExpandoMenuBar::MaxHorizontalWidth() 933 { 934 return (BScreen(Window())).Frame().Width() 935 - fBarView->DragRegion()->Frame().Width() - 1 936 - fBarView->BarMenuBar()->Frame().Width(); 937 } 938 939 940 void 941 TExpandoMenuBar::SizeWindow(int32 delta) 942 { 943 // instead of resizing the window here and there in the 944 // code the resize method will be centered in one place 945 // thus, the same behavior (good or bad) will be used 946 // wherever window sizing is done 947 if (fBarView == NULL || Window() == NULL) 948 return; 949 950 BRect screenFrame = (BScreen(Window())).Frame(); 951 fBarView->SizeWindow(screenFrame); 952 fBarView->PositionWindow(screenFrame); 953 954 if (!Vertical()) 955 CheckItemSizes(delta); 956 957 fBarView->CheckForScrolling(); 958 Window()->UpdateIfNeeded(); 959 Invalidate(); 960 } 961 962 963 void 964 TExpandoMenuBar::StartMonitoringWindows() 965 { 966 if (sMonThread != B_ERROR) 967 return; 968 969 sDoMonitor = true; 970 sMonThread = spawn_thread(monitor_team_windows, 971 "Expando Window Watcher", B_LOW_PRIORITY, this); 972 resume_thread(sMonThread); 973 } 974 975 976 void 977 TExpandoMenuBar::StopMonitoringWindows() 978 { 979 if (sMonThread == B_ERROR) 980 return; 981 982 sDoMonitor = false; 983 status_t returnCode; 984 wait_for_thread(sMonThread, &returnCode); 985 986 sMonThread = B_ERROR; 987 } 988 989 990 int32 991 TExpandoMenuBar::monitor_team_windows(void* arg) 992 { 993 TExpandoMenuBar* teamMenu = (TExpandoMenuBar*)arg; 994 995 while (teamMenu->sDoMonitor) { 996 sMonLocker.Lock(); 997 998 if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) { 999 int32 totalItems = teamMenu->CountItems(); 1000 1001 // Set all WindowMenuItems to require an update. 1002 TWindowMenuItem* item = NULL; 1003 for (int32 i = 0; i < totalItems; i++) { 1004 if (!teamMenu->SubmenuAt(i)) { 1005 item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i)); 1006 item->SetRequireUpdate(true); 1007 } 1008 } 1009 1010 // Perform SetTo() on all the items that still exist as well as add 1011 // new items. 1012 bool itemModified = false; 1013 bool resize = false; 1014 TTeamMenuItem* teamItem = NULL; 1015 1016 for (int32 i = 0; i < totalItems; i++) { 1017 if (teamMenu->SubmenuAt(i) == NULL) 1018 continue; 1019 1020 teamItem = static_cast<TTeamMenuItem*>(teamMenu->ItemAt(i)); 1021 if (teamItem->IsExpanded()) { 1022 int32 teamCount = teamItem->Teams()->CountItems(); 1023 for (int32 j = 0; j < teamCount; j++) { 1024 // The following code is almost a copy/paste from 1025 // WindowMenu.cpp 1026 team_id theTeam = (addr_t)teamItem->Teams()->ItemAt(j); 1027 int32 count = 0; 1028 int32* tokens = get_token_list(theTeam, &count); 1029 1030 for (int32 k = 0; k < count; k++) { 1031 client_window_info* wInfo 1032 = get_window_info(tokens[k]); 1033 if (wInfo == NULL) 1034 continue; 1035 1036 BString windowName(wInfo->name); 1037 1038 BString teamPrefix(teamItem->Label()); 1039 teamPrefix.Append(": "); 1040 1041 BString teamSuffix(" - "); 1042 teamSuffix.Append(teamItem->Label()); 1043 1044 if (windowName.StartsWith(teamPrefix)) 1045 windowName.RemoveFirst(teamPrefix); 1046 if (windowName.EndsWith(teamSuffix)) 1047 windowName.RemoveLast(teamSuffix); 1048 1049 if (TWindowMenu::WindowShouldBeListed(wInfo)) { 1050 // Check if we have a matching window item... 1051 item = teamItem->ExpandedWindowItem( 1052 wInfo->server_token); 1053 if (item != NULL) { 1054 item->SetTo(windowName, 1055 wInfo->server_token, wInfo->is_mini, 1056 ((1 << current_workspace()) 1057 & wInfo->workspaces) != 0); 1058 1059 if (strcasecmp(item->Label(), windowName) 1060 > 0) { 1061 item->SetLabel(windowName); 1062 } 1063 if (item->Modified()) 1064 itemModified = true; 1065 } else if (teamItem->IsExpanded()) { 1066 // Add the item 1067 item = new TWindowMenuItem(windowName, 1068 wInfo->server_token, wInfo->is_mini, 1069 ((1 << current_workspace()) 1070 & wInfo->workspaces) != 0, false); 1071 item->SetExpanded(true); 1072 teamMenu->AddItem(item, 1073 TWindowMenuItem::InsertIndexFor( 1074 teamMenu, i + 1, item)); 1075 resize = true; 1076 } 1077 } 1078 free(wInfo); 1079 } 1080 free(tokens); 1081 } 1082 } 1083 } 1084 1085 // Remove any remaining items which require an update. 1086 for (int32 i = 0; i < totalItems; i++) { 1087 if (!teamMenu->SubmenuAt(i)) { 1088 item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i)); 1089 if (item && item->RequiresUpdate()) { 1090 item = static_cast<TWindowMenuItem*> 1091 (teamMenu->RemoveItem(i)); 1092 delete item; 1093 totalItems--; 1094 1095 resize = true; 1096 } 1097 } 1098 } 1099 1100 // If any of the WindowMenuItems changed state, we need to force a 1101 // repaint. 1102 if (itemModified || resize) { 1103 teamMenu->Invalidate(); 1104 if (resize) 1105 teamMenu->SizeWindow(1); 1106 } 1107 1108 teamMenu->Window()->Unlock(); 1109 } 1110 1111 sMonLocker.Unlock(); 1112 1113 // sleep for a bit... 1114 snooze(150000); 1115 } 1116 return B_OK; 1117 } 1118 1119 1120 void 1121 TExpandoMenuBar::_FinishedDrag(bool invoke) 1122 { 1123 if (fPreviousDragTargetItem != NULL) { 1124 if (invoke) 1125 fPreviousDragTargetItem->Invoke(); 1126 1127 fPreviousDragTargetItem->SetOverrideSelected(false); 1128 fPreviousDragTargetItem = NULL; 1129 } 1130 1131 if (!invoke && fBarView != NULL && fBarView->Dragging()) 1132 fBarView->DragStop(true); 1133 } 1134