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 trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "tracker_private.h" 37 #include "BarApp.h" 38 #include "Switcher.h" 39 #include "ResourceSet.h" 40 #include "WindowMenuItem.h" 41 #include "icons.h" 42 43 #include <Bitmap.h> 44 #include <Debug.h> 45 #include <Font.h> 46 #include <Mime.h> 47 #include <Node.h> 48 #include <NodeInfo.h> 49 #include <Roster.h> 50 #include <Screen.h> 51 #include <String.h> 52 53 #include <string.h> 54 #include <stdlib.h> 55 #include <float.h> 56 57 58 #define _ALLOW_STICKY_ 0 59 // allows you to press 's' to keep the switcher window on screen 60 61 #if __HAIKU__ 62 static const color_space kIconFormat = B_RGBA32; 63 #else 64 static const color_space kIconFormat = B_CMAP8; 65 #endif 66 67 68 class TTeamGroup { 69 public: 70 TTeamGroup(); 71 TTeamGroup(BList *teams, uint32 flags, char *name, const char *sig); 72 virtual ~TTeamGroup(); 73 74 void Draw(BView *, BRect bounds, bool main); 75 76 BList *TeamList() const 77 { return fTeams; } 78 const char *Name() const 79 { return fName; } 80 const char *Signature() const 81 { return fSignature; } 82 uint32 Flags() const 83 { return fFlags; } 84 const BBitmap *SmallIcon() const 85 { return fSmallIcon; } 86 const BBitmap *LargeIcon() const 87 { return fLargeIcon; } 88 89 private: 90 BList *fTeams; 91 uint32 fFlags; 92 char fSignature[B_MIME_TYPE_LENGTH]; 93 char *fName; 94 BBitmap *fSmallIcon; 95 BBitmap *fLargeIcon; 96 }; 97 98 class TSwitcherWindow : public BWindow { 99 public: 100 TSwitcherWindow(BRect frame, TSwitchManager *mgr); 101 virtual ~TSwitcherWindow(); 102 103 virtual bool QuitRequested(); 104 virtual void MessageReceived(BMessage *message); 105 virtual void Show(); 106 virtual void Hide(); 107 virtual void WindowActivated(bool state); 108 109 void DoKey(uint32 key, uint32 mods); 110 TIconView *IconView(); 111 TWindowView *WindowView(); 112 TBox *TopView(); 113 bool HairTrigger(); 114 void Update(int32 previous, int32 current, int32 prevSlot, 115 int32 currentSlot, bool forward); 116 int32 SlotOf(int32); 117 void Redraw(int32 index); 118 119 private: 120 TSwitchManager *fManager; 121 TIconView *fIconView; 122 TBox *fTopView; 123 TWindowView *fWindowView; 124 bool fHairTrigger; 125 bool fSkipKeyRepeats; 126 }; 127 128 class TWindowView : public BView { 129 public: 130 TWindowView(BRect frame, TSwitchManager *manager, TSwitcherWindow *switcher); 131 132 void UpdateGroup(int32 groupIndex, int32 windowIndex); 133 134 virtual void AttachedToWindow(); 135 virtual void Draw(BRect update); 136 virtual void Pulse(); 137 virtual void GetPreferredSize(float *w, float *h); 138 void ScrollTo(float x, float y) 139 { ScrollTo(BPoint(x,y)); } 140 virtual void ScrollTo(BPoint where); 141 142 void ShowIndex(int32 windex); 143 BRect FrameOf(int32 index) const; 144 145 private: 146 int32 fCurrentToken; 147 float fItemHeight; 148 TSwitcherWindow *fSwitcher; 149 TSwitchManager *fManager; 150 bool fLocal; 151 }; 152 153 class TIconView : public BView { 154 public: 155 TIconView(BRect frame, TSwitchManager *manager, TSwitcherWindow *switcher); 156 ~TIconView(); 157 158 void Showing(); 159 void Hiding(); 160 161 virtual void KeyDown(const char *bytes, int32 numBytes); 162 virtual void Pulse(); 163 virtual void MouseDown(BPoint ); 164 virtual void Draw(BRect ); 165 166 void ScrollTo(float x, float y) 167 { ScrollTo(BPoint(x,y)); } 168 virtual void ScrollTo(BPoint where); 169 void Update(int32 previous, int32 current, int32 previousSlot, 170 int32 currentSlot, bool forward); 171 void DrawTeams(BRect update); 172 int32 SlotOf(int32) const; 173 BRect FrameOf(int32) const; 174 int32 ItemAtPoint(BPoint) const; 175 int32 IndexAt(int32 slot) const; 176 void CenterOn(int32 index); 177 178 private: 179 void CacheIcons(TTeamGroup *); 180 void AnimateIcon(BBitmap *startIcon, BBitmap *endIcon); 181 182 bool fAutoScrolling; 183 bool fCapsState; 184 TSwitcherWindow *fSwitcher; 185 TSwitchManager *fManager; 186 BBitmap *fOffBitmap; 187 BView *fOffView; 188 BBitmap *fCurrentSmall; 189 BBitmap *fCurrentLarge; 190 }; 191 192 class TBox : public BBox { 193 public: 194 TBox(BRect bounds, TSwitchManager *manager, TSwitcherWindow *, TIconView *iconView); 195 196 virtual void Draw(BRect update); 197 virtual void AllAttached(); 198 virtual void DrawIconScrollers(bool force); 199 virtual void DrawWindowScrollers(bool force); 200 virtual void MouseDown(BPoint where); 201 202 private: 203 TSwitchManager *fManager; 204 TSwitcherWindow *fWindow; 205 TIconView *fIconView; 206 BRect fCenter; 207 bool fLeftScroller; 208 bool fRightScroller; 209 bool fUpScroller; 210 bool fDownScroller; 211 }; 212 213 214 const int32 kHorizontalMargin = 11; 215 const int32 kVerticalMargin = 10; 216 217 // SLOT_SIZE must be divisible by 4. That's because of the scrolling 218 // animation. If this needs to change then look at TIconView::Update() 219 220 const int32 kSlotSize = 36; 221 const int32 kScrollStep = kSlotSize / 2; 222 const int32 kNumSlots = 7; 223 const int32 kCenterSlot = 3; 224 225 226 // #pragma mark - 227 228 229 static int32 230 LowBitIndex(uint32 value) 231 { 232 int32 result = 0; 233 int32 bitMask = 1; 234 235 if (value == 0) 236 return -1; 237 238 while (result < 32 && (value & bitMask) == 0) { 239 result++; 240 bitMask = bitMask << 1; 241 } 242 return result; 243 } 244 245 246 inline bool 247 IsVisibleInCurrentWorkspace(const window_info *windowInfo) 248 { 249 /* 250 The window list is always ordered from the top 251 front visible window (the first on the list), going down through all 252 the other visible windows, then all the hidden or non workspace 253 visible window at the end. 254 255 layer > 2 : normal visible window. 256 layer == 2 : reserved for the desktop window (visible also). 257 layer < 2 : hidden (0) and non workspace visible window (1) 258 */ 259 return windowInfo->layer > 2; 260 } 261 262 263 bool 264 IsKeyDown(int32 key) 265 { 266 key_info keyInfo; 267 268 get_key_info(&keyInfo); 269 return (keyInfo.key_states[key >> 3] & (1 << ((7 - key) & 7))) != 0; 270 } 271 272 273 bool 274 IsWindowOK(const window_info *windowInfo) 275 { 276 // is_mini (true means that the window is minimized). 277 // if not, then 278 // show_hide >= 1 means that the window is hidden. 279 // 280 // If the window is both minimized and hidden, then you get : 281 // TWindow->is_mini = false; 282 // TWindow->was_mini = true; 283 // TWindow->show_hide >= 1; 284 285 if (windowInfo->w_type != _STD_W_TYPE_) 286 return false; 287 288 if (windowInfo->is_mini) 289 return true; 290 291 return windowInfo->show_hide_level <= 0; 292 } 293 294 295 bool 296 OKToUse(const TTeamGroup *teamGroup) 297 { 298 if (!teamGroup) 299 return false; 300 301 // skip background applications 302 if ((teamGroup->Flags() & B_BACKGROUND_APP) != 0) 303 return false; 304 305 // skip the Deakbar itself 306 if (strcasecmp(teamGroup->Signature(), kDeskbarSignature) == 0) 307 return false; 308 309 return true; 310 } 311 312 313 int 314 SmartStrcmp(const char *s1, const char *s2) 315 { 316 if (strcasecmp(s1, s2) == 0) 317 return 0; 318 319 // if the strings on differ in spaces or underscores they still match 320 while (*s1 && *s2) { 321 if ((*s1 == ' ') || (*s1 == '_')) { 322 s1++; 323 continue; 324 } 325 if ((*s2 == ' ') || (*s2 == '_')) { 326 s2++; 327 continue; 328 } 329 if (*s1 != *s2) 330 return 1; // they differ 331 s1++; 332 s2++; 333 } 334 335 // if one of the strings ended before the other 336 // ??? could process trailing spaces & underscores! 337 if (*s1) 338 return 1; 339 if (*s2) 340 return 1; 341 342 return 0; 343 } 344 345 346 // #pragma mark - 347 348 349 TTeamGroup::TTeamGroup() 350 : 351 fTeams(NULL), 352 fFlags(0), 353 fName(NULL), 354 fSmallIcon(NULL), 355 fLargeIcon(NULL) 356 { 357 fSignature[0] = '\0'; 358 } 359 360 361 TTeamGroup::TTeamGroup(BList *teams, uint32 flags, char *name, 362 const char *signature) 363 : 364 fTeams(teams), 365 fFlags(flags), 366 fName(name), 367 fSmallIcon(NULL), 368 fLargeIcon(NULL) 369 { 370 strcpy(fSignature, signature); 371 372 fSmallIcon = new BBitmap(BRect(0, 0, 15, 15), kIconFormat); 373 fLargeIcon = new BBitmap(BRect(0, 0, 31, 31), kIconFormat); 374 375 app_info appInfo; 376 if (be_roster->GetAppInfo(signature, &appInfo) == B_OK) { 377 BNode node(&(appInfo.ref)); 378 if (node.InitCheck() == B_OK) { 379 BNodeInfo nodeInfo(&node); 380 if (nodeInfo.InitCheck() == B_OK) { 381 nodeInfo.GetTrackerIcon(fSmallIcon, B_MINI_ICON); 382 nodeInfo.GetTrackerIcon(fLargeIcon, B_LARGE_ICON); 383 } 384 } 385 } 386 } 387 388 389 TTeamGroup::~TTeamGroup() 390 { 391 delete fTeams; 392 free(fName); 393 delete fSmallIcon; 394 delete fLargeIcon; 395 } 396 397 398 void 399 TTeamGroup::Draw(BView *view, BRect bounds, bool main) 400 { 401 BRect rect; 402 if (main) { 403 rect = fLargeIcon->Bounds(); 404 rect.OffsetTo(bounds.LeftTop()); 405 rect.OffsetBy(2, 2); 406 view->DrawBitmap(fLargeIcon, rect); 407 } else { 408 rect = fSmallIcon->Bounds(); 409 rect.OffsetTo(bounds.LeftTop()); 410 rect.OffsetBy(10, 10); 411 view->DrawBitmap(fSmallIcon, rect); 412 } 413 } 414 415 416 // #pragma mark - 417 418 419 TSwitchManager::TSwitchManager(BPoint point) 420 : BHandler("SwitchManager"), 421 fMainMonitor(create_sem(1, "main_monitor")), 422 fBlock(false), 423 fSkipUntil(0), 424 fGroupList(10), 425 fCurrentIndex(0), 426 fCurrentSlot(0), 427 fWindowID(-1) 428 { 429 BRect rect(point.x, point.y, 430 point.x + (kSlotSize * kNumSlots) - 1 + (2 * kHorizontalMargin), 431 point.y + 82); 432 fWindow = new TSwitcherWindow(rect, this); 433 fWindow->AddHandler(this); 434 435 fWindow->Lock(); 436 fWindow->Run(); 437 438 BList tmpList; 439 TBarApp::Subscribe(BMessenger(this), &tmpList); 440 441 for (int32 i = 0; ; i++) { 442 BarTeamInfo *barTeamInfo = (BarTeamInfo *)tmpList.ItemAt(i); 443 if (!barTeamInfo) 444 break; 445 446 TTeamGroup *tinfo = new TTeamGroup(barTeamInfo->teams, barTeamInfo->flags, 447 barTeamInfo->name, barTeamInfo->sig); 448 fGroupList.AddItem(tinfo); 449 450 barTeamInfo->teams = NULL; 451 barTeamInfo->name = NULL; 452 453 delete barTeamInfo; 454 } 455 fWindow->Unlock(); 456 } 457 458 459 TSwitchManager::~TSwitchManager() 460 { 461 for (int32 i = fGroupList.CountItems(); i-- > 0;) { 462 TTeamGroup *teamInfo = static_cast<TTeamGroup *>(fGroupList.ItemAt(i)); 463 delete teamInfo; 464 } 465 } 466 467 468 void 469 TSwitchManager::MessageReceived(BMessage *message) 470 { 471 switch (message->what) { 472 case B_SOME_APP_QUIT: 473 { 474 // This is only sent when last team of a matching set quits 475 team_id teamID; 476 int i = 0; 477 TTeamGroup *tinfo; 478 message->FindInt32("team", &teamID); 479 while ((tinfo = (TTeamGroup *) fGroupList.ItemAt(i)) != NULL) { 480 if (tinfo->TeamList()->HasItem((void *)teamID)) { 481 fGroupList.RemoveItem(i); 482 483 if (OKToUse(tinfo)) { 484 fWindow->Redraw(i); 485 if (i <= fCurrentIndex) { 486 fCurrentIndex--; 487 CycleApp(true); 488 } 489 } 490 delete tinfo; 491 break; 492 } 493 i++; 494 } 495 break; 496 } 497 498 case B_SOME_APP_LAUNCHED: 499 { 500 BList *teams; 501 const char *name; 502 BBitmap *smallIcon; 503 uint32 flags; 504 const char *signature; 505 506 if (message->FindPointer("teams", (void **)&teams) != B_OK) 507 break; 508 509 if (message->FindPointer("icon", (void **)&smallIcon) != B_OK) { 510 delete teams; 511 break; 512 } 513 delete smallIcon; 514 if (message->FindString("sig", &signature) != B_OK) { 515 delete teams; 516 break; 517 } 518 if (message->FindInt32("flags", (int32 *)&flags) != B_OK) { 519 delete teams; 520 break; 521 } 522 if (message->FindString("name", &name) != B_OK) { 523 delete teams; 524 break; 525 } 526 527 TTeamGroup *tinfo = new TTeamGroup(teams, flags, strdup(name), signature); 528 529 fGroupList.AddItem(tinfo); 530 if (OKToUse(tinfo)) 531 fWindow->Redraw(fGroupList.CountItems() - 1); 532 533 break; 534 } 535 536 case msg_AddTeam: 537 { 538 const char *signature = message->FindString("sig"); 539 team_id team = message->FindInt32("team"); 540 541 int32 numItems = fGroupList.CountItems(); 542 for (int32 i = 0; i < numItems; i++) { 543 TTeamGroup *tinfo = (TTeamGroup *)fGroupList.ItemAt(i); 544 if (strcasecmp(tinfo->Signature(), signature) == 0) { 545 if (!(tinfo->TeamList()->HasItem((void *)team))) 546 tinfo->TeamList()->AddItem((void *)team); 547 break; 548 } 549 } 550 break; 551 } 552 553 case msg_RemoveTeam: 554 { 555 team_id team = message->FindInt32("team"); 556 557 int32 numItems = fGroupList.CountItems(); 558 for (int32 i = 0; i < numItems; i++) { 559 TTeamGroup *tinfo = (TTeamGroup *)fGroupList.ItemAt(i); 560 if (tinfo->TeamList()->HasItem((void *)team)) { 561 tinfo->TeamList()->RemoveItem((void *)team); 562 break; 563 } 564 } 565 break; 566 } 567 568 case 'TASK': 569 { 570 // The first TASK message calls MainEntry. Subsequent ones 571 // call Process(). 572 bigtime_t time; 573 message->FindInt64("when", (int64 *)&time); 574 575 // The fSkipUntil stuff can be removed once the new input_server 576 // starts differentiating initial key_downs from KeyDowns generated 577 // by auto-repeat. Until then the fSkipUntil stuff helps, but it 578 // isn't perfect. 579 580 if (time < fSkipUntil) 581 break; 582 583 status_t status = acquire_sem_etc(fMainMonitor, 1, B_TIMEOUT, 0); 584 if (status != B_OK) { 585 if (!fWindow->IsHidden() && !fBlock) { 586 // Want to skip TASK msgs posted before the window 587 // was made visible. Better UI feel if we do this. 588 if (time > fSkipUntil) { 589 uint32 modifiers; 590 message->FindInt32("modifiers", (int32 *)&modifiers); 591 Process((modifiers & B_SHIFT_KEY) == 0, (modifiers & B_OPTION_KEY) != 0); 592 } 593 } 594 } else 595 MainEntry(message); 596 597 break; 598 } 599 600 default: 601 break; 602 } 603 } 604 605 606 void 607 TSwitchManager::MainEntry(BMessage *message) 608 { 609 bigtime_t keyRepeatRate; 610 get_key_repeat_delay(&keyRepeatRate); 611 612 if (keyRepeatRate < 200000) 613 keyRepeatRate = 200000; 614 615 bigtime_t timeout = system_time() + keyRepeatRate; 616 617 app_info appInfo; 618 be_roster->GetActiveAppInfo(&appInfo); 619 620 int32 index; 621 fCurrentIndex = (FindTeam(appInfo.team, &index) != NULL) ? index : 0; 622 int32 key; 623 message->FindInt32("key", (int32 *)&key); 624 625 uint32 modifierKeys = 0; 626 while (system_time() < timeout) { 627 modifierKeys = modifiers(); 628 if (!IsKeyDown(key)) { 629 QuickSwitch(message); 630 return; 631 } 632 if ((modifierKeys & B_CONTROL_KEY) == 0) { 633 QuickSwitch(message); 634 return; 635 } 636 snooze(50000); 637 } 638 639 Process((modifierKeys & B_SHIFT_KEY) == 0, (modifierKeys & B_OPTION_KEY) != 0); 640 } 641 642 643 void 644 TSwitchManager::Stop(bool do_action, uint32) 645 { 646 fWindow->Hide(); 647 if (do_action) 648 ActivateApp(true, true); 649 650 release_sem(fMainMonitor); 651 } 652 653 654 TTeamGroup * 655 TSwitchManager::FindTeam(team_id teamID, int32 *index) 656 { 657 int i = 0; 658 TTeamGroup *tinfo; 659 while ((tinfo = (TTeamGroup *)fGroupList.ItemAt(i)) != NULL) { 660 if (tinfo->TeamList()->HasItem((void*) teamID)) { 661 *index = i; 662 return tinfo; 663 } 664 i++; 665 } 666 667 return NULL; 668 } 669 670 671 void 672 TSwitchManager::Process(bool forward, bool byWindow) 673 { 674 bool hidden = false; 675 if (fWindow->Lock()) { 676 hidden = fWindow->IsHidden(); 677 fWindow->Unlock(); 678 } 679 if (byWindow) { 680 // If hidden we need to get things started by switching to correct app 681 if (hidden) 682 SwitchToApp(fCurrentIndex, fCurrentIndex, forward); 683 CycleWindow(forward, false); 684 } else 685 CycleApp(forward, false); 686 687 if (hidden) { 688 // more auto keyrepeat code 689 // Because of key repeats we don't want to respond to any extraneous 690 // 'TASK' messages until the window is completely shown. So block here. 691 // the WindowActivated hook function will unblock. 692 fBlock = true; 693 694 if (fWindow->Lock()) { 695 BRect screenFrame = BScreen().Frame(); 696 BRect windowFrame = fWindow->Frame(); 697 698 if (!screenFrame.Contains(windowFrame)) { 699 // center the window 700 BPoint point((screenFrame.left + screenFrame.right) / 2, 701 (screenFrame.top + screenFrame.bottom) / 2); 702 703 point.x -= (windowFrame.Width() / 2); 704 point.y -= (windowFrame.Height() / 2); 705 fWindow->MoveTo(point); 706 } 707 708 fWindow->Show(); 709 fWindow->Unlock(); 710 } 711 } 712 } 713 714 715 void 716 TSwitchManager::QuickSwitch(BMessage *message) 717 { 718 uint32 modifiers = 0; 719 message->FindInt32("modifiers", (int32 *) &modifiers); 720 721 team_id team; 722 if (message->FindInt32("team", &team) == B_OK) { 723 bool forward = ((modifiers & B_SHIFT_KEY) == 0); 724 725 if ((modifiers & B_OPTION_KEY) != 0) 726 SwitchWindow(team, forward, true); 727 else 728 CycleApp(forward, true); 729 } 730 731 release_sem(fMainMonitor); 732 } 733 734 735 int32 736 TSwitchManager::CountVisibleGroups() 737 { 738 int32 result = 0; 739 740 int32 count = fGroupList.CountItems(); 741 for (int32 i = 0; i < count; i++) { 742 if (!OKToUse((TTeamGroup *) fGroupList.ItemAt(i))) 743 continue; 744 745 result++; 746 } 747 return result; 748 } 749 750 751 void 752 TSwitchManager::CycleWindow(bool forward, bool wrap) 753 { 754 int32 max = CountWindows(fCurrentIndex); 755 int32 prev = fCurrentWindow; 756 int32 next = fCurrentWindow; 757 758 if (forward) { 759 next++; 760 if (next >= max) { 761 if (!wrap) 762 return; 763 next = 0; 764 } 765 } else { 766 next--; 767 if (next < 0) { 768 if (!wrap) 769 return; 770 next = max - 1; 771 } 772 } 773 fCurrentWindow = next; 774 775 if (fCurrentWindow != prev) 776 fWindow->WindowView()->ShowIndex(fCurrentWindow); 777 } 778 779 780 void 781 TSwitchManager::CycleApp(bool forward, bool activateNow) 782 { 783 int32 startIndex = fCurrentIndex; 784 int32 max = fGroupList.CountItems(); 785 786 for (;;) { 787 if (forward) { 788 fCurrentIndex++; 789 if (fCurrentIndex >= max) 790 fCurrentIndex = 0; 791 } else { 792 fCurrentIndex--; 793 if (fCurrentIndex < 0) 794 fCurrentIndex = max - 1; 795 } 796 if ((fCurrentIndex == startIndex)) 797 // we've gone completely through the list without finding 798 // a good app. Oh well. 799 return; 800 801 if (!OKToUse((TTeamGroup *)fGroupList.ItemAt(fCurrentIndex))) 802 continue; 803 804 // if we're here then we found a good one 805 SwitchToApp(startIndex, fCurrentIndex, forward); 806 807 if (!activateNow) 808 break; 809 810 if (ActivateApp(false, false)) 811 break; 812 } 813 } 814 815 816 void 817 TSwitchManager::SwitchToApp(int32 previousIndex, int32 newIndex, bool forward) 818 { 819 int32 previousSlot = fCurrentSlot; 820 821 fCurrentIndex = newIndex; 822 fCurrentSlot = fWindow->SlotOf(fCurrentIndex); 823 fCurrentWindow = 0; 824 825 fWindow->Update(previousIndex, fCurrentIndex, previousSlot, fCurrentSlot, forward); 826 } 827 828 829 bool 830 TSwitchManager::ActivateApp(bool forceShow, bool allowWorkspaceSwitch) 831 { 832 // Let's get the info about the selected window. If it doesn't exist 833 // anymore then get info about first window. If that doesn't exist then 834 // do nothing. 835 window_info *windowInfo = WindowInfo(fCurrentIndex, fCurrentWindow); 836 if (windowInfo == NULL) { 837 windowInfo = WindowInfo(fCurrentIndex, 0); 838 if (windowInfo == NULL) 839 return false; 840 } 841 842 int32 currentWorkspace = current_workspace(); 843 TTeamGroup *teamGroup = (TTeamGroup *)fGroupList.ItemAt(fCurrentIndex); 844 // Let's handle the easy case first: There's only 1 team in the group 845 if (teamGroup->TeamList()->CountItems() == 1) { 846 bool result; 847 if (forceShow && (fCurrentWindow != 0 || windowInfo->is_mini)) 848 do_window_action(windowInfo->id, B_BRING_TO_FRONT, 849 BRect(0, 0, 0, 0), false); 850 851 if (!forceShow && windowInfo->is_mini) { 852 // we aren't unhiding minimized windows, so we can't do 853 // anything here 854 result = false; 855 } else if (!allowWorkspaceSwitch 856 && (windowInfo->workspaces & (1 << currentWorkspace)) == 0) { 857 // we're not supposed to switch workspaces so abort. 858 result = false; 859 } else { 860 result = true; 861 be_roster->ActivateApp((team_id)teamGroup->TeamList()->ItemAt(0)); 862 } 863 864 ASSERT(windowInfo); 865 free(windowInfo); 866 return result; 867 } 868 869 // Now the trickier case. We're trying to Bring to the Front a group 870 // of teams. The current window (defined by fCurrentWindow) will define 871 // which workspace we're going to. Then, once that is determined we 872 // want to bring to the front every window of the group of teams that 873 // lives in that workspace. 874 875 if ((windowInfo->workspaces & (1 << currentWorkspace)) == 0) { 876 if (!allowWorkspaceSwitch) { 877 // If the first window in the list isn't in current workspace, 878 // then none are. So we can't switch to this app. 879 ASSERT(windowInfo); 880 free(windowInfo); 881 return false; 882 } 883 int32 destWorkspace = LowBitIndex(windowInfo->workspaces); 884 // now switch to that workspace 885 activate_workspace(destWorkspace); 886 } 887 888 if (!forceShow && windowInfo->is_mini) { 889 // If the first window in the list is hidden then no windows in 890 // this group are visible. So we can't switch to this app. 891 ASSERT(windowInfo); 892 free(windowInfo); 893 return false; 894 } 895 896 int32 tokenCount; 897 int32 *tokens = get_token_list(-1, &tokenCount); 898 if (!tokens) { 899 ASSERT(windowInfo); 900 free(windowInfo); 901 return true; // weird error, so don't try to recover 902 } 903 904 BList windowsToActivate; 905 906 // Now we go through all the windows in the current workspace list in order. 907 // As we hit member teams we build the "activate" list. 908 for (int32 i = 0; i < tokenCount; i++) { 909 window_info *matchWindowInfo = get_window_info(tokens[i]); 910 if (!matchWindowInfo) { 911 // That window probably closed. Just go to the next one. 912 continue; 913 } 914 if (!IsVisibleInCurrentWorkspace(matchWindowInfo)) { 915 // first non-visible in workspace window means we're done. 916 free(matchWindowInfo); 917 break; 918 } 919 if (matchWindowInfo->id != windowInfo->id 920 && teamGroup->TeamList()->HasItem((void *)matchWindowInfo->team)) 921 windowsToActivate.AddItem((void *)matchWindowInfo->id); 922 923 free(matchWindowInfo); 924 } 925 926 free(tokens); 927 928 // Want to go through the list backwards to keep windows in same relative 929 // order. 930 int32 i = windowsToActivate.CountItems() - 1; 931 for (; i >= 0; i--) { 932 int32 wid = (int32) windowsToActivate.ItemAt(i); 933 do_window_action(wid, B_BRING_TO_FRONT, BRect(0, 0, 0, 0), false); 934 } 935 936 // now bring the select window on top of everything. 937 938 do_window_action(windowInfo->id, B_BRING_TO_FRONT, BRect(0, 0, 0, 0), false); 939 free(windowInfo); 940 return true; 941 } 942 943 944 void 945 TSwitchManager::QuitApp() 946 { 947 // check if we're in the last slot already (the last usable team group) 948 949 TTeamGroup *teamGroup; 950 int32 count = 0; 951 for (int32 i = fCurrentIndex + 1; i < fGroupList.CountItems(); i++) { 952 teamGroup = (TTeamGroup *)fGroupList.ItemAt(i); 953 954 if (!OKToUse(teamGroup)) 955 continue; 956 957 count++; 958 } 959 960 teamGroup = (TTeamGroup *)fGroupList.ItemAt(fCurrentIndex); 961 962 if (count == 0) { 963 // switch to previous app in the list so that we don't jump to 964 // the start of the list (try to keep the same position when 965 // the apps at the current index go away) 966 CycleApp(false, false); 967 } 968 969 // send the quit request to all teams in this group 970 971 for (int32 i = teamGroup->TeamList()->CountItems(); i-- > 0;) { 972 team_id team = (team_id)teamGroup->TeamList()->ItemAt(i); 973 app_info info; 974 if (be_roster->GetRunningAppInfo(team, &info) == B_OK) { 975 if (!strcasecmp(info.signature, kTrackerSignature)) { 976 // Tracker can't be quit this way 977 continue; 978 } 979 980 BMessenger messenger(NULL, team); 981 messenger.SendMessage(B_QUIT_REQUESTED); 982 } 983 } 984 } 985 986 987 window_info * 988 TSwitchManager::WindowInfo(int32 groupIndex, int32 windowIndex) 989 { 990 TTeamGroup *teamGroup = (TTeamGroup *) fGroupList.ItemAt(groupIndex); 991 if (!teamGroup) 992 return NULL; 993 994 int32 tokenCount; 995 int32 *tokens = get_token_list(-1, &tokenCount); 996 if (!tokens) 997 return NULL; 998 999 int32 matches = 0; 1000 1001 // Want to find the "windowIndex'th" window in window order that belongs 1002 // the the specified group (groupIndex). Since multiple teams can belong to 1003 // the same group (multiple-launch apps) we get the list of _every_ 1004 // window and go from there. 1005 1006 window_info *result = NULL; 1007 for (int32 i = 0; i < tokenCount; i++) { 1008 window_info *windowInfo = get_window_info(tokens[i]); 1009 if (windowInfo) { 1010 // skip hidden/special windows 1011 if (IsWindowOK(windowInfo) 1012 && (teamGroup->TeamList()->HasItem((void *)windowInfo->team))) { 1013 // this window belongs to the team! 1014 if (matches == windowIndex) { 1015 // we found it! 1016 result = windowInfo; 1017 break; 1018 } 1019 matches++; 1020 } 1021 free(windowInfo); 1022 } 1023 // else - that window probably closed. Just go to the next one. 1024 } 1025 1026 free(tokens); 1027 1028 return result; 1029 } 1030 1031 1032 int32 1033 TSwitchManager::CountWindows(int32 groupIndex, bool ) 1034 { 1035 TTeamGroup *teamGroup = (TTeamGroup *)fGroupList.ItemAt(groupIndex); 1036 if (!teamGroup) 1037 return 0; 1038 1039 int32 result = 0; 1040 1041 for (int32 i = 0; ; i++) { 1042 team_id teamID = (team_id)teamGroup->TeamList()->ItemAt(i); 1043 if (teamID == 0) 1044 break; 1045 1046 int32 count; 1047 int32 *tokens = get_token_list(teamID, &count); 1048 if (!tokens) 1049 continue; 1050 1051 for (int32 i = 0; i < count; i++) { 1052 window_info *windowInfo = get_window_info(tokens[i]); 1053 if (windowInfo) { 1054 if (IsWindowOK(windowInfo)) 1055 result++; 1056 free(windowInfo); 1057 } 1058 } 1059 free(tokens); 1060 } 1061 1062 return result; 1063 } 1064 1065 1066 void 1067 TSwitchManager::ActivateWindow(int32 windowID) 1068 { 1069 if (windowID == -1) 1070 windowID = fWindowID; 1071 1072 do_window_action(windowID, B_BRING_TO_FRONT, BRect(0, 0, 0, 0), false); 1073 } 1074 1075 1076 void 1077 TSwitchManager::SwitchWindow(team_id team, bool, bool activate) 1078 { 1079 // Find the _last_ window in the current workspace that belongs 1080 // to the group. This is the window to activate. 1081 1082 int32 index; 1083 TTeamGroup*teamGroup = FindTeam(team, &index); 1084 1085 // cycle through the windows in the active application 1086 int32 count; 1087 int32 *tokens = get_token_list(-1, &count); 1088 for (int32 i = count-1; i >= 0; i--) { 1089 window_info *windowInfo; 1090 windowInfo = get_window_info(tokens[i]); 1091 if (windowInfo && IsVisibleInCurrentWorkspace(windowInfo) 1092 && teamGroup->TeamList()->HasItem((void *)windowInfo->team)) { 1093 fWindowID = windowInfo->id; 1094 if (activate) 1095 ActivateWindow(windowInfo->id); 1096 1097 free(windowInfo); 1098 break; 1099 } 1100 free(windowInfo); 1101 } 1102 free(tokens); 1103 } 1104 1105 1106 void 1107 TSwitchManager::Unblock() 1108 { 1109 fBlock = false; 1110 fSkipUntil = system_time(); 1111 } 1112 1113 1114 int32 1115 TSwitchManager::CurrentIndex() 1116 { 1117 return fCurrentIndex; 1118 } 1119 1120 1121 int32 1122 TSwitchManager::CurrentWindow() 1123 { 1124 return fCurrentWindow; 1125 } 1126 1127 1128 int32 1129 TSwitchManager::CurrentSlot() 1130 { 1131 return fCurrentSlot; 1132 } 1133 1134 1135 BList * 1136 TSwitchManager::GroupList() 1137 { 1138 return &fGroupList; 1139 } 1140 1141 1142 // #pragma mark - 1143 1144 1145 TBox::TBox(BRect bounds, TSwitchManager *manager, TSwitcherWindow *window, 1146 TIconView *iview) 1147 : BBox(bounds, "top", B_FOLLOW_NONE, B_WILL_DRAW, B_NO_BORDER), 1148 fManager(manager), 1149 fWindow(window), 1150 fIconView(iview), 1151 fLeftScroller(false), 1152 fRightScroller(false) 1153 { 1154 } 1155 1156 1157 void 1158 TBox::AllAttached() 1159 { 1160 BRect centerRect(kCenterSlot * kSlotSize, 0, 1161 (kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1); 1162 BRect frame = fIconView->Frame(); 1163 1164 // scroll the centerRect to correct location 1165 centerRect.OffsetBy(frame.left, frame.top); 1166 1167 // switch to local coords 1168 fIconView->ConvertToParent(¢erRect); 1169 1170 fCenter = centerRect; 1171 } 1172 1173 1174 void 1175 TBox::MouseDown(BPoint where) 1176 { 1177 if (!fLeftScroller && !fRightScroller && !fUpScroller && !fDownScroller) 1178 return; 1179 1180 BRect frame = fIconView->Frame(); 1181 BRect bounds = Bounds(); 1182 1183 if (fLeftScroller) { 1184 BRect lhit(0, frame.top, frame.left, frame.bottom); 1185 if (lhit.Contains(where)) { 1186 // Want to scroll by NUMSLOTS-1 slots 1187 int32 previousIndex = fManager->CurrentIndex(); 1188 int32 previousSlot = fManager->CurrentSlot(); 1189 int32 newSlot = previousSlot - (kNumSlots - 1); 1190 if (newSlot < 0) 1191 newSlot = 0; 1192 int32 newIndex = fIconView->IndexAt(newSlot); 1193 1194 fManager->SwitchToApp(previousIndex, newIndex, false); 1195 } 1196 } 1197 1198 if (fRightScroller) { 1199 BRect rhit(frame.right, frame.top, bounds.right, frame.bottom); 1200 if (rhit.Contains(where)) { 1201 // Want to scroll by NUMSLOTS-1 slots 1202 int32 previousIndex = fManager->CurrentIndex(); 1203 int32 previousSlot = fManager->CurrentSlot(); 1204 int32 newSlot = previousSlot + (kNumSlots-1); 1205 int32 newIndex = fIconView->IndexAt(newSlot); 1206 1207 if (newIndex < 0) { 1208 // don't have a page full to scroll 1209 int32 valid = fManager->CountVisibleGroups(); 1210 newIndex = fIconView->IndexAt(valid-1); 1211 } 1212 fManager->SwitchToApp(previousIndex, newIndex, true); 1213 } 1214 } 1215 1216 frame = fWindow->WindowView()->Frame(); 1217 if (fUpScroller) { 1218 BRect hit1(frame.left - 10, frame.top, frame.left, (frame.top+frame.bottom)/2); 1219 BRect hit2(frame.right, frame.top, frame.right + 10, (frame.top+frame.bottom)/2); 1220 if (hit1.Contains(where) || hit2.Contains(where)) { 1221 // Want to scroll up 1 window 1222 fManager->CycleWindow(false, false); 1223 } 1224 } 1225 1226 if (fDownScroller) { 1227 BRect hit1(frame.left - 10, (frame.top+frame.bottom) / 2, frame.left, frame.bottom); 1228 BRect hit2(frame.right, (frame.top+frame.bottom) / 2, frame.right + 10, frame.bottom); 1229 if (hit1.Contains(where) || hit2.Contains(where)) { 1230 // Want to scroll down 1 window 1231 fManager->CycleWindow(true, false); 1232 } 1233 } 1234 } 1235 1236 1237 void 1238 TBox::Draw(BRect update) 1239 { 1240 static const int32 kChildInset = 7; 1241 static const int32 kWedge = 6; 1242 1243 BBox::Draw(update); 1244 1245 // The fancy border around the icon view 1246 1247 BRect bounds = Bounds(); 1248 float height = fIconView->Bounds().Height(); 1249 float center = (bounds.right + bounds.left) / 2; 1250 1251 BRect box(3, 3, bounds.right - 3, 3 + height + kChildInset * 2); 1252 rgb_color white = {255,255,255,255}; 1253 rgb_color standardGray = ui_color(B_PANEL_BACKGROUND_COLOR); 1254 rgb_color veryDarkGray = {128, 128, 128, 255}; 1255 rgb_color darkGray = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT); 1256 1257 // Fill the area with dark gray 1258 SetHighColor(darkGray); 1259 box.InsetBy(1,1); 1260 FillRect(box); 1261 1262 box.InsetBy(-1,-1); 1263 1264 BeginLineArray(50); 1265 1266 // The main frame around the icon view 1267 AddLine(box.LeftTop(), BPoint(center-kWedge, box.top), veryDarkGray); 1268 AddLine(BPoint(center+kWedge, box.top), box.RightTop(), veryDarkGray); 1269 1270 AddLine(box.LeftBottom(), BPoint(center-kWedge, box.bottom), veryDarkGray); 1271 AddLine(BPoint(center+kWedge, box.bottom), box.RightBottom(), veryDarkGray); 1272 AddLine(box.LeftBottom() + BPoint(1, 1), 1273 BPoint(center-kWedge, box.bottom + 1), white); 1274 AddLine(BPoint(center+kWedge, box.bottom) + BPoint(0, 1), 1275 box.RightBottom() + BPoint(1, 1), white); 1276 1277 AddLine(box.LeftTop(), box.LeftBottom(), veryDarkGray); 1278 AddLine(box.RightTop(), box.RightBottom(), veryDarkGray); 1279 AddLine(box.RightTop() + BPoint(1, 1), 1280 box.RightBottom() + BPoint(1, 1), white); 1281 1282 // downward pointing area at top of frame 1283 BPoint point(center - kWedge, box.top); 1284 AddLine(point, point + BPoint(kWedge, kWedge), veryDarkGray); 1285 AddLine(point + BPoint(kWedge, kWedge), 1286 BPoint(center+kWedge, point.y), veryDarkGray); 1287 1288 AddLine(point + BPoint(1, 0), 1289 point + BPoint(1, 0) + BPoint(kWedge - 1, kWedge - 1), white); 1290 1291 AddLine(point + BPoint(2, -1) + BPoint(kWedge - 1, kWedge - 1), 1292 BPoint(center+kWedge-1, point.y), darkGray); 1293 1294 BPoint topPoint = point; 1295 1296 // upward pointing area at bottom of frame 1297 point.y = box.bottom; 1298 point.x = center - kWedge; 1299 AddLine(point, point + BPoint(kWedge, -kWedge), veryDarkGray); 1300 AddLine(point + BPoint(kWedge, -kWedge), 1301 BPoint(center+kWedge, point.y), veryDarkGray); 1302 1303 AddLine(point + BPoint(1, 0), 1304 point + BPoint(1, 0) + BPoint(kWedge - 1, -(kWedge - 1)), white); 1305 1306 AddLine(point + BPoint(2 , 1) + BPoint(kWedge - 1, -(kWedge - 1)), 1307 BPoint(center + kWedge - 1, point.y), darkGray); 1308 1309 BPoint bottomPoint = point; 1310 1311 EndLineArray(); 1312 1313 // fill the downward pointing arrow area 1314 SetHighColor(standardGray); 1315 FillTriangle(topPoint + BPoint(2, 0), 1316 topPoint + BPoint(2, 0) + BPoint(kWedge - 2, kWedge - 2), 1317 BPoint(center + kWedge - 2, topPoint.y)); 1318 1319 // fill the upward pointing arrow area 1320 SetHighColor(standardGray); 1321 FillTriangle(bottomPoint + BPoint(2,0), 1322 bottomPoint + BPoint(2, 0) + BPoint(kWedge - 2, -(kWedge - 2)), 1323 BPoint(center + kWedge - 2, bottomPoint.y)); 1324 1325 DrawIconScrollers(false); 1326 DrawWindowScrollers(false); 1327 1328 } 1329 1330 1331 void 1332 TBox::DrawIconScrollers(bool force) 1333 { 1334 bool updateLeft = false; 1335 bool updateRight = false; 1336 rgb_color leftc; 1337 rgb_color rightc; 1338 rgb_color bkg = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT); 1339 rgb_color dark = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT); 1340 1341 BRect rect = fIconView->Bounds(); 1342 if (rect.left > (kSlotSize * kCenterSlot)) { 1343 updateLeft = true; 1344 fLeftScroller = true; 1345 leftc = dark; 1346 } else { 1347 fLeftScroller = false; 1348 if (force) { 1349 updateLeft = true; 1350 leftc = bkg; 1351 } 1352 } 1353 1354 int32 maxIndex = fManager->GroupList()->CountItems() - 1; 1355 // last_frame is in fIconView coordinate space 1356 BRect lastFrame = fIconView->FrameOf(maxIndex); 1357 1358 if (lastFrame.right > rect.right) { 1359 updateRight = true; 1360 fRightScroller = true; 1361 rightc = dark; 1362 } else { 1363 fRightScroller = false; 1364 if (force) { 1365 updateRight = true; 1366 rightc = bkg; 1367 } 1368 } 1369 1370 rect = fIconView->Frame(); 1371 if (updateLeft) { 1372 SetHighColor(leftc); 1373 BPoint pt1, pt2, pt3; 1374 pt1.x = rect.left - 5; 1375 pt1.y = floorf((rect.bottom + rect.top) / 2); 1376 pt2.x = pt3.x = pt1.x + 3; 1377 pt2.y = pt1.y - 3; 1378 pt3.y = pt1.y + 3; 1379 FillTriangle(pt1, pt2, pt3); 1380 } 1381 if (updateRight) { 1382 SetHighColor(rightc); 1383 BPoint pt1, pt2, pt3; 1384 pt1.x = rect.right + 4; 1385 pt1.y = rintf((rect.bottom + rect.top) / 2); 1386 pt2.x = pt3.x = pt1.x - 4; 1387 pt2.y = pt1.y - 4; 1388 pt3.y = pt1.y + 4; 1389 FillTriangle(pt1, pt2, pt3); 1390 } 1391 } 1392 1393 1394 void 1395 TBox::DrawWindowScrollers(bool force) 1396 { 1397 bool updateUp = false; 1398 bool updateDown = false; 1399 rgb_color upColor; 1400 rgb_color downColor; 1401 rgb_color bkg = ui_color(B_PANEL_BACKGROUND_COLOR); 1402 rgb_color dark = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT); 1403 1404 BRect rect = fWindow->WindowView()->Bounds(); 1405 if (rect.top != 0) { 1406 updateUp = true; 1407 fUpScroller = true; 1408 upColor = dark; 1409 } else { 1410 fUpScroller = false; 1411 if (force) { 1412 updateUp = true; 1413 upColor = bkg; 1414 } 1415 } 1416 1417 int32 groupIndex = fManager->CurrentIndex(); 1418 int32 maxIndex = fManager->CountWindows(groupIndex) - 1; 1419 1420 BRect lastFrame(0, 0, 0, 0); 1421 if (maxIndex >= 0) { 1422 lastFrame = fWindow->WindowView()->FrameOf(maxIndex); 1423 } 1424 if (maxIndex >= 0 && lastFrame.bottom > rect.bottom) { 1425 updateDown = true; 1426 fDownScroller = true; 1427 downColor = dark; 1428 } else { 1429 fDownScroller = false; 1430 if (force) { 1431 updateDown = true; 1432 downColor = bkg; 1433 } 1434 } 1435 1436 rect = fWindow->WindowView()->Frame(); 1437 rect.InsetBy(-3, 0); 1438 if (updateUp) { 1439 SetHighColor(upColor); 1440 BPoint pt1, pt2, pt3; 1441 pt1.x = rect.left - 6; 1442 pt1.y = rect.top + 3; 1443 pt2.y = pt3.y = pt1.y + 4; 1444 pt2.x = pt1.x - 4; 1445 pt3.x = pt1.x + 4; 1446 FillTriangle(pt1, pt2, pt3); 1447 1448 pt1.x += rect.Width() + 12; 1449 pt2.x += rect.Width() + 12; 1450 pt3.x += rect.Width() + 12; 1451 FillTriangle(pt1, pt2, pt3); 1452 } 1453 if (updateDown) { 1454 SetHighColor(downColor); 1455 BPoint pt1, pt2, pt3; 1456 pt1.x = rect.left - 6; 1457 pt1.y = rect.bottom - 3; 1458 pt2.y = pt3.y = pt1.y - 4; 1459 pt2.x = pt1.x - 4; 1460 pt3.x = pt1.x + 4; 1461 FillTriangle(pt1, pt2, pt3); 1462 1463 pt1.x += rect.Width() + 12; 1464 pt2.x += rect.Width() + 12; 1465 pt3.x += rect.Width() + 12; 1466 FillTriangle(pt1, pt2, pt3); 1467 1468 } 1469 Sync(); 1470 } 1471 1472 1473 // #pragma mark - 1474 1475 1476 TSwitcherWindow::TSwitcherWindow(BRect frame, TSwitchManager *manager) 1477 : BWindow(frame, "Twitcher", B_MODAL_WINDOW_LOOK, 1478 B_MODAL_ALL_WINDOW_FEEL, 1479 B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE, B_ALL_WORKSPACES), 1480 fManager(manager), 1481 fHairTrigger(true) 1482 { 1483 BRect rect = frame; 1484 rect.OffsetTo(B_ORIGIN); 1485 rect.InsetBy(kHorizontalMargin, 0); 1486 rect.top = kVerticalMargin; 1487 rect.bottom = rect.top + kSlotSize - 1; 1488 1489 fIconView = new TIconView(rect, manager, this); 1490 1491 rect.top = rect.bottom + (kVerticalMargin * 1 + 4); 1492 rect.InsetBy(9, 0); 1493 1494 fWindowView = new TWindowView(rect, manager, this); 1495 fWindowView->ResizeToPreferred(); 1496 1497 fTopView = new TBox(Bounds(), fManager, this, fIconView); 1498 AddChild(fTopView); 1499 1500 SetPulseRate(0); 1501 fTopView->AddChild(fIconView); 1502 fTopView->AddChild(fWindowView); 1503 } 1504 1505 1506 TSwitcherWindow::~TSwitcherWindow() 1507 { 1508 } 1509 1510 1511 void 1512 TSwitcherWindow::MessageReceived(BMessage *message) 1513 { 1514 switch (message->what) { 1515 case B_KEY_DOWN: 1516 { 1517 int32 repeats = 0; 1518 if (message->FindInt32("be:key_repeat", &repeats) == B_OK 1519 && (fSkipKeyRepeats || (repeats % 6) != 0)) 1520 break; 1521 1522 // The first actual key press let's us listening to repeated keys 1523 fSkipKeyRepeats = false; 1524 1525 uint32 rawChar; 1526 uint32 modifiers; 1527 message->FindInt32("raw_char", 0, (int32 *)&rawChar); 1528 message->FindInt32("modifiers", 0, (int32 *)&modifiers); 1529 DoKey(rawChar, modifiers); 1530 break; 1531 } 1532 1533 default: 1534 BWindow::MessageReceived(message); 1535 } 1536 } 1537 1538 1539 void 1540 TSwitcherWindow::Redraw(int32 index) 1541 { 1542 BRect frame = fIconView->FrameOf(index); 1543 frame.right = fIconView->Bounds().right; 1544 fIconView->Invalidate(frame); 1545 } 1546 1547 1548 void 1549 TSwitcherWindow::DoKey(uint32 key, uint32 modifiers) 1550 { 1551 bool forward = ((modifiers & B_SHIFT_KEY) == 0); 1552 1553 switch (key) { 1554 case B_RIGHT_ARROW: 1555 case '`': 1556 fManager->CycleApp(true, false); 1557 break; 1558 1559 case B_LEFT_ARROW: 1560 case '~': 1561 fManager->CycleApp(false, false); 1562 break; 1563 1564 case B_UP_ARROW: 1565 fManager->CycleWindow(false, false); 1566 break; 1567 1568 case B_DOWN_ARROW: 1569 fManager->CycleWindow(true, false); 1570 break; 1571 1572 case B_TAB: 1573 fManager->CycleApp(forward, false); 1574 break; 1575 1576 case B_ESCAPE: 1577 fManager->Stop(false, 0); 1578 break; 1579 1580 case B_SPACE: 1581 case B_ENTER: 1582 fManager->Stop(true, modifiers); 1583 break; 1584 1585 case 'q': 1586 case 'Q': 1587 fManager->QuitApp(); 1588 break; 1589 1590 #if _ALLOW_STICKY_ 1591 case 's': 1592 case 'S': 1593 if (fHairTrigger) { 1594 SetLook(B_TITLED_WINDOW_LOOK); 1595 fHairTrigger = false; 1596 } else { 1597 SetLook(B_MODAL_WINDOW_LOOK); 1598 fHairTrigger = true; 1599 } 1600 break; 1601 #endif 1602 } 1603 } 1604 1605 1606 bool 1607 TSwitcherWindow::QuitRequested() 1608 { 1609 ((TBarApp *) be_app)->Settings()->switcherLoc = Frame().LeftTop(); 1610 fManager->Stop(false, 0); 1611 return false; 1612 } 1613 1614 1615 void 1616 TSwitcherWindow::WindowActivated(bool state) 1617 { 1618 if (state) 1619 fManager->Unblock(); 1620 } 1621 1622 1623 void 1624 TSwitcherWindow::Update(int32 prev, int32 current, int32 previousSlot, 1625 int32 currentSlot, bool forward) 1626 { 1627 if (!IsHidden()) 1628 fIconView->Update(prev, current, previousSlot, currentSlot, forward); 1629 else 1630 fIconView->CenterOn(current); 1631 1632 fWindowView->UpdateGroup(current, 0); 1633 } 1634 1635 1636 void 1637 TSwitcherWindow::Hide() 1638 { 1639 fIconView->Hiding(); 1640 SetPulseRate(0); 1641 BWindow::Hide(); 1642 } 1643 1644 1645 void 1646 TSwitcherWindow::Show() 1647 { 1648 fHairTrigger = true; 1649 fSkipKeyRepeats = true; 1650 fIconView->Showing(); 1651 SetPulseRate(100000); 1652 SetLook(B_MODAL_WINDOW_LOOK); 1653 BWindow::Show(); 1654 } 1655 1656 1657 TBox * 1658 TSwitcherWindow::TopView() 1659 { 1660 return fTopView; 1661 } 1662 1663 1664 bool 1665 TSwitcherWindow::HairTrigger() 1666 { 1667 return fHairTrigger; 1668 } 1669 1670 1671 inline int32 1672 TSwitcherWindow::SlotOf(int32 i) 1673 { 1674 return fIconView->SlotOf(i); 1675 } 1676 1677 1678 inline TIconView * 1679 TSwitcherWindow::IconView() 1680 { 1681 return fIconView; 1682 } 1683 1684 1685 inline TWindowView * 1686 TSwitcherWindow::WindowView() 1687 { 1688 return fWindowView; 1689 } 1690 1691 1692 // #pragma mark - 1693 1694 1695 TIconView::TIconView(BRect frame, TSwitchManager *manager, TSwitcherWindow *switcherWindow) 1696 : BView(frame, "main_view", B_FOLLOW_NONE, 1697 B_WILL_DRAW | B_PULSE_NEEDED), 1698 fAutoScrolling(false), 1699 fSwitcher(switcherWindow), 1700 fManager(manager) 1701 { 1702 BRect rect(0, 0, kSlotSize - 1, kSlotSize - 1); 1703 rgb_color color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT); 1704 1705 fOffView = new BView(rect, "off_view", B_FOLLOW_NONE, B_WILL_DRAW); 1706 fOffView->SetHighColor(color); 1707 fOffBitmap = new BBitmap(rect, B_RGB32, true); 1708 fOffBitmap->AddChild(fOffView); 1709 1710 fCurrentSmall = new BBitmap(BRect(0, 0, 15, 15), kIconFormat); 1711 fCurrentLarge = new BBitmap(BRect(0, 0, 31, 31), kIconFormat); 1712 1713 SetViewColor(color); 1714 SetLowColor(color); 1715 } 1716 1717 1718 TIconView::~TIconView() 1719 { 1720 delete fCurrentSmall; 1721 delete fCurrentLarge; 1722 delete fOffBitmap; 1723 } 1724 1725 1726 void 1727 TIconView::KeyDown(const char *, int32) 1728 { 1729 } 1730 1731 1732 void 1733 TIconView::CacheIcons(TTeamGroup *teamGroup) 1734 { 1735 const BBitmap *bitmap = teamGroup->SmallIcon(); 1736 ASSERT(bitmap); 1737 fCurrentSmall->SetBits(bitmap->Bits(), bitmap->BitsLength(), 0, 1738 bitmap->ColorSpace()); 1739 1740 bitmap = teamGroup->LargeIcon(); 1741 ASSERT(bitmap); 1742 fCurrentLarge->SetBits(bitmap->Bits(), bitmap->BitsLength(), 0, 1743 bitmap->ColorSpace()); 1744 } 1745 1746 1747 void 1748 TIconView::AnimateIcon(BBitmap *startIcon, BBitmap *endIcon) 1749 { 1750 BRect centerRect(kCenterSlot*kSlotSize, 0, 1751 (kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1); 1752 BRect startIconBounds = startIcon->Bounds(); 1753 BRect bounds = Bounds(); 1754 float width = startIconBounds.Width(); 1755 int32 amount = (width < 20) ? -2 : 2; 1756 1757 // center the starting icon inside of centerRect 1758 float off = (centerRect.Width() - width) / 2; 1759 startIconBounds.OffsetTo(BPoint(off, off)); 1760 1761 // scroll the centerRect to correct location 1762 centerRect.OffsetBy(bounds.left, 0); 1763 1764 BRect destRect = fOffBitmap->Bounds(); 1765 // scroll to the centerRect location 1766 destRect.OffsetTo(centerRect.left, 0); 1767 // center the destRect inside of centerRect. 1768 off = (centerRect.Width() - destRect.Width()) / 2; 1769 destRect.OffsetBy(BPoint(off, off)); 1770 1771 fOffBitmap->Lock(); 1772 fOffView->SetDrawingMode(B_OP_ALPHA); 1773 for (int i = 0; i < 2; i++) { 1774 startIconBounds.InsetBy(amount,amount); 1775 snooze(20000); 1776 fOffView->FillRect(fOffView->Bounds()); 1777 fOffView->DrawBitmap(startIcon, startIconBounds); 1778 fOffView->Sync(); 1779 DrawBitmap(fOffBitmap, destRect); 1780 } 1781 for (int i = 0; i < 2; i++) { 1782 startIconBounds.InsetBy(amount,amount); 1783 snooze(20000); 1784 fOffView->FillRect(fOffView->Bounds()); 1785 fOffView->DrawBitmap(endIcon, startIconBounds); 1786 fOffView->Sync(); 1787 DrawBitmap(fOffBitmap, destRect); 1788 } 1789 1790 fOffView->SetDrawingMode(B_OP_COPY); 1791 fOffBitmap->Unlock(); 1792 } 1793 1794 1795 void 1796 TIconView::Update(int32, int32 current, int32 previousSlot, int32 currentSlot, 1797 bool forward) 1798 { 1799 // Animate the shrinking of the currently centered icon. 1800 AnimateIcon(fCurrentLarge, fCurrentSmall); 1801 1802 int32 nslots = abs(previousSlot - currentSlot); 1803 int32 stepSize = kScrollStep; 1804 1805 if (forward && (currentSlot < previousSlot)) { 1806 // we were at the end of the list and we just moved to the start 1807 forward = false; 1808 if (previousSlot - currentSlot > 4) 1809 stepSize *= 2; 1810 } else if (!forward && (currentSlot > previousSlot)) { 1811 // we're are moving backwards and we just hit start of list and 1812 // we wrapped to the end. 1813 forward = true; 1814 if (currentSlot - previousSlot > 4) 1815 stepSize *= 2; 1816 } 1817 1818 int32 scrollValue = forward ? stepSize : -stepSize; 1819 int32 total = 0; 1820 1821 fAutoScrolling = true; 1822 while (total < (nslots * kSlotSize)) { 1823 ScrollBy(scrollValue, 0); 1824 snooze(1000); 1825 total += stepSize; 1826 Window()->UpdateIfNeeded(); 1827 } 1828 fAutoScrolling = false; 1829 1830 TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(current); 1831 ASSERT(teamGroup); 1832 CacheIcons(teamGroup); 1833 1834 // Animate the expansion of the currently centered icon 1835 AnimateIcon(fCurrentSmall, fCurrentLarge); 1836 } 1837 1838 1839 void 1840 TIconView::CenterOn(int32 index) 1841 { 1842 BRect rect = FrameOf(index); 1843 ScrollTo(rect.left - (kCenterSlot * kSlotSize), 0); 1844 } 1845 1846 1847 int32 1848 TIconView::ItemAtPoint(BPoint point) const 1849 { 1850 float tmpPointVerticalIndex = (point.x / kSlotSize) - kCenterSlot; 1851 if (tmpPointVerticalIndex < 0) 1852 return -1; 1853 1854 int32 pointVerticalIndex = (int32)tmpPointVerticalIndex; 1855 1856 for (int32 i = 0, verticalIndex = 0; ; i++) { 1857 1858 TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(i); 1859 if (teamGroup == NULL) 1860 break; 1861 1862 if (!OKToUse(teamGroup)) 1863 continue; 1864 1865 if (verticalIndex == pointVerticalIndex) 1866 return i; 1867 1868 verticalIndex++; 1869 } 1870 return -1; 1871 } 1872 1873 1874 void 1875 TIconView::ScrollTo(BPoint where) 1876 { 1877 BView::ScrollTo(where); 1878 fSwitcher->TopView()->DrawIconScrollers(true); 1879 } 1880 1881 1882 int32 1883 TIconView::IndexAt(int32 slot) const 1884 { 1885 BList *list = fManager->GroupList(); 1886 int32 count = list->CountItems(); 1887 int32 slotIndex = 0; 1888 1889 for (int32 i = 0; i < count; i++) { 1890 TTeamGroup *teamGroup = (TTeamGroup *)list->ItemAt(i); 1891 1892 if (!OKToUse(teamGroup)) 1893 continue; 1894 1895 if (slotIndex == slot) { 1896 return i; 1897 } 1898 slotIndex++; 1899 } 1900 return -1; 1901 } 1902 1903 1904 int32 1905 TIconView::SlotOf(int32 index) const 1906 { 1907 BRect rect = FrameOf(index); 1908 return (int32)(rect.left / kSlotSize) - kCenterSlot; 1909 } 1910 1911 1912 BRect 1913 TIconView::FrameOf(int32 index) const 1914 { 1915 BList *list = fManager->GroupList(); 1916 int32 visible = kCenterSlot - 1; 1917 // first few slots in view are empty 1918 1919 TTeamGroup *teamGroup; 1920 for (int32 i = 0; i <= index; i++) { 1921 teamGroup = (TTeamGroup *)list->ItemAt(i); 1922 1923 if (!OKToUse(teamGroup)) 1924 continue; 1925 1926 visible++; 1927 } 1928 1929 return BRect(visible * kSlotSize, 0, (visible + 1) * kSlotSize - 1, kSlotSize - 1); 1930 } 1931 1932 1933 void 1934 TIconView::DrawTeams(BRect update) 1935 { 1936 int32 mainIndex = fManager->CurrentIndex(); 1937 BList *list = fManager->GroupList(); 1938 int32 count = list->CountItems(); 1939 1940 BRect rect(kCenterSlot * kSlotSize, 0, 1941 (kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1); 1942 1943 for (int32 i = 0; i < count; i++) { 1944 TTeamGroup *teamGroup = (TTeamGroup *) list->ItemAt(i); 1945 1946 if (!OKToUse(teamGroup)) 1947 continue; 1948 1949 if (rect.Intersects(update) && teamGroup) { 1950 SetDrawingMode(B_OP_OVER); 1951 1952 teamGroup->Draw(this, rect, !fAutoScrolling && (i == mainIndex)); 1953 1954 if (i == mainIndex) 1955 CacheIcons(teamGroup); 1956 1957 SetDrawingMode(B_OP_COPY); 1958 } 1959 rect.OffsetBy(kSlotSize,0); 1960 } 1961 } 1962 1963 1964 void 1965 TIconView::Draw(BRect update) 1966 { 1967 DrawTeams(update); 1968 } 1969 1970 1971 void 1972 TIconView::MouseDown(BPoint where) 1973 { 1974 int32 index = ItemAtPoint(where); 1975 if (index >= 0) { 1976 int32 previousIndex = fManager->CurrentIndex(); 1977 int32 previousSlot = fManager->CurrentSlot(); 1978 int32 currentSlot = SlotOf(index); 1979 fManager->SwitchToApp(previousIndex, index, (currentSlot > previousSlot)); 1980 } 1981 } 1982 1983 1984 void 1985 TIconView::Pulse() 1986 { 1987 uint32 modifiersKeys = modifiers(); 1988 if (fSwitcher->HairTrigger() && (modifiersKeys & B_CONTROL_KEY) == 0) { 1989 fManager->Stop(true, modifiersKeys); 1990 return; 1991 } 1992 1993 if (!fSwitcher->HairTrigger()) { 1994 uint32 buttons; 1995 BPoint point; 1996 GetMouse(&point, &buttons); 1997 if (buttons != 0) { 1998 point = ConvertToScreen(point); 1999 if (!Window()->Frame().Contains(point)) 2000 fManager->Stop(false, 0); 2001 } 2002 } 2003 } 2004 2005 2006 void 2007 TIconView::Showing() 2008 { 2009 } 2010 2011 2012 void 2013 TIconView::Hiding() 2014 { 2015 ScrollTo(B_ORIGIN); 2016 } 2017 2018 2019 // #pragma mark - 2020 2021 2022 TWindowView::TWindowView(BRect rect, TSwitchManager *manager, TSwitcherWindow *window) 2023 : BView(rect, "wlist_view", B_FOLLOW_NONE, B_WILL_DRAW | B_PULSE_NEEDED), 2024 fCurrentToken(-1), 2025 fSwitcher(window), 2026 fManager(manager) 2027 { 2028 SetFont(be_plain_font); 2029 } 2030 2031 2032 void 2033 TWindowView::AttachedToWindow() 2034 { 2035 if (Parent()) 2036 SetViewColor(Parent()->ViewColor()); 2037 else 2038 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 2039 } 2040 2041 2042 void 2043 TWindowView::ScrollTo(BPoint where) 2044 { 2045 BView::ScrollTo(where); 2046 fSwitcher->TopView()->DrawWindowScrollers(true); 2047 } 2048 2049 2050 2051 BRect 2052 TWindowView::FrameOf(int32 index) const 2053 { 2054 return BRect(0, index * fItemHeight, 100, ((index + 1) * fItemHeight) - 1); 2055 } 2056 2057 2058 const int32 kWindowScrollSteps = 3; 2059 2060 void 2061 TWindowView::GetPreferredSize(float *_width, float *_height) 2062 { 2063 font_height fh; 2064 be_plain_font->GetHeight(&fh); 2065 fItemHeight = (int32) fh.ascent + fh.descent; 2066 2067 // top & bottom margin 2068 fItemHeight = fItemHeight + 3 + 3; 2069 2070 // want fItemHeight to be divisible by kWindowScrollSteps. 2071 fItemHeight = ((((int) fItemHeight) + kWindowScrollSteps) / kWindowScrollSteps) 2072 * kWindowScrollSteps; 2073 2074 *_height = fItemHeight; 2075 2076 // leave width alone 2077 *_width = Bounds().Width(); 2078 } 2079 2080 2081 void 2082 TWindowView::ShowIndex(int32 newIndex) 2083 { 2084 // convert index to scroll location 2085 BPoint point(0, newIndex * fItemHeight); 2086 BRect bounds = Bounds(); 2087 2088 int32 groupIndex = fManager->CurrentIndex(); 2089 TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(groupIndex); 2090 if (!teamGroup) 2091 return; 2092 2093 window_info *windowInfo = fManager->WindowInfo(groupIndex, newIndex); 2094 if (windowInfo == NULL) 2095 return; 2096 2097 fCurrentToken = windowInfo->id; 2098 free(windowInfo); 2099 2100 if (bounds.top == point.y) 2101 return; 2102 2103 int32 oldIndex = (int32) (bounds.top / fItemHeight); 2104 2105 int32 stepSize = (int32) (fItemHeight / kWindowScrollSteps); 2106 int32 scrollValue = (newIndex > oldIndex) ? stepSize : -stepSize; 2107 int32 total = 0; 2108 int32 nslots = abs(newIndex - oldIndex); 2109 2110 while (total < (nslots * (int32)fItemHeight)) { 2111 ScrollBy(0, scrollValue); 2112 snooze(10000); 2113 total += stepSize; 2114 Window()->UpdateIfNeeded(); 2115 } 2116 } 2117 2118 2119 void 2120 TWindowView::Draw(BRect update) 2121 { 2122 int32 groupIndex = fManager->CurrentIndex(); 2123 TTeamGroup *teamGroup = (TTeamGroup *) fManager->GroupList()->ItemAt(groupIndex); 2124 if (!teamGroup) 2125 return; 2126 2127 BRect bounds = Bounds(); 2128 int32 windowIndex = (int32) (bounds.top / fItemHeight); 2129 BRect windowRect = bounds; 2130 2131 windowRect.top = windowIndex * fItemHeight; 2132 windowRect.bottom = ((windowIndex+1) * fItemHeight) - 1; 2133 2134 for (int32 i = 0; i < 3; i++) { 2135 if (!update.Intersects(windowRect)) { 2136 windowIndex++; 2137 windowRect.OffsetBy(0, fItemHeight); 2138 continue; 2139 } 2140 2141 // is window in current workspace? 2142 2143 bool local = true; 2144 bool minimized = false; 2145 BString title; 2146 2147 window_info *windowInfo = fManager->WindowInfo(groupIndex, windowIndex); 2148 if (windowInfo != NULL) { 2149 if (SmartStrcmp(windowInfo->name, teamGroup->Name()) != 0) 2150 title << teamGroup->Name() << ": " << windowInfo->name; 2151 else 2152 title = teamGroup->Name(); 2153 2154 int32 currentWorkspace = current_workspace(); 2155 if ((windowInfo->workspaces & (1 << currentWorkspace)) == 0) 2156 local = false; 2157 2158 minimized = windowInfo->is_mini; 2159 free(windowInfo); 2160 } else 2161 title = teamGroup->Name(); 2162 2163 if (!title.Length()) 2164 return; 2165 2166 float stringWidth = StringWidth(title.String()); 2167 float maxWidth = bounds.Width() - (14 + 5); 2168 2169 if (stringWidth > maxWidth) { 2170 // window title is too long, need to truncate 2171 TruncateString(&title, B_TRUNCATE_END, maxWidth); 2172 stringWidth = maxWidth; 2173 } 2174 2175 BPoint point((bounds.Width() - (stringWidth + 14 + 5)) / 2, windowRect.bottom - 4); 2176 BPoint p(point.x, (windowRect.top + windowRect.bottom) / 2); 2177 SetDrawingMode(B_OP_OVER); 2178 const BBitmap *bitmap = AppResSet()->FindBitmap(B_MESSAGE_TYPE, 2179 minimized ? R_WindowHiddenIcon : R_WindowShownIcon); 2180 p.y -= (bitmap->Bounds().bottom - bitmap->Bounds().top) / 2; 2181 DrawBitmap(bitmap, p); 2182 2183 if (!local) { 2184 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT)); 2185 p.x -= 8; 2186 p.y += 4; 2187 StrokeLine(p + BPoint(2, 2), p + BPoint(2, 2)); 2188 StrokeLine(p + BPoint(4, 2), p + BPoint(6, 2)); 2189 2190 StrokeLine(p + BPoint(0, 5), p + BPoint(0, 5)); 2191 StrokeLine(p + BPoint(2, 5), p + BPoint(6, 5)); 2192 2193 StrokeLine(p + BPoint(1, 8), p + BPoint(1, 8)); 2194 StrokeLine(p + BPoint(3, 8), p + BPoint(6, 8)); 2195 2196 SetHighColor(0, 0, 0); 2197 } 2198 2199 point.x += 21; 2200 MovePenTo(point); 2201 2202 DrawString(title.String()); 2203 SetDrawingMode(B_OP_COPY); 2204 2205 windowIndex++; 2206 windowRect.OffsetBy(0, fItemHeight); 2207 } 2208 } 2209 2210 2211 void 2212 TWindowView::UpdateGroup(int32 , int32 windowIndex) 2213 { 2214 ScrollTo(0, windowIndex * fItemHeight); 2215 Invalidate(Bounds()); 2216 } 2217 2218 2219 void 2220 TWindowView::Pulse() 2221 { 2222 // If selected window went away then reset to first window 2223 window_info *windowInfo = get_window_info(fCurrentToken); 2224 if (windowInfo == NULL) { 2225 Invalidate(); 2226 ShowIndex(0); 2227 } else 2228 free(windowInfo); 2229 } 2230 2231