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