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