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