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