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