1/* 2 * Copyright 2010-2014 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * John Scipione, jscipione@gmail.com 7 * Clemens Zeidler, haiku@clemens-zeidler.de 8 */ 9 10 11#include "StackAndTile.h" 12 13#include <Debug.h> 14 15#include "StackAndTilePrivate.h" 16 17#include "Desktop.h" 18#include "SATWindow.h" 19#include "Tiling.h" 20#include "Window.h" 21 22 23static const int32 kRightOptionKey = 0x67; 24static const int32 kTabKey = 0x26; 25static const int32 kPageUpKey = 0x21; 26static const int32 kPageDownKey = 0x36; 27static const int32 kLeftArrowKey = 0x61; 28static const int32 kUpArrowKey = 0x57; 29static const int32 kRightArrowKey = 0x63; 30static const int32 kDownArrowKey = 0x62; 31 32static const int32 kModifiers = B_SHIFT_KEY | B_COMMAND_KEY 33 | B_CONTROL_KEY | B_OPTION_KEY | B_MENU_KEY; 34 35 36using namespace std; 37 38 39// #pragma mark - StackAndTile 40 41 42StackAndTile::StackAndTile() 43 : 44 fDesktop(NULL), 45 fSATKeyPressed(false), 46 fCurrentSATWindow(NULL) 47{ 48 49} 50 51 52StackAndTile::~StackAndTile() 53{ 54 55} 56 57 58int32 59StackAndTile::Identifier() 60{ 61 return BPrivate::kMagicSATIdentifier; 62} 63 64 65void 66StackAndTile::ListenerRegistered(Desktop* desktop) 67{ 68 fDesktop = desktop; 69 70 WindowList& windows = desktop->AllWindows(); 71 for (Window *window = windows.FirstWindow(); window != NULL; 72 window = window->NextWindow(kAllWindowList)) 73 WindowAdded(window); 74} 75 76 77void 78StackAndTile::ListenerUnregistered() 79{ 80 for (SATWindowMap::iterator it = fSATWindowMap.begin(); 81 it != fSATWindowMap.end(); it++) { 82 SATWindow* satWindow = it->second; 83 delete satWindow; 84 } 85 fSATWindowMap.clear(); 86} 87 88 89bool 90StackAndTile::HandleMessage(Window* sender, BPrivate::LinkReceiver& link, 91 BPrivate::LinkSender& reply) 92{ 93 if (sender == NULL) 94 return _HandleMessage(link, reply); 95 96 SATWindow* satWindow = GetSATWindow(sender); 97 if (!satWindow) 98 return false; 99 100 return satWindow->HandleMessage(satWindow, link, reply); 101} 102 103 104void 105StackAndTile::WindowAdded(Window* window) 106{ 107 SATWindow* satWindow = new (std::nothrow)SATWindow(this, window); 108 if (!satWindow) 109 return; 110 111 ASSERT(fSATWindowMap.find(window) == fSATWindowMap.end()); 112 fSATWindowMap[window] = satWindow; 113} 114 115 116void 117StackAndTile::WindowRemoved(Window* window) 118{ 119 STRACE_SAT("StackAndTile::WindowRemoved %s\n", window->Title()); 120 121 SATWindowMap::iterator it = fSATWindowMap.find(window); 122 if (it == fSATWindowMap.end()) 123 return; 124 125 SATWindow* satWindow = it->second; 126 // delete SATWindow 127 delete satWindow; 128 fSATWindowMap.erase(it); 129} 130 131 132bool 133StackAndTile::KeyPressed(uint32 what, int32 key, int32 modifiers) 134{ 135 if (what == B_MODIFIERS_CHANGED 136 || (what == B_UNMAPPED_KEY_DOWN && key == kRightOptionKey) 137 || (what == B_UNMAPPED_KEY_UP && key == kRightOptionKey)) { 138 // switch to and from stacking and snapping mode 139 bool wasPressed = fSATKeyPressed; 140 fSATKeyPressed = (what == B_MODIFIERS_CHANGED 141 && (modifiers & kModifiers) == B_OPTION_KEY) 142 || (what == B_UNMAPPED_KEY_DOWN && key == kRightOptionKey); 143 if (wasPressed && !fSATKeyPressed) 144 _StopSAT(); 145 if (!wasPressed && fSATKeyPressed) 146 _StartSAT(); 147 } 148 149 if (!SATKeyPressed() || what != B_KEY_DOWN) 150 return false; 151 152 SATWindow* frontWindow = GetSATWindow(fDesktop->FocusWindow()); 153 SATGroup* currentGroup = _GetSATGroup(frontWindow); 154 155 switch (key) { 156 case kLeftArrowKey: 157 case kRightArrowKey: 158 case kTabKey: 159 { 160 // go to previous or next window tab in current window group 161 if (currentGroup == NULL) 162 return false; 163 164 int32 groupSize = currentGroup->CountItems(); 165 if (groupSize <= 1) 166 return false; 167 168 for (int32 i = 0; i < groupSize; i++) { 169 SATWindow* targetWindow = currentGroup->WindowAt(i); 170 if (targetWindow == frontWindow) { 171 if (key == kLeftArrowKey 172 || (key == kTabKey && (modifiers & B_SHIFT_KEY) != 0)) { 173 // Go to previous window tab (wrap around) 174 int32 previousIndex = i > 0 ? i - 1 : groupSize - 1; 175 targetWindow = currentGroup->WindowAt(previousIndex); 176 } else { 177 // Go to next window tab (wrap around) 178 int32 nextIndex = i < groupSize - 1 ? i + 1 : 0; 179 targetWindow = currentGroup->WindowAt(nextIndex); 180 } 181 182 _ActivateWindow(targetWindow); 183 return true; 184 } 185 } 186 break; 187 } 188 189 case kUpArrowKey: 190 case kPageUpKey: 191 { 192 // go to previous window group 193 GroupIterator groups(this, fDesktop); 194 groups.SetCurrentGroup(currentGroup); 195 SATGroup* backmostGroup = NULL; 196 197 while (true) { 198 SATGroup* group = groups.NextGroup(); 199 if (group == NULL || group == currentGroup) 200 break; 201 else if (group->CountItems() < 1) 202 continue; 203 204 if (currentGroup == NULL) { 205 SATWindow* activeWindow = group->ActiveWindow(); 206 if (activeWindow != NULL) 207 _ActivateWindow(activeWindow); 208 else 209 _ActivateWindow(group->WindowAt(0)); 210 211 return true; 212 } 213 backmostGroup = group; 214 } 215 if (backmostGroup != NULL && backmostGroup != currentGroup) { 216 SATWindow* activeWindow = backmostGroup->ActiveWindow(); 217 if (activeWindow != NULL) 218 _ActivateWindow(activeWindow); 219 else 220 _ActivateWindow(backmostGroup->WindowAt(0)); 221 222 return true; 223 } 224 225 break; 226 } 227 228 case kDownArrowKey: 229 case kPageDownKey: 230 { 231 // go to next window group 232 GroupIterator groups(this, fDesktop); 233 groups.SetCurrentGroup(currentGroup); 234 235 while (true) { 236 SATGroup* group = groups.NextGroup(); 237 if (group == NULL || group == currentGroup) 238 break; 239 else if (group->CountItems() < 1) 240 continue; 241 242 SATWindow* activeWindow = group->ActiveWindow(); 243 if (activeWindow != NULL) 244 _ActivateWindow(activeWindow); 245 else 246 _ActivateWindow(group->WindowAt(0)); 247 248 if (currentGroup != NULL && frontWindow != NULL) { 249 Window* window = frontWindow->GetWindow(); 250 fDesktop->SendWindowBehind(window); 251 WindowSentBehind(window, NULL); 252 } 253 return true; 254 } 255 break; 256 } 257 } 258 259 return false; 260} 261 262 263void 264StackAndTile::MouseDown(Window* window, BMessage* message, const BPoint& where) 265{ 266 SATWindow* satWindow = GetSATWindow(window); 267 if (!satWindow || !satWindow->GetDecorator()) 268 return; 269 270 // fCurrentSATWindow is not zero if e.g. the secondary and the primary 271 // mouse button are pressed at the same time 272 if ((message->FindInt32("buttons") & B_PRIMARY_MOUSE_BUTTON) == 0 || 273 fCurrentSATWindow != NULL) 274 return; 275 276 // we are only interested in single clicks 277 if (message->FindInt32("clicks") == 2) 278 return; 279 280 int32 tab; 281 switch (satWindow->GetDecorator()->RegionAt(where, tab)) { 282 case Decorator::REGION_TAB: 283 case Decorator::REGION_LEFT_BORDER: 284 case Decorator::REGION_RIGHT_BORDER: 285 case Decorator::REGION_TOP_BORDER: 286 case Decorator::REGION_BOTTOM_BORDER: 287 case Decorator::REGION_LEFT_TOP_CORNER: 288 case Decorator::REGION_LEFT_BOTTOM_CORNER: 289 case Decorator::REGION_RIGHT_TOP_CORNER: 290 case Decorator::REGION_RIGHT_BOTTOM_CORNER: 291 break; 292 293 default: 294 return; 295 } 296 297 ASSERT(fCurrentSATWindow == NULL); 298 fCurrentSATWindow = satWindow; 299 300 if (!SATKeyPressed()) 301 return; 302 303 _StartSAT(); 304} 305 306 307void 308StackAndTile::MouseUp(Window* window, BMessage* message, const BPoint& where) 309{ 310 if (fSATKeyPressed) 311 _StopSAT(); 312 313 fCurrentSATWindow = NULL; 314} 315 316 317void 318StackAndTile::WindowMoved(Window* window) 319{ 320 SATWindow* satWindow = GetSATWindow(window); 321 if (satWindow == NULL) 322 return; 323 324 if (SATKeyPressed() && fCurrentSATWindow) 325 satWindow->FindSnappingCandidates(); 326 else 327 satWindow->DoGroupLayout(); 328} 329 330 331void 332StackAndTile::WindowResized(Window* window) 333{ 334 SATWindow* satWindow = GetSATWindow(window); 335 if (satWindow == NULL) 336 return; 337 satWindow->Resized(); 338 339 if (SATKeyPressed() && fCurrentSATWindow) 340 satWindow->FindSnappingCandidates(); 341 else 342 satWindow->DoGroupLayout(); 343} 344 345 346void 347StackAndTile::WindowActivated(Window* window) 348{ 349 SATWindow* satWindow = GetSATWindow(window); 350 if (satWindow == NULL) 351 return; 352 353 _ActivateWindow(satWindow); 354} 355 356 357void 358StackAndTile::WindowSentBehind(Window* window, Window* behindOf) 359{ 360 SATWindow* satWindow = GetSATWindow(window); 361 if (satWindow == NULL) 362 return; 363 364 SATGroup* group = satWindow->GetGroup(); 365 if (group == NULL) 366 return; 367 368 Desktop* desktop = satWindow->GetWindow()->Desktop(); 369 if (desktop == NULL) 370 return; 371 372 const WindowAreaList& areaList = group->GetAreaList(); 373 for (int32 i = 0; i < areaList.CountItems(); i++) { 374 WindowArea* area = areaList.ItemAt(i); 375 SATWindow* topWindow = area->TopWindow(); 376 if (topWindow == NULL || topWindow == satWindow) 377 continue; 378 desktop->SendWindowBehind(topWindow->GetWindow(), behindOf); 379 } 380} 381 382 383void 384StackAndTile::WindowWorkspacesChanged(Window* window, uint32 workspaces) 385{ 386 SATWindow* satWindow = GetSATWindow(window); 387 if (satWindow == NULL) 388 return; 389 390 SATGroup* group = satWindow->GetGroup(); 391 if (group == NULL) 392 return; 393 394 Desktop* desktop = satWindow->GetWindow()->Desktop(); 395 if (desktop == NULL) 396 return; 397 398 const WindowAreaList& areaList = group->GetAreaList(); 399 for (int32 i = 0; i < areaList.CountItems(); i++) { 400 WindowArea* area = areaList.ItemAt(i); 401 if (area->WindowList().HasItem(satWindow)) 402 continue; 403 SATWindow* topWindow = area->TopWindow(); 404 desktop->SetWindowWorkspaces(topWindow->GetWindow(), workspaces); 405 } 406} 407 408 409void 410StackAndTile::WindowHidden(Window* window, bool fromMinimize) 411{ 412 SATWindow* satWindow = GetSATWindow(window); 413 if (satWindow == NULL) 414 return; 415 416 SATGroup* group = satWindow->GetGroup(); 417 if (group == NULL) 418 return; 419 420 if (fromMinimize == false && group->CountItems() > 1) 421 group->RemoveWindow(satWindow, false); 422} 423 424 425void 426StackAndTile::WindowMinimized(Window* window, bool minimize) 427{ 428 SATWindow* satWindow = GetSATWindow(window); 429 if (satWindow == NULL) 430 return; 431 432 SATGroup* group = satWindow->GetGroup(); 433 if (group == NULL) 434 return; 435 436 Desktop* desktop = satWindow->GetWindow()->Desktop(); 437 if (desktop == NULL) 438 return; 439 440 for (int i = 0; i < group->CountItems(); i++) { 441 SATWindow* listWindow = group->WindowAt(i); 442 if (listWindow != satWindow) 443 listWindow->GetWindow()->ServerWindow()->NotifyMinimize(minimize); 444 } 445} 446 447 448void 449StackAndTile::WindowTabLocationChanged(Window* window, float location, 450 bool isShifting) 451{ 452 453} 454 455 456void 457StackAndTile::SizeLimitsChanged(Window* window, int32 minWidth, int32 maxWidth, 458 int32 minHeight, int32 maxHeight) 459{ 460 SATWindow* satWindow = GetSATWindow(window); 461 if (!satWindow) 462 return; 463 satWindow->SetOriginalSizeLimits(minWidth, maxWidth, minHeight, maxHeight); 464 465 // trigger a relayout 466 WindowMoved(window); 467} 468 469 470void 471StackAndTile::WindowLookChanged(Window* window, window_look look) 472{ 473 SATWindow* satWindow = GetSATWindow(window); 474 if (!satWindow) 475 return; 476 satWindow->WindowLookChanged(look); 477} 478 479 480void 481StackAndTile::WindowFeelChanged(Window* window, window_feel feel) 482{ 483 // check if it is still a compatible feel 484 if (feel == B_NORMAL_WINDOW_FEEL) 485 return; 486 SATWindow* satWindow = GetSATWindow(window); 487 if (satWindow == NULL) 488 return; 489 490 SATGroup* group = satWindow->GetGroup(); 491 if (group == NULL) 492 return; 493 494 if (group->CountItems() > 1) 495 group->RemoveWindow(satWindow, false); 496} 497 498 499bool 500StackAndTile::SetDecoratorSettings(Window* window, const BMessage& settings) 501{ 502 SATWindow* satWindow = GetSATWindow(window); 503 if (!satWindow) 504 return false; 505 506 return satWindow->SetSettings(settings); 507} 508 509 510void 511StackAndTile::GetDecoratorSettings(Window* window, BMessage& settings) 512{ 513 SATWindow* satWindow = GetSATWindow(window); 514 if (!satWindow) 515 return; 516 517 satWindow->GetSettings(settings); 518} 519 520 521SATWindow* 522StackAndTile::GetSATWindow(Window* window) 523{ 524 if (window == NULL) 525 return NULL; 526 527 SATWindowMap::const_iterator it = fSATWindowMap.find( 528 window); 529 if (it != fSATWindowMap.end()) 530 return it->second; 531 532 // TODO fix race condition with WindowAdded this method is called before 533 // WindowAdded and a SATWindow is created twice! 534 return NULL; 535 536 // If we don't know this window, memory allocation might has been failed 537 // previously. Try to add the window now. 538 SATWindow* satWindow = new (std::nothrow)SATWindow(this, window); 539 if (satWindow) 540 fSATWindowMap[window] = satWindow; 541 542 return satWindow; 543} 544 545 546SATWindow* 547StackAndTile::FindSATWindow(uint64 id) 548{ 549 for (SATWindowMap::const_iterator it = fSATWindowMap.begin(); 550 it != fSATWindowMap.end(); it++) { 551 SATWindow* window = it->second; 552 if (window->Id() == id) 553 return window; 554 } 555 556 return NULL; 557} 558 559 560// #pragma mark - StackAndTile private methods 561 562 563void 564StackAndTile::_StartSAT() 565{ 566 STRACE_SAT("StackAndTile::_StartSAT()\n"); 567 if (!fCurrentSATWindow) 568 return; 569 570 // Remove window from the group. 571 SATGroup* group = fCurrentSATWindow->GetGroup(); 572 if (group == NULL) 573 return; 574 575 group->RemoveWindow(fCurrentSATWindow, false); 576 // Bring window to the front. (in focus follow mouse this is not 577 // automatically the case) 578 _ActivateWindow(fCurrentSATWindow); 579 580 fCurrentSATWindow->FindSnappingCandidates(); 581} 582 583 584void 585StackAndTile::_StopSAT() 586{ 587 STRACE_SAT("StackAndTile::_StopSAT()\n"); 588 if (!fCurrentSATWindow) 589 return; 590 if (fCurrentSATWindow->JoinCandidates()) 591 _ActivateWindow(fCurrentSATWindow); 592} 593 594 595void 596StackAndTile::_ActivateWindow(SATWindow* satWindow) 597{ 598 if (satWindow == NULL) 599 return; 600 601 SATGroup* group = satWindow->GetGroup(); 602 if (group == NULL) 603 return; 604 605 Desktop* desktop = satWindow->GetWindow()->Desktop(); 606 if (desktop == NULL) 607 return; 608 609 WindowArea* area = satWindow->GetWindowArea(); 610 if (area == NULL) 611 return; 612 613 area->MoveToTopLayer(satWindow); 614 615 // save the active window of the current group 616 SATWindow* frontWindow = GetSATWindow(fDesktop->FocusWindow()); 617 SATGroup* currentGroup = _GetSATGroup(frontWindow); 618 if (currentGroup != NULL && currentGroup != group && frontWindow != NULL) 619 currentGroup->SetActiveWindow(frontWindow); 620 else 621 group->SetActiveWindow(satWindow); 622 623 const WindowAreaList& areas = group->GetAreaList(); 624 int32 areasCount = areas.CountItems(); 625 for (int32 i = 0; i < areasCount; i++) { 626 WindowArea* currentArea = areas.ItemAt(i); 627 if (currentArea == area) 628 continue; 629 630 desktop->ActivateWindow(currentArea->TopWindow()->GetWindow()); 631 } 632 633 desktop->ActivateWindow(satWindow->GetWindow()); 634} 635 636 637bool 638StackAndTile::_HandleMessage(BPrivate::LinkReceiver& link, 639 BPrivate::LinkSender& reply) 640{ 641 int32 what; 642 link.Read<int32>(&what); 643 644 switch (what) { 645 case BPrivate::kSaveAllGroups: 646 { 647 BMessage allGroupsArchive; 648 GroupIterator groups(this, fDesktop); 649 while (true) { 650 SATGroup* group = groups.NextGroup(); 651 if (group == NULL) 652 break; 653 if (group->CountItems() <= 1) 654 continue; 655 BMessage groupArchive; 656 if (group->ArchiveGroup(groupArchive) != B_OK) 657 continue; 658 allGroupsArchive.AddMessage("group", &groupArchive); 659 } 660 int32 size = allGroupsArchive.FlattenedSize(); 661 char buffer[size]; 662 if (allGroupsArchive.Flatten(buffer, size) == B_OK) { 663 reply.StartMessage(B_OK); 664 reply.Attach<int32>(size); 665 reply.Attach(buffer, size); 666 } else 667 reply.StartMessage(B_ERROR); 668 reply.Flush(); 669 break; 670 } 671 672 case BPrivate::kRestoreGroup: 673 { 674 int32 size; 675 if (link.Read<int32>(&size) == B_OK) { 676 char buffer[size]; 677 BMessage group; 678 if (link.Read(buffer, size) == B_OK 679 && group.Unflatten(buffer) == B_OK) { 680 status_t status = SATGroup::RestoreGroup(group, this); 681 reply.StartMessage(status); 682 reply.Flush(); 683 } 684 } 685 break; 686 } 687 688 default: 689 return false; 690 } 691 692 return true; 693} 694 695 696SATGroup* 697StackAndTile::_GetSATGroup(SATWindow* window) 698{ 699 if (window == NULL) 700 return NULL; 701 702 SATGroup* group = window->GetGroup(); 703 if (group == NULL) 704 return NULL; 705 706 if (group->CountItems() < 1) 707 return NULL; 708 709 return group; 710} 711 712 713// #pragma mark - GroupIterator 714 715 716GroupIterator::GroupIterator(StackAndTile* sat, Desktop* desktop) 717 : 718 fStackAndTile(sat), 719 fDesktop(desktop), 720 fCurrentGroup(NULL) 721{ 722 RewindToFront(); 723} 724 725 726void 727GroupIterator::RewindToFront() 728{ 729 fCurrentWindow = fDesktop->CurrentWindows().LastWindow(); 730} 731 732 733SATGroup* 734GroupIterator::NextGroup() 735{ 736 SATGroup* group = NULL; 737 do { 738 Window* window = fCurrentWindow; 739 if (window == NULL) { 740 group = NULL; 741 break; 742 } 743 fCurrentWindow = fCurrentWindow->PreviousWindow( 744 fCurrentWindow->CurrentWorkspace()); 745 if (window->IsHidden() 746 || strcmp(window->Title(), "Deskbar") == 0 747 || strcmp(window->Title(), "Desktop") == 0) { 748 continue; 749 } 750 751 SATWindow* satWindow = fStackAndTile->GetSATWindow(window); 752 group = satWindow->GetGroup(); 753 } while (group == NULL || fCurrentGroup == group); 754 755 fCurrentGroup = group; 756 return fCurrentGroup; 757} 758 759 760// #pragma mark - WindowIterator 761 762 763WindowIterator::WindowIterator(SATGroup* group, bool reverseLayerOrder) 764 : 765 fGroup(group), 766 fReverseLayerOrder(reverseLayerOrder) 767{ 768 if (fReverseLayerOrder) 769 _ReverseRewind(); 770 else 771 Rewind(); 772} 773 774 775void 776WindowIterator::Rewind() 777{ 778 fAreaIndex = 0; 779 fWindowIndex = 0; 780 fCurrentArea = fGroup->GetAreaList().ItemAt(fAreaIndex); 781} 782 783 784SATWindow* 785WindowIterator::NextWindow() 786{ 787 if (fReverseLayerOrder) 788 return _ReverseNextWindow(); 789 790 if (fWindowIndex == fCurrentArea->LayerOrder().CountItems()) { 791 fAreaIndex++; 792 fWindowIndex = 0; 793 fCurrentArea = fGroup->GetAreaList().ItemAt(fAreaIndex); 794 if (!fCurrentArea) 795 return NULL; 796 } 797 SATWindow* window = fCurrentArea->LayerOrder().ItemAt(fWindowIndex); 798 fWindowIndex++; 799 return window; 800} 801 802 803// #pragma mark - WindowIterator private methods 804 805 806SATWindow* 807WindowIterator::_ReverseNextWindow() 808{ 809 if (fWindowIndex < 0) { 810 fAreaIndex++; 811 fCurrentArea = fGroup->GetAreaList().ItemAt(fAreaIndex); 812 if (!fCurrentArea) 813 return NULL; 814 fWindowIndex = fCurrentArea->LayerOrder().CountItems() - 1; 815 } 816 SATWindow* window = fCurrentArea->LayerOrder().ItemAt(fWindowIndex); 817 fWindowIndex--; 818 return window; 819} 820 821 822void 823WindowIterator::_ReverseRewind() 824{ 825 Rewind(); 826 if (fCurrentArea) 827 fWindowIndex = fCurrentArea->LayerOrder().CountItems() - 1; 828} 829