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
TExpandoMenuBar(menu_layout layout,TBarView * barView)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
AllAttached()102 TExpandoMenuBar::AllAttached()
103 {
104 BMenuBar::AllAttached();
105
106 SizeWindow(0);
107 }
108
109
110 void
AttachedToWindow()111 TExpandoMenuBar::AttachedToWindow()
112 {
113 BMenuBar::AttachedToWindow();
114
115 fTeamList.MakeEmpty();
116
117 if (Vertical())
118 StartMonitoringWindows();
119 }
120
121
122 void
DetachedFromWindow()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
MessageReceived(BMessage * message)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
MouseDown(BPoint where)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
MouseMoved(BPoint where,uint32 code,const BMessage * message)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
MouseUp(BPoint where)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
BuildItems()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
InDeskbarMenu(BPoint loc) const552 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*
ItemAtPoint(BPoint point)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*
TeamItemAtPoint(BPoint point,BMenuItem ** _item)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
AddTeam(BList * team,BBitmap * icon,char * name,char * signature)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
AddTeam(team_id team,const char * signature)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
RemoveTeam(team_id team,bool partial)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
CheckItemSizes(int32 delta,bool reset)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
MinHorizontalItemWidth()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
MaxHorizontalItemWidth()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
MenuLayout() const824 TExpandoMenuBar::MenuLayout() const
825 {
826 return Layout();
827 }
828
829
830 void
SetMenuLayout(menu_layout layout)831 TExpandoMenuBar::SetMenuLayout(menu_layout layout)
832 {
833 BPrivate::MenuPrivate(this).SetLayout(layout);
834 InvalidateLayout();
835 }
836
837
838 void
Draw(BRect updateRect)839 TExpandoMenuBar::Draw(BRect updateRect)
840 {
841 BMenu::Draw(updateRect);
842 }
843
844
845 void
DrawBackground(BRect updateRect)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
CheckForSizeOverrun()860 TExpandoMenuBar::CheckForSizeOverrun()
861 {
862 if (Vertical())
863 return CheckForSizeOverrunVertical();
864 else
865 return CheckForSizeOverrunHorizontal();
866 }
867
868
869 bool
CheckForSizeOverrunVertical()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
CheckForSizeOverrunHorizontal()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
MaxHorizontalWidth()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
SizeWindow(int32 delta)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
StartMonitoringWindows()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
StopMonitoringWindows()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
monitor_team_windows(void * arg)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
_FinishedDrag(bool invoke)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