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