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 { 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 if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1 337 && item == menuItem && item == fLastClickedItem) { 338 be_roster->ActivateApp((addr_t)item->Teams()->ItemAt(0)); 339 // activate this team 340 return; 341 // absorb the message 342 } 343 344 BMenuBar::MouseDown(where); 345 } 346 347 348 void 349 TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage* message) 350 { 351 int32 buttons; 352 BMessage* currentMessage = Window()->CurrentMessage(); 353 if (currentMessage == NULL 354 || currentMessage->FindInt32("buttons", &buttons) != B_OK) { 355 buttons = 0; 356 } 357 358 if (message == NULL) { 359 // force a cleanup 360 _FinishedDrag(); 361 362 if (Vertical() && buttons != 0 363 && static_cast<TBarApp*>(be_app)->Settings()->superExpando) { 364 TTeamMenuItem* lastItem 365 = dynamic_cast<TTeamMenuItem*>(fLastClickedItem); 366 if (lastItem != NULL) { 367 if (lastItem->ExpanderBounds().Contains(where)) 368 lastItem->SetArrowDirection( 369 BControlLook::B_RIGHT_DOWN_ARROW); 370 else { 371 lastItem->SetArrowDirection(lastItem->IsExpanded() 372 ? BControlLook::B_DOWN_ARROW 373 : BControlLook::B_RIGHT_ARROW); 374 } 375 376 Invalidate(lastItem->ExpanderBounds()); 377 } 378 } 379 380 switch (code) { 381 case B_INSIDE_VIEW: 382 { 383 BMenuItem* menuItem; 384 TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem); 385 386 if (item == NULL || menuItem == NULL) { 387 // item is NULL, remove the tooltip and break out 388 fLastMousedOverItem = NULL; 389 SetToolTip((const char*)NULL); 390 break; 391 } 392 393 if (menuItem == fLastMousedOverItem) { 394 // already set the tooltip for this item, break out 395 break; 396 } 397 398 TWindowMenuItem* windowMenuItem 399 = dynamic_cast<TWindowMenuItem*>(menuItem); 400 if (windowMenuItem != NULL && fBarView != NULL && Vertical() 401 && fBarView->ExpandoState() && item->IsExpanded()) { 402 // expando mode window menu item 403 fLastMousedOverItem = menuItem; 404 if (strcasecmp(windowMenuItem->TruncatedLabel(), 405 windowMenuItem->Label()) > 0) { 406 // label is truncated, set tooltip 407 SetToolTip(windowMenuItem->Label()); 408 } else 409 SetToolTip((const char*)NULL); 410 411 break; 412 } 413 414 if (!dynamic_cast<TBarApp*>(be_app)->Settings()->hideLabels) { 415 // item has a visible label, set tool tip if truncated 416 fLastMousedOverItem = menuItem; 417 if (strcasecmp(item->TruncatedLabel(), item->Label()) > 0) { 418 // label is truncated, set tooltip 419 SetToolTip(item->Label()); 420 } else 421 SetToolTip((const char*)NULL); 422 423 break; 424 } 425 426 SetToolTip(item->Label()); 427 // new item, set the tooltip to the item label 428 fLastMousedOverItem = menuItem; 429 // save the current menuitem for the next MouseMoved() call 430 break; 431 } 432 } 433 434 BMenuBar::MouseMoved(where, code, message); 435 return; 436 } 437 438 if (buttons == 0) 439 return; 440 441 switch (code) { 442 case B_ENTERED_VIEW: 443 // fPreviousDragTargetItem should always be NULL here anyways. 444 if (fPreviousDragTargetItem != NULL) 445 _FinishedDrag(); 446 447 fBarView->CacheDragData(message); 448 fPreviousDragTargetItem = NULL; 449 break; 450 451 case B_OUTSIDE_VIEW: 452 // NOTE: Should not be here, but for the sake of defensive 453 // programming... fall-through 454 case B_EXITED_VIEW: 455 _FinishedDrag(); 456 break; 457 458 case B_INSIDE_VIEW: 459 if (fBarView != NULL && fBarView->Dragging()) { 460 TTeamMenuItem* item = NULL; 461 int32 itemCount = CountItems(); 462 for (int32 i = 0; i < itemCount; i++) { 463 BMenuItem* _item = ItemAt(i); 464 if (_item->Frame().Contains(where)) { 465 item = dynamic_cast<TTeamMenuItem*>(_item); 466 break; 467 } 468 } 469 if (item == fPreviousDragTargetItem) 470 break; 471 if (fPreviousDragTargetItem != NULL) 472 fPreviousDragTargetItem->SetOverrideSelected(false); 473 if (item != NULL) 474 item->SetOverrideSelected(true); 475 fPreviousDragTargetItem = item; 476 } 477 break; 478 } 479 } 480 481 482 void 483 TExpandoMenuBar::MouseUp(BPoint where) 484 { 485 TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem); 486 fLastClickedItem = NULL; 487 488 if (fBarView != NULL && fBarView->Dragging()) { 489 _FinishedDrag(true); 490 return; 491 // absorb the message 492 } 493 494 if (Vertical() && static_cast<TBarApp*>(be_app)->Settings()->superExpando 495 && lastItem != NULL && lastItem->ExpanderBounds().Contains(where)) { 496 lastItem->ToggleExpandState(true); 497 lastItem->SetArrowDirection(lastItem->IsExpanded() 498 ? BControlLook::B_DOWN_ARROW 499 : BControlLook::B_RIGHT_ARROW); 500 501 Invalidate(lastItem->ExpanderBounds()); 502 } 503 BMenuBar::MouseUp(where); 504 } 505 506 507 void 508 TExpandoMenuBar::BuildItems() 509 { 510 BMessenger self(this); 511 TBarApp::Subscribe(self, &fTeamList); 512 513 desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings(); 514 515 float itemWidth = -1.0f; 516 if (Vertical() && (fBarView->ExpandoState() || fBarView->FullState())) { 517 itemWidth = settings->width; 518 } 519 SetMaxContentWidth(itemWidth); 520 521 TeamMenuItemMap items; 522 int32 itemCount = CountItems(); 523 BList itemList(itemCount); 524 for (int32 i = 0; i < itemCount; i++) { 525 BMenuItem* menuItem = RemoveItem((int32)0); 526 itemList.AddItem(menuItem); 527 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(menuItem); 528 if (item != NULL) 529 items[BString(item->Signature()).ToLower()] = item; 530 } 531 532 if (settings->sortRunningApps) 533 fTeamList.SortItems(TTeamMenu::CompareByName); 534 535 int32 teamCount = fTeamList.CountItems(); 536 for (int32 i = 0; i < teamCount; i++) { 537 BarTeamInfo* barInfo = (BarTeamInfo*)fTeamList.ItemAt(i); 538 TeamMenuItemMap::const_iterator iter 539 = items.find(BString(barInfo->sig).ToLower()); 540 if (iter == items.end()) { 541 // new team 542 TTeamMenuItem* item = new TTeamMenuItem(barInfo->teams, 543 barInfo->icon, barInfo->name, barInfo->sig, itemWidth); 544 545 if (settings->trackerAlwaysFirst 546 && strcasecmp(barInfo->sig, kTrackerSignature) == 0) { 547 AddItem(item, 0); 548 } else 549 AddItem(item); 550 551 if (fFirstBuild && Vertical() && settings->expandNewTeams) 552 item->ToggleExpandState(true); 553 } else { 554 // existing team, update info and add it 555 TTeamMenuItem* item = iter->second; 556 item->SetIcon(barInfo->icon); 557 item->SetOverrideWidth(itemWidth); 558 559 if (settings->trackerAlwaysFirst 560 && strcasecmp(barInfo->sig, kTrackerSignature) == 0) { 561 AddItem(item, 0); 562 } else 563 AddItem(item); 564 565 // add window items back 566 int32 index = itemList.IndexOf(item); 567 TWindowMenuItem* windowItem; 568 TWindowMenu* submenu = dynamic_cast<TWindowMenu*>(item->Submenu()); 569 bool hasWindowItems = false; 570 while ((windowItem = dynamic_cast<TWindowMenuItem*>( 571 (BMenuItem*)(itemList.ItemAt(++index)))) != NULL) { 572 if (Vertical()) 573 AddItem(windowItem); 574 else { 575 delete windowItem; 576 hasWindowItems = submenu != NULL; 577 } 578 } 579 580 // unexpand if turn off show team expander 581 if (Vertical() && !settings->superExpando && item->IsExpanded()) 582 item->ToggleExpandState(false); 583 584 if (hasWindowItems) { 585 // add (new) window items in submenu 586 submenu->SetExpanded(false, 0); 587 submenu->AttachedToWindow(); 588 } 589 } 590 } 591 592 if (CountItems() == 0) { 593 // If we're empty, BMenuBar::AttachedToWindow() resizes us to some 594 // weird value - we just override it again 595 ResizeTo(gMinimumWindowWidth, 0); 596 } else { 597 // first build isn't complete until we've gotten here with an item 598 fFirstBuild = false; 599 } 600 } 601 602 603 bool 604 TExpandoMenuBar::InDeskbarMenu(BPoint loc) const 605 { 606 TBarWindow* window = dynamic_cast<TBarWindow*>(Window()); 607 if (window != NULL) { 608 if (TDeskbarMenu* bemenu = window->DeskbarMenu()) { 609 bool inDeskbarMenu = false; 610 if (bemenu->LockLooper()) { 611 inDeskbarMenu = bemenu->Frame().Contains(loc); 612 bemenu->UnlockLooper(); 613 } 614 return inDeskbarMenu; 615 } 616 } 617 618 return false; 619 } 620 621 622 /*! Returns the team menu item that belongs to the item under the 623 specified \a point. 624 If \a _item is given, it will return the exact menu item under 625 that point (which might be a window item when the expander is on). 626 */ 627 TTeamMenuItem* 628 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item) 629 { 630 TTeamMenuItem* lastApp = NULL; 631 int32 itemCount = CountItems(); 632 633 for (int32 i = 0; i < itemCount; i++) { 634 BMenuItem* item = ItemAt(i); 635 636 if (dynamic_cast<TTeamMenuItem*>(item) != NULL) 637 lastApp = (TTeamMenuItem*)item; 638 639 if (item && item->Frame().Contains(point)) { 640 if (_item != NULL) 641 *_item = item; 642 643 return lastApp; 644 } 645 } 646 647 // no item found 648 649 if (_item != NULL) 650 *_item = NULL; 651 652 return NULL; 653 } 654 655 656 void 657 TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name, 658 char* signature) 659 { 660 TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature); 661 662 desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings(); 663 if (settings != NULL && settings->trackerAlwaysFirst 664 && strcasecmp(signature, kTrackerSignature) == 0) { 665 AddItem(item, 0); 666 } else if (settings->sortRunningApps) { 667 TTeamMenuItem* teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(0)); 668 int32 firstApp = 0; 669 670 // if Tracker should always be the first item, we need to skip it 671 // when sorting in the current item 672 if (settings->trackerAlwaysFirst && teamItem != NULL 673 && strcasecmp(teamItem->Signature(), kTrackerSignature) == 0) { 674 firstApp++; 675 } 676 677 BCollator collator; 678 BLocale::Default()->GetCollator(&collator); 679 680 int32 i = firstApp; 681 int32 itemCount = CountItems(); 682 while (i < itemCount) { 683 teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 684 if (teamItem != NULL && collator.Compare(teamItem->Label(), name) 685 > 0) { 686 AddItem(item, i); 687 break; 688 } 689 i++; 690 } 691 // was the item added to the list yet? 692 if (i == itemCount) 693 AddItem(item); 694 } else 695 AddItem(item); 696 697 if (Vertical() && settings != NULL && settings->superExpando 698 && settings->expandNewTeams) { 699 item->ToggleExpandState(false); 700 } 701 702 SizeWindow(1); 703 Window()->UpdateIfNeeded(); 704 } 705 706 707 void 708 TExpandoMenuBar::AddTeam(team_id team, const char* signature) 709 { 710 int32 itemCount = CountItems(); 711 for (int32 i = 0; i < itemCount; i++) { 712 // Only add to team menu items 713 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 714 if (item != NULL && strcasecmp(item->Signature(), signature) == 0 715 && !(item->Teams()->HasItem((void*)(addr_t)team))) { 716 item->Teams()->AddItem((void*)(addr_t)team); 717 break; 718 } 719 } 720 } 721 722 723 void 724 TExpandoMenuBar::RemoveTeam(team_id team, bool partial) 725 { 726 TWindowMenuItem* windowItem = NULL; 727 728 for (int32 i = CountItems() - 1; i >= 0; i--) { 729 TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i)); 730 if (item != NULL && item->Teams()->HasItem((void*)(addr_t)team)) { 731 item->Teams()->RemoveItem(team); 732 if (partial) 733 return; 734 735 BAutolock locker(sMonLocker); 736 // make the update thread wait 737 RemoveItem(i); 738 if (item == fPreviousDragTargetItem) 739 fPreviousDragTargetItem = NULL; 740 741 if (item == fLastMousedOverItem) 742 fLastMousedOverItem = NULL; 743 744 if (item == fLastClickedItem) 745 fLastClickedItem = NULL; 746 747 delete item; 748 while ((windowItem = dynamic_cast<TWindowMenuItem*>( 749 ItemAt(i))) != NULL) { 750 // Also remove window items (if there are any) 751 RemoveItem(i); 752 if (windowItem == fLastMousedOverItem) 753 fLastMousedOverItem = NULL; 754 755 if (windowItem == fLastClickedItem) 756 fLastClickedItem = NULL; 757 758 delete windowItem; 759 } 760 SizeWindow(-1); 761 Window()->UpdateIfNeeded(); 762 return; 763 } 764 } 765 } 766 767 768 void 769 TExpandoMenuBar::CheckItemSizes(int32 delta, bool reset) 770 { 771 // horizontal only 772 if (fBarView == NULL || Vertical()) 773 return; 774 775 // minimum two items before size overrun can occur 776 int32 itemCount = CountItems(); 777 if (itemCount < 2) 778 return; 779 780 float minItemWidth = MinHorizontalItemWidth(); 781 float maxItemWidth = MaxHorizontalItemWidth(); 782 float maxMenuWidth = maxItemWidth * itemCount; 783 float maxWidth = MaxHorizontalWidth(); 784 bool tooWide = maxMenuWidth > maxWidth; 785 786 // start at max width 787 float newItemWidth = maxItemWidth; 788 789 if (delta < 0 && fOverflow) { 790 // removing an item, check if menu is still too wide 791 if (tooWide) 792 newItemWidth = floorf(maxWidth / itemCount); 793 else 794 newItemWidth = maxItemWidth; 795 } else if (tooWide) { 796 fOverflow = true; 797 newItemWidth = std::min(floorf(maxWidth / itemCount), maxItemWidth); 798 } 799 800 // see if we should grow items 801 fUnderflow = delta < 0 && newItemWidth < maxItemWidth; 802 803 if (fOverflow || fUnderflow || fFirstBuild || reset) { 804 // clip within limits 805 if (newItemWidth > maxItemWidth) 806 newItemWidth = maxItemWidth; 807 else if (newItemWidth < minItemWidth) 808 newItemWidth = minItemWidth; 809 810 SetMaxContentWidth(newItemWidth); 811 if (newItemWidth == maxItemWidth) 812 fOverflow = false; 813 814 for (int32 index = 0; ; index++) { 815 TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index); 816 if (item == NULL) 817 break; 818 819 item->SetOverrideWidth(newItemWidth); 820 } 821 822 InvalidateLayout(); 823 824 ResizeTo(newItemWidth * itemCount, Frame().Height()); 825 } 826 } 827 828 829 float 830 TExpandoMenuBar::MinHorizontalItemWidth() 831 { 832 int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize(); 833 float iconOnlyWidth = iconSize + kIconPadding; 834 835 return static_cast<TBarApp*>(be_app)->Settings()->hideLabels 836 ? iconOnlyWidth 837 : (iconSize - kMinimumIconSize) + gMinimumWindowWidth 838 + (be_plain_font->Size() - 12) * 4; 839 } 840 841 842 float 843 TExpandoMenuBar::MaxHorizontalItemWidth() 844 { 845 int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize(); 846 float iconOnlyWidth = iconSize + kIconPadding; 847 848 // hide labels 849 if (static_cast<TBarApp*>(be_app)->Settings()->hideLabels) 850 return iconOnlyWidth + kIconPadding; // add an extra icon padding 851 852 // set max item width to 1.25x min item width 853 return floorf(MinHorizontalItemWidth() * 1.25); 854 } 855 856 857 menu_layout 858 TExpandoMenuBar::MenuLayout() const 859 { 860 return Layout(); 861 } 862 863 864 void 865 TExpandoMenuBar::SetMenuLayout(menu_layout layout) 866 { 867 BPrivate::MenuPrivate(this).SetLayout(layout); 868 InvalidateLayout(); 869 } 870 871 872 void 873 TExpandoMenuBar::Draw(BRect updateRect) 874 { 875 BMenu::Draw(updateRect); 876 } 877 878 879 void 880 TExpandoMenuBar::DrawBackground(BRect updateRect) 881 { 882 if (Vertical()) 883 return; 884 885 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 1.22)); 886 StrokeLine(Bounds().RightTop(), Bounds().RightBottom()); 887 } 888 889 890 /*! Some methods to help determine if we are showing too many apps 891 and need to add or remove in scroll arrows. 892 */ 893 bool 894 TExpandoMenuBar::CheckForSizeOverrun() 895 { 896 if (Vertical()) 897 return CheckForSizeOverrunVertical(); 898 else 899 return CheckForSizeOverrunHorizontal(); 900 } 901 902 903 bool 904 TExpandoMenuBar::CheckForSizeOverrunVertical() 905 { 906 if (Window() == NULL || !Vertical()) 907 return false; 908 909 return Window()->Frame().bottom > (BScreen(Window())).Frame().bottom; 910 911 } 912 913 914 bool 915 TExpandoMenuBar::CheckForSizeOverrunHorizontal() 916 { 917 if (fBarView == NULL || Vertical()) 918 return false; 919 920 // minimum two items before size overrun can occur 921 int32 itemCount = CountItems(); 922 if (itemCount < 2) 923 return false; 924 925 float minMenuWidth = MinHorizontalItemWidth() * itemCount; 926 float maxWidth = MaxHorizontalWidth(); 927 928 return minMenuWidth > maxWidth; 929 } 930 931 932 float 933 TExpandoMenuBar::MaxHorizontalWidth() 934 { 935 return (fBarView->DragRegion()->Frame().left - 1) - kDeskbarMenuWidth; 936 } 937 938 939 void 940 TExpandoMenuBar::SizeWindow(int32 delta) 941 { 942 // instead of resizing the window here and there in the 943 // code the resize method will be centered in one place 944 // thus, the same behavior (good or bad) will be used 945 // wherever window sizing is done 946 if (fBarView == NULL || Window() == NULL) 947 return; 948 949 BRect screenFrame = (BScreen(Window())).Frame(); 950 fBarView->SizeWindow(screenFrame); 951 fBarView->PositionWindow(screenFrame); 952 953 if (!Vertical()) 954 CheckItemSizes(delta); 955 956 fBarView->CheckForScrolling(); 957 Window()->UpdateIfNeeded(); 958 Invalidate(); 959 } 960 961 962 void 963 TExpandoMenuBar::StartMonitoringWindows() 964 { 965 if (sMonThread != B_ERROR) 966 return; 967 968 sDoMonitor = true; 969 sMonThread = spawn_thread(monitor_team_windows, 970 "Expando Window Watcher", B_LOW_PRIORITY, this); 971 resume_thread(sMonThread); 972 } 973 974 975 void 976 TExpandoMenuBar::StopMonitoringWindows() 977 { 978 if (sMonThread == B_ERROR) 979 return; 980 981 sDoMonitor = false; 982 status_t returnCode; 983 wait_for_thread(sMonThread, &returnCode); 984 985 sMonThread = B_ERROR; 986 } 987 988 989 int32 990 TExpandoMenuBar::monitor_team_windows(void* arg) 991 { 992 TExpandoMenuBar* teamMenu = (TExpandoMenuBar*)arg; 993 994 while (teamMenu->sDoMonitor) { 995 sMonLocker.Lock(); 996 997 if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) { 998 int32 totalItems = teamMenu->CountItems(); 999 1000 // Set all WindowMenuItems to require an update. 1001 TWindowMenuItem* item = NULL; 1002 for (int32 i = 0; i < totalItems; i++) { 1003 if (!teamMenu->SubmenuAt(i)) { 1004 item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i)); 1005 item->SetRequireUpdate(true); 1006 } 1007 } 1008 1009 // Perform SetTo() on all the items that still exist as well as add 1010 // new items. 1011 bool itemModified = false; 1012 bool resize = false; 1013 TTeamMenuItem* teamItem = NULL; 1014 1015 for (int32 i = 0; i < totalItems; i++) { 1016 if (teamMenu->SubmenuAt(i) == NULL) 1017 continue; 1018 1019 teamItem = static_cast<TTeamMenuItem*>(teamMenu->ItemAt(i)); 1020 if (teamItem->IsExpanded()) { 1021 int32 teamCount = teamItem->Teams()->CountItems(); 1022 for (int32 j = 0; j < teamCount; j++) { 1023 // The following code is almost a copy/paste from 1024 // WindowMenu.cpp 1025 team_id theTeam = (addr_t)teamItem->Teams()->ItemAt(j); 1026 int32 count = 0; 1027 int32* tokens = get_token_list(theTeam, &count); 1028 1029 for (int32 k = 0; k < count; k++) { 1030 client_window_info* wInfo 1031 = get_window_info(tokens[k]); 1032 if (wInfo == NULL) 1033 continue; 1034 1035 BString windowName(wInfo->name); 1036 1037 BString teamPrefix(teamItem->Label()); 1038 teamPrefix.Append(": "); 1039 1040 BString teamSuffix(" - "); 1041 teamSuffix.Append(teamItem->Label()); 1042 1043 if (windowName.StartsWith(teamPrefix)) 1044 windowName.RemoveFirst(teamPrefix); 1045 if (windowName.EndsWith(teamSuffix)) 1046 windowName.RemoveLast(teamSuffix); 1047 1048 if (TWindowMenu::WindowShouldBeListed(wInfo)) { 1049 // Check if we have a matching window item... 1050 item = teamItem->ExpandedWindowItem( 1051 wInfo->server_token); 1052 if (item != NULL) { 1053 item->SetTo(windowName, 1054 wInfo->server_token, wInfo->is_mini, 1055 ((1 << current_workspace()) 1056 & wInfo->workspaces) != 0); 1057 1058 if (strcasecmp(item->Label(), windowName) 1059 > 0) { 1060 item->SetLabel(windowName); 1061 } 1062 if (item->Modified()) 1063 itemModified = true; 1064 } else if (teamItem->IsExpanded()) { 1065 // Add the item 1066 item = new TWindowMenuItem(windowName, 1067 wInfo->server_token, wInfo->is_mini, 1068 ((1 << current_workspace()) 1069 & wInfo->workspaces) != 0, false); 1070 item->SetExpanded(true); 1071 teamMenu->AddItem(item, 1072 TWindowMenuItem::InsertIndexFor( 1073 teamMenu, i + 1, item)); 1074 resize = true; 1075 } 1076 } 1077 free(wInfo); 1078 } 1079 free(tokens); 1080 } 1081 } 1082 } 1083 1084 // Remove any remaining items which require an update. 1085 for (int32 i = 0; i < totalItems; i++) { 1086 if (!teamMenu->SubmenuAt(i)) { 1087 item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i)); 1088 if (item && item->RequiresUpdate()) { 1089 item = static_cast<TWindowMenuItem*> 1090 (teamMenu->RemoveItem(i)); 1091 delete item; 1092 totalItems--; 1093 1094 resize = true; 1095 } 1096 } 1097 } 1098 1099 // If any of the WindowMenuItems changed state, we need to force a 1100 // repaint. 1101 if (itemModified || resize) { 1102 teamMenu->Invalidate(); 1103 if (resize) 1104 teamMenu->SizeWindow(1); 1105 } 1106 1107 teamMenu->Window()->Unlock(); 1108 } 1109 1110 sMonLocker.Unlock(); 1111 1112 // sleep for a bit... 1113 snooze(150000); 1114 } 1115 return B_OK; 1116 } 1117 1118 1119 void 1120 TExpandoMenuBar::_FinishedDrag(bool invoke) 1121 { 1122 if (fPreviousDragTargetItem != NULL) { 1123 if (invoke) 1124 fPreviousDragTargetItem->Invoke(); 1125 1126 fPreviousDragTargetItem->SetOverrideSelected(false); 1127 fPreviousDragTargetItem = NULL; 1128 } 1129 1130 if (!invoke && fBarView != NULL && fBarView->Dragging()) 1131 fBarView->DragStop(true); 1132 } 1133