1 /* 2 * Copyright 2002-2016, Haiku, Inc. All rights reserved. 3 * Copyright 2002, François Revol, revol@free.fr. 4 * This file is distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * François Revol, revol@free.fr 8 * Axel Dörfler, axeld@pinc-software.de 9 * Oliver "Madison" Kohl, 10 * Matt Madia 11 * Daniel Devine, devine@ddevnet.net 12 */ 13 14 15 #include <AboutWindow.h> 16 #include <Application.h> 17 #include <Catalog.h> 18 #include <Deskbar.h> 19 #include <Dragger.h> 20 #include <Entry.h> 21 #include <File.h> 22 #include <FindDirectory.h> 23 #include <Locale.h> 24 #include <MenuItem.h> 25 #include <Path.h> 26 #include <PopUpMenu.h> 27 #include <Roster.h> 28 #include <Screen.h> 29 #include <TextView.h> 30 #include <Window.h> 31 32 #include <ctype.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 37 #include <InterfacePrivate.h> 38 #include <ViewPrivate.h> 39 #include <WindowPrivate.h> 40 41 #undef B_TRANSLATION_CONTEXT 42 #define B_TRANSLATION_CONTEXT "Workspaces" 43 44 45 static const char* kDeskbarItemName = "workspaces"; 46 static const char* kSignature = "application/x-vnd.Be-WORK"; 47 static const char* kDeskbarSignature = "application/x-vnd.Be-TSKB"; 48 static const char* kScreenPrefletSignature = "application/x-vnd.Haiku-Screen"; 49 static const char* kOldSettingFile = "Workspace_data"; 50 static const char* kSettingsFile = "Workspaces_settings"; 51 52 static const uint32 kMsgChangeCount = 'chWC'; 53 static const uint32 kMsgToggleTitle = 'tgTt'; 54 static const uint32 kMsgToggleBorder = 'tgBd'; 55 static const uint32 kMsgToggleAutoRaise = 'tgAR'; 56 static const uint32 kMsgToggleAlwaysOnTop = 'tgAT'; 57 static const uint32 kMsgToggleLiveInDeskbar = 'tgDb'; 58 static const uint32 kMsgToggleSwitchOnWheel = 'tgWh'; 59 60 61 extern "C" _EXPORT BView* instantiate_deskbar_item(float maxWidth, 62 float maxHeight); 63 64 65 static status_t 66 OpenSettingsFile(BFile& file, int mode) 67 { 68 BPath path; 69 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path); 70 if (status != B_OK) 71 status = find_directory(B_SYSTEM_SETTINGS_DIRECTORY, &path); 72 if (status != B_OK) 73 return status; 74 75 status = path.Append(kSettingsFile); 76 if (status != B_OK) 77 return status; 78 79 status = file.SetTo(path.Path(), mode); 80 if (mode == B_READ_ONLY && status == B_ENTRY_NOT_FOUND) { 81 if (find_directory(B_SYSTEM_SETTINGS_DIRECTORY, &path) == B_OK 82 && path.Append(kSettingsFile) == B_OK) { 83 status = file.SetTo(path.Path(), mode); 84 } 85 } 86 87 return status; 88 } 89 90 91 class WorkspacesSettings { 92 public: 93 WorkspacesSettings(); 94 virtual ~WorkspacesSettings(); 95 96 BRect WindowFrame() const { return fWindowFrame; } 97 BRect ScreenFrame() const { return fScreenFrame; } 98 99 bool AutoRaising() const { return fAutoRaising; } 100 bool AlwaysOnTop() const { return fAlwaysOnTop; } 101 bool HasTitle() const { return fHasTitle; } 102 bool HasBorder() const { return fHasBorder; } 103 bool SettingsLoaded() const { return fLoaded; } 104 105 void UpdateFramesForScreen(BRect screenFrame); 106 void UpdateScreenFrame(); 107 108 void SetWindowFrame(BRect); 109 void SetAutoRaising(bool enable) { fAutoRaising = enable; } 110 void SetAlwaysOnTop(bool enable) { fAlwaysOnTop = enable; } 111 void SetHasTitle(bool enable) { fHasTitle = enable; } 112 void SetHasBorder(bool enable) { fHasBorder = enable; } 113 114 private: 115 BRect fWindowFrame; 116 BRect fScreenFrame; 117 bool fAutoRaising; 118 bool fAlwaysOnTop; 119 bool fHasTitle; 120 bool fHasBorder; 121 bool fLoaded; 122 }; 123 124 class WorkspacesView : public BView { 125 public: 126 WorkspacesView(BRect frame, bool showDragger); 127 WorkspacesView(BMessage* archive); 128 ~WorkspacesView(); 129 130 static WorkspacesView* Instantiate(BMessage* archive); 131 virtual status_t Archive(BMessage* archive, bool deep = true) const; 132 133 virtual void AttachedToWindow(); 134 virtual void DetachedFromWindow(); 135 virtual void FrameMoved(BPoint newPosition); 136 virtual void FrameResized(float newWidth, float newHeight); 137 virtual void MessageReceived(BMessage* message); 138 virtual void MouseMoved(BPoint where, uint32 transit, 139 const BMessage* dragMessage); 140 virtual void MouseDown(BPoint where); 141 142 bool SwitchOnWheel() const { return fSwitchOnWheel; } 143 void SetSwitchOnWheel(bool enable); 144 145 private: 146 void _AboutRequested(); 147 148 void _UpdateParentClipping(); 149 void _ExcludeFromParentClipping(); 150 void _CleanupParentClipping(); 151 152 friend class WorkspacesWindow; 153 154 void _LoadSettings(); 155 void _SaveSettings(); 156 157 BView* fParentWhichDrawsOnChildren; 158 BRect fCurrentFrame; 159 bool fSwitchOnWheel; 160 }; 161 162 class WorkspacesWindow : public BWindow { 163 public: 164 WorkspacesWindow(WorkspacesSettings *settings); 165 virtual ~WorkspacesWindow(); 166 167 virtual void ScreenChanged(BRect frame, color_space mode); 168 virtual void FrameMoved(BPoint origin); 169 virtual void FrameResized(float width, float height); 170 virtual void Zoom(BPoint origin, float width, float height); 171 172 virtual void MessageReceived(BMessage *msg); 173 virtual bool QuitRequested(); 174 175 void SetAutoRaise(bool enable); 176 bool IsAutoRaising() const { return fSettings->AutoRaising(); } 177 178 float GetTabHeight() { return fSettings->HasTitle() ? fTabHeight : 0; } 179 float GetBorderWidth() { return fBorderWidth; } 180 float GetScreenBorderOffset() { return 2.0 * fBorderWidth; } 181 182 private: 183 WorkspacesSettings *fSettings; 184 WorkspacesView *fWorkspacesView; 185 float fTabHeight; 186 float fBorderWidth; 187 }; 188 189 class WorkspacesApp : public BApplication { 190 public: 191 WorkspacesApp(); 192 virtual ~WorkspacesApp(); 193 194 virtual void AboutRequested(); 195 virtual void ArgvReceived(int32 argc, char **argv); 196 virtual void ReadyToRun(); 197 198 void Usage(const char *programName); 199 200 private: 201 WorkspacesWindow* fWindow; 202 }; 203 204 205 // #pragma mark - WorkspacesSettings 206 207 208 WorkspacesSettings::WorkspacesSettings() 209 : 210 fAutoRaising(false), 211 fAlwaysOnTop(false), 212 fHasTitle(true), 213 fHasBorder(true), 214 fLoaded(false) 215 { 216 UpdateScreenFrame(); 217 218 BScreen screen; 219 220 BFile file; 221 if (OpenSettingsFile(file, B_READ_ONLY) == B_OK) { 222 BMessage settings; 223 if (settings.Unflatten(&file) == B_OK) { 224 fLoaded = settings.FindRect("window", &fWindowFrame) == B_OK 225 && settings.FindRect("screen", &fScreenFrame) == B_OK; 226 settings.FindBool("auto-raise", &fAutoRaising); 227 settings.FindBool("always on top", &fAlwaysOnTop); 228 if (settings.FindBool("has title", &fHasTitle) != B_OK) 229 fHasTitle = true; 230 if (settings.FindBool("has border", &fHasBorder) != B_OK) 231 fHasBorder = true; 232 } 233 } else { 234 // try reading BeOS compatible settings 235 BPath path; 236 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { 237 path.Append(kOldSettingFile); 238 BFile file(path.Path(), B_READ_ONLY); 239 if (file.InitCheck() == B_OK 240 && file.Read(&fWindowFrame, sizeof(BRect)) == sizeof(BRect)) { 241 // we now also store the frame of the screen to know 242 // in which context the window frame has been chosen 243 BRect frame; 244 if (file.Read(&frame, sizeof(BRect)) == sizeof(BRect)) 245 fScreenFrame = frame; 246 else 247 fScreenFrame = screen.Frame(); 248 249 fLoaded = true; 250 } 251 } 252 } 253 254 if (fLoaded) { 255 // if the current screen frame is different from the one 256 // just loaded, we need to alter the window frame accordingly 257 if (fScreenFrame != screen.Frame()) 258 UpdateFramesForScreen(screen.Frame()); 259 } 260 } 261 262 263 WorkspacesSettings::~WorkspacesSettings() 264 { 265 BFile file; 266 if (OpenSettingsFile(file, B_WRITE_ONLY | B_ERASE_FILE | B_CREATE_FILE) 267 != B_OK) { 268 return; 269 } 270 271 // switch on wheel saved by view later on 272 273 BMessage settings('wksp'); 274 if (settings.AddRect("window", fWindowFrame) == B_OK 275 && settings.AddRect("screen", fScreenFrame) == B_OK 276 && settings.AddBool("auto-raise", fAutoRaising) == B_OK 277 && settings.AddBool("always on top", fAlwaysOnTop) == B_OK 278 && settings.AddBool("has title", fHasTitle) == B_OK 279 && settings.AddBool("has border", fHasBorder) == B_OK) { 280 settings.Flatten(&file); 281 } 282 } 283 284 285 void 286 WorkspacesSettings::UpdateFramesForScreen(BRect newScreenFrame) 287 { 288 // don't change the position if the screen frame hasn't changed 289 if (newScreenFrame == fScreenFrame) 290 return; 291 292 // adjust horizontal position 293 if (fWindowFrame.right > fScreenFrame.right / 2) { 294 fWindowFrame.OffsetTo(newScreenFrame.right 295 - (fScreenFrame.right - fWindowFrame.left), fWindowFrame.top); 296 } 297 298 // adjust vertical position 299 if (fWindowFrame.bottom > fScreenFrame.bottom / 2) { 300 fWindowFrame.OffsetTo(fWindowFrame.left, 301 newScreenFrame.bottom - (fScreenFrame.bottom - fWindowFrame.top)); 302 } 303 304 fScreenFrame = newScreenFrame; 305 } 306 307 308 void 309 WorkspacesSettings::UpdateScreenFrame() 310 { 311 BScreen screen; 312 fScreenFrame = screen.Frame(); 313 } 314 315 316 void 317 WorkspacesSettings::SetWindowFrame(BRect frame) 318 { 319 fWindowFrame = frame; 320 } 321 322 323 // #pragma mark - WorkspacesView 324 325 326 WorkspacesView::WorkspacesView(BRect frame, bool showDragger = true) 327 : 328 BView(frame, kDeskbarItemName, B_FOLLOW_ALL, 329 kWorkspacesViewFlag | B_FRAME_EVENTS), 330 fParentWhichDrawsOnChildren(NULL), 331 fCurrentFrame(frame), 332 fSwitchOnWheel(false) 333 { 334 _LoadSettings(); 335 336 if (showDragger) { 337 frame.OffsetTo(B_ORIGIN); 338 frame.top = frame.bottom - 7; 339 frame.left = frame.right - 7; 340 BDragger* dragger = new BDragger(frame, this, 341 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 342 AddChild(dragger); 343 } 344 } 345 346 347 WorkspacesView::WorkspacesView(BMessage* archive) 348 : 349 BView(archive), 350 fParentWhichDrawsOnChildren(NULL), 351 fCurrentFrame(Frame()), 352 fSwitchOnWheel(false) 353 { 354 _LoadSettings(); 355 356 // Just in case we are instantiated from an older archive... 357 SetFlags(Flags() | B_FRAME_EVENTS); 358 // Make sure the auto-raise feature didn't leave any artifacts - this is 359 // not a good idea to keep enabled for a replicant. 360 if (EventMask() != 0) 361 SetEventMask(0); 362 } 363 364 365 WorkspacesView::~WorkspacesView() 366 { 367 _SaveSettings(); 368 } 369 370 371 /*static*/ WorkspacesView* 372 WorkspacesView::Instantiate(BMessage* archive) 373 { 374 if (!validate_instantiation(archive, "WorkspacesView")) 375 return NULL; 376 377 return new WorkspacesView(archive); 378 } 379 380 381 status_t 382 WorkspacesView::Archive(BMessage* archive, bool deep) const 383 { 384 status_t status = BView::Archive(archive, deep); 385 if (status == B_OK) 386 status = archive->AddString("add_on", kSignature); 387 if (status == B_OK) 388 status = archive->AddString("class", "WorkspacesView"); 389 390 return status; 391 } 392 393 394 void 395 WorkspacesView::_AboutRequested() 396 { 397 BAboutWindow* window = new BAboutWindow( 398 B_TRANSLATE_SYSTEM_NAME("Workspaces"), kSignature); 399 400 const char* authors[] = { 401 "Axel Dörfler", 402 "Oliver \"Madison\" Kohl", 403 "Matt Madia", 404 "François Revol", 405 NULL 406 }; 407 408 const char* extraCopyrights[] = { 409 "2002 François Revol", 410 NULL 411 }; 412 413 const char* extraInfo = "Send windows behind using the Option key. " 414 "Move windows to front using the Control key.\n"; 415 416 window->AddCopyright(2002, "Haiku, Inc.", 417 extraCopyrights); 418 window->AddAuthors(authors); 419 window->AddExtraInfo(extraInfo); 420 421 window->Show(); 422 } 423 424 425 void 426 WorkspacesView::AttachedToWindow() 427 { 428 BView* parent = Parent(); 429 if (parent != NULL && (parent->Flags() & B_DRAW_ON_CHILDREN) != 0) { 430 fParentWhichDrawsOnChildren = parent; 431 _ExcludeFromParentClipping(); 432 } 433 } 434 435 436 void 437 WorkspacesView::DetachedFromWindow() 438 { 439 if (fParentWhichDrawsOnChildren != NULL) 440 _CleanupParentClipping(); 441 } 442 443 444 void 445 WorkspacesView::FrameMoved(BPoint newPosition) 446 { 447 _UpdateParentClipping(); 448 } 449 450 451 void 452 WorkspacesView::FrameResized(float newWidth, float newHeight) 453 { 454 _UpdateParentClipping(); 455 } 456 457 458 void 459 WorkspacesView::_UpdateParentClipping() 460 { 461 if (fParentWhichDrawsOnChildren != NULL) { 462 _CleanupParentClipping(); 463 _ExcludeFromParentClipping(); 464 fParentWhichDrawsOnChildren->Invalidate(fCurrentFrame); 465 fCurrentFrame = Frame(); 466 } 467 } 468 469 470 void 471 WorkspacesView::_ExcludeFromParentClipping() 472 { 473 // Prevent the parent view to draw over us. Do so in a way that allows 474 // restoring the parent to the previous state. 475 fParentWhichDrawsOnChildren->PushState(); 476 477 BRegion clipping(fParentWhichDrawsOnChildren->Bounds()); 478 clipping.Exclude(Frame()); 479 fParentWhichDrawsOnChildren->ConstrainClippingRegion(&clipping); 480 } 481 482 483 void 484 WorkspacesView::_CleanupParentClipping() 485 { 486 // Restore the previous parent state. NOTE: This relies on views 487 // being detached in exactly the opposite order as them being 488 // attached. Otherwise we would mess up states if a sibbling view did 489 // the same thing we did in AttachedToWindow()... 490 fParentWhichDrawsOnChildren->PopState(); 491 } 492 493 494 void 495 WorkspacesView::_LoadSettings() 496 { 497 BFile file; 498 if (OpenSettingsFile(file, B_READ_ONLY) == B_OK) { 499 BMessage settings; 500 if (settings.Unflatten(&file) == B_OK) 501 settings.FindBool("switch on wheel", &fSwitchOnWheel); 502 } 503 } 504 505 506 void 507 WorkspacesView::_SaveSettings() 508 { 509 BFile file; 510 if (OpenSettingsFile(file, B_READ_ONLY | B_CREATE_FILE) != B_OK) 511 return; 512 513 BMessage settings('wksp'); 514 settings.Unflatten(&file); 515 516 if (OpenSettingsFile(file, B_WRITE_ONLY | B_ERASE_FILE) != B_OK) 517 return; 518 519 if (settings.ReplaceBool("switch on wheel", fSwitchOnWheel) != B_OK) 520 settings.AddBool("switch on wheel", fSwitchOnWheel); 521 522 settings.Flatten(&file); 523 } 524 525 526 void 527 WorkspacesView::MessageReceived(BMessage* message) 528 { 529 switch (message->what) { 530 case B_ABOUT_REQUESTED: 531 _AboutRequested(); 532 break; 533 534 case B_MOUSE_WHEEL_CHANGED: 535 { 536 if (!fSwitchOnWheel) 537 break; 538 539 float deltaY = message->FindFloat("be:wheel_delta_y"); 540 if (deltaY > 0.1) 541 activate_workspace(current_workspace() + 1); 542 else if (deltaY < -0.1) 543 activate_workspace(current_workspace() - 1); 544 break; 545 } 546 547 case kMsgChangeCount: 548 be_roster->Launch(kScreenPrefletSignature); 549 break; 550 551 case kMsgToggleLiveInDeskbar: 552 { 553 // only actually used from the replicant itself 554 // since HasItem() locks up we just remove directly. 555 BDeskbar deskbar; 556 // we shouldn't do this here actually, but it works for now... 557 deskbar.RemoveItem(kDeskbarItemName); 558 break; 559 } 560 561 case kMsgToggleSwitchOnWheel: 562 { 563 fSwitchOnWheel = !fSwitchOnWheel; 564 break; 565 } 566 567 default: 568 BView::MessageReceived(message); 569 break; 570 } 571 } 572 573 574 void 575 WorkspacesView::MouseMoved(BPoint where, uint32 transit, 576 const BMessage* dragMessage) 577 { 578 WorkspacesWindow* window = dynamic_cast<WorkspacesWindow*>(Window()); 579 if (window == NULL || !window->IsAutoRaising()) 580 return; 581 582 // Auto-Raise 583 584 where = ConvertToScreen(where); 585 BScreen screen(window); 586 BRect screenFrame = screen.Frame(); 587 BRect windowFrame = window->Frame(); 588 float tabHeight = window->GetTabHeight(); 589 float borderWidth = window->GetBorderWidth(); 590 591 if (where.x == screenFrame.left || where.x == screenFrame.right 592 || where.y == screenFrame.top || where.y == screenFrame.bottom) { 593 // cursor is on screen edge 594 595 // Stretch frame to also accept mouse moves over the window borders 596 windowFrame.InsetBy(-borderWidth, -(tabHeight + borderWidth)); 597 598 if (windowFrame.Contains(where)) 599 window->Activate(); 600 } 601 } 602 603 604 void 605 WorkspacesView::MouseDown(BPoint where) 606 { 607 // With enabled auto-raise feature, we'll get mouse messages we don't 608 // want to handle here. 609 if (!Bounds().Contains(where)) 610 return; 611 612 int32 buttons = 0; 613 if (Window() != NULL && Window()->CurrentMessage() != NULL) 614 Window()->CurrentMessage()->FindInt32("buttons", &buttons); 615 616 if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0) 617 return; 618 619 // open context menu 620 621 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 622 menu->SetFont(be_plain_font); 623 624 // TODO: alternatively change the count here directly? 625 BMenuItem* changeItem = new BMenuItem(B_TRANSLATE("Change workspace count" 626 B_UTF8_ELLIPSIS), new BMessage(kMsgChangeCount)); 627 menu->AddItem(changeItem); 628 629 BMenuItem* switchItem = new BMenuItem(B_TRANSLATE("Switch on mouse wheel"), 630 new BMessage(kMsgToggleSwitchOnWheel)); 631 menu->AddItem(switchItem); 632 switchItem->SetMarked(fSwitchOnWheel); 633 634 WorkspacesWindow *window = dynamic_cast<WorkspacesWindow*>(Window()); 635 if (window != NULL) { 636 // inside Workspaces app 637 BMenuItem* item; 638 639 menu->AddSeparatorItem(); 640 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show window tab"), 641 new BMessage(kMsgToggleTitle))); 642 if (window->Look() == B_TITLED_WINDOW_LOOK) 643 item->SetMarked(true); 644 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show window border"), 645 new BMessage(kMsgToggleBorder))); 646 if (window->Look() == B_TITLED_WINDOW_LOOK 647 || window->Look() == B_MODAL_WINDOW_LOOK) { 648 item->SetMarked(true); 649 } 650 651 menu->AddSeparatorItem(); 652 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"), 653 new BMessage(kMsgToggleAlwaysOnTop))); 654 if (window->Feel() == B_FLOATING_ALL_WINDOW_FEEL) 655 item->SetMarked(true); 656 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Auto-raise"), 657 new BMessage(kMsgToggleAutoRaise))); 658 if (window->IsAutoRaising()) 659 item->SetMarked(true); 660 if (be_roster->IsRunning(kDeskbarSignature)) { 661 menu->AddItem(item = new BMenuItem( 662 B_TRANSLATE("Live in the Deskbar"), 663 new BMessage(kMsgToggleLiveInDeskbar))); 664 BDeskbar deskbar; 665 item->SetMarked(deskbar.HasItem(kDeskbarItemName)); 666 } 667 668 menu->AddSeparatorItem(); 669 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 670 new BMessage(B_QUIT_REQUESTED))); 671 menu->SetTargetForItems(window); 672 } else { 673 // we're replicated in some way... 674 BMenuItem* item; 675 676 menu->AddSeparatorItem(); 677 678 // check which way 679 BDragger *dragger = dynamic_cast<BDragger*>(ChildAt(0)); 680 if (dragger) { 681 // replicant 682 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove replicant"), 683 new BMessage(B_TRASH_TARGET))); 684 item->SetTarget(dragger); 685 } else { 686 // Deskbar item 687 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove replicant"), 688 new BMessage(kMsgToggleLiveInDeskbar))); 689 item->SetTarget(this); 690 } 691 } 692 693 changeItem->SetTarget(this); 694 switchItem->SetTarget(this); 695 696 ConvertToScreen(&where); 697 menu->Go(where, true, true, true); 698 } 699 700 701 void 702 WorkspacesView::SetSwitchOnWheel(bool enable) 703 { 704 if (enable == fSwitchOnWheel) 705 return; 706 707 fSwitchOnWheel = enable; 708 } 709 710 711 // #pragma mark - WorkspacesWindow 712 713 714 WorkspacesWindow::WorkspacesWindow(WorkspacesSettings *settings) 715 : 716 BWindow(settings->WindowFrame(), B_TRANSLATE_SYSTEM_NAME("Workspaces"), 717 B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 718 B_AVOID_FRONT | B_WILL_ACCEPT_FIRST_CLICK | B_CLOSE_ON_ESCAPE, 719 B_ALL_WORKSPACES), 720 fSettings(settings), 721 fWorkspacesView(NULL) 722 { 723 // Turn window decor on to grab decor widths. 724 BMessage windowSettings; 725 float borderWidth = 0; 726 727 SetLook(B_TITLED_WINDOW_LOOK); 728 if (GetDecoratorSettings(&windowSettings) == B_OK) { 729 BRect tabFrame = windowSettings.FindRect("tab frame"); 730 borderWidth = windowSettings.FindFloat("border width"); 731 fTabHeight = tabFrame.Height(); 732 fBorderWidth = borderWidth; 733 } 734 735 if (!fSettings->SettingsLoaded()) { 736 // No settings, compute a reasonable default frame. 737 // We aim for previews at 10% of actual screen size, and matching the 738 // aspect ratio. We then scale that down, until it fits the screen. 739 // Finally, we put the window on the bottom right of the screen so the 740 // auto-raise mode can be used. 741 742 BScreen screen; 743 744 float screenWidth = screen.Frame().Width(); 745 float screenHeight = screen.Frame().Height(); 746 float aspectRatio = screenWidth / screenHeight; 747 748 uint32 columns, rows; 749 BPrivate::get_workspaces_layout(&columns, &rows); 750 751 // default size of ~1/10 of screen width 752 float workspaceWidth = screenWidth / 10; 753 float workspaceHeight = workspaceWidth / aspectRatio; 754 755 float width = floor(workspaceWidth * columns); 756 float height = floor(workspaceHeight * rows); 757 758 // If you have too many workspaces to fit on the screen, shrink until 759 // they fit. 760 while (width + 2 * borderWidth > screenWidth 761 || height + 2 * borderWidth + GetTabHeight() > screenHeight) { 762 width = floor(0.95 * width); 763 height = floor(0.95 * height); 764 } 765 766 BRect frame = fSettings->ScreenFrame(); 767 frame.OffsetBy(-2.0 * borderWidth, -2.0 * borderWidth); 768 frame.left = frame.right - width; 769 frame.top = frame.bottom - height; 770 ResizeTo(frame.Width(), frame.Height()); 771 772 // Put it in bottom corner by default. 773 MoveTo(screenWidth - frame.Width() - borderWidth, 774 screenHeight - frame.Height() - borderWidth); 775 776 fSettings->SetWindowFrame(frame); 777 } 778 779 if (!fSettings->HasBorder()) 780 SetLook(B_NO_BORDER_WINDOW_LOOK); 781 else if (!fSettings->HasTitle()) 782 SetLook(B_MODAL_WINDOW_LOOK); 783 784 fWorkspacesView = new WorkspacesView(Bounds()); 785 AddChild(fWorkspacesView); 786 787 if (fSettings->AlwaysOnTop()) 788 SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 789 else 790 SetAutoRaise(fSettings->AutoRaising()); 791 } 792 793 794 WorkspacesWindow::~WorkspacesWindow() 795 { 796 delete fSettings; 797 } 798 799 800 void 801 WorkspacesWindow::ScreenChanged(BRect rect, color_space mode) 802 { 803 fSettings->UpdateFramesForScreen(rect); 804 MoveTo(fSettings->WindowFrame().LeftTop()); 805 } 806 807 808 void 809 WorkspacesWindow::FrameMoved(BPoint origin) 810 { 811 fSettings->SetWindowFrame(Frame()); 812 } 813 814 815 void 816 WorkspacesWindow::FrameResized(float width, float height) 817 { 818 if (!(modifiers() & B_SHIFT_KEY)) { 819 BWindow::FrameResized(width, height); 820 return; 821 } 822 823 uint32 columns, rows; 824 BPrivate::get_workspaces_layout(&columns, &rows); 825 826 BScreen screen; 827 float screenWidth = screen.Frame().Width(); 828 float screenHeight = screen.Frame().Height(); 829 830 float windowAspectRatio 831 = (columns * screenWidth) / (rows * screenHeight); 832 833 float newHeight = width / windowAspectRatio; 834 835 if (height != newHeight) 836 ResizeTo(width, newHeight); 837 838 fSettings->SetWindowFrame(Frame()); 839 } 840 841 842 void 843 WorkspacesWindow::Zoom(BPoint origin, float width, float height) 844 { 845 BScreen screen; 846 float screenWidth = screen.Frame().Width(); 847 float screenHeight = screen.Frame().Height(); 848 float aspectRatio = screenWidth / screenHeight; 849 850 uint32 columns, rows; 851 BPrivate::get_workspaces_layout(&columns, &rows); 852 853 float workspaceWidth = Frame().Width() / columns; 854 float workspaceHeight = workspaceWidth / aspectRatio; 855 856 width = floor(workspaceWidth * columns); 857 height = floor(workspaceHeight * rows); 858 859 while (width + 2 * GetScreenBorderOffset() > screenWidth 860 || height + 2 * GetScreenBorderOffset() + GetTabHeight() 861 > screenHeight) { 862 width = floor(0.95 * width); 863 height = floor(0.95 * height); 864 } 865 866 ResizeTo(width, height); 867 868 if (fSettings->AutoRaising()) { 869 // The auto-raising mode makes sense only if the window is positionned 870 // exactly in the bottom-right corner. If the setting is enabled, move 871 // the window there. 872 origin = screen.Frame().RightBottom(); 873 origin.x -= GetScreenBorderOffset() + width; 874 origin.y -= GetScreenBorderOffset() + height; 875 876 MoveTo(origin); 877 } 878 } 879 880 881 void 882 WorkspacesWindow::MessageReceived(BMessage *message) 883 { 884 switch (message->what) { 885 case B_SIMPLE_DATA: 886 { 887 // Drop from Tracker 888 entry_ref ref; 889 for (int i = 0; (message->FindRef("refs", i, &ref) == B_OK); i++) 890 be_roster->Launch(&ref); 891 break; 892 } 893 894 case B_ABOUT_REQUESTED: 895 PostMessage(message, ChildAt(0)); 896 break; 897 898 case kMsgToggleBorder: 899 { 900 bool enable = false; 901 if (Look() == B_NO_BORDER_WINDOW_LOOK) 902 enable = true; 903 904 if (enable) 905 if (fSettings->HasTitle()) 906 SetLook(B_TITLED_WINDOW_LOOK); 907 else 908 SetLook(B_MODAL_WINDOW_LOOK); 909 else 910 SetLook(B_NO_BORDER_WINDOW_LOOK); 911 912 fSettings->SetHasBorder(enable); 913 break; 914 } 915 916 case kMsgToggleTitle: 917 { 918 bool enable = false; 919 if (Look() == B_MODAL_WINDOW_LOOK 920 || Look() == B_NO_BORDER_WINDOW_LOOK) 921 enable = true; 922 923 if (enable) 924 SetLook(B_TITLED_WINDOW_LOOK); 925 else 926 SetLook(B_MODAL_WINDOW_LOOK); 927 928 // No matter what the setting for title, we must force the border on 929 fSettings->SetHasBorder(true); 930 fSettings->SetHasTitle(enable); 931 break; 932 } 933 934 case kMsgToggleAutoRaise: 935 SetAutoRaise(!IsAutoRaising()); 936 SetFeel(B_NORMAL_WINDOW_FEEL); 937 break; 938 939 case kMsgToggleAlwaysOnTop: 940 { 941 bool enable = false; 942 if (Feel() != B_FLOATING_ALL_WINDOW_FEEL) 943 enable = true; 944 945 if (enable) 946 SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 947 else 948 SetFeel(B_NORMAL_WINDOW_FEEL); 949 950 fSettings->SetAlwaysOnTop(enable); 951 break; 952 } 953 954 case kMsgToggleLiveInDeskbar: 955 { 956 BDeskbar deskbar; 957 if (deskbar.HasItem(kDeskbarItemName)) 958 deskbar.RemoveItem(kDeskbarItemName); 959 else { 960 fWorkspacesView->_SaveSettings(); 961 // save "switch on wheel" setting for replicant to load 962 entry_ref ref; 963 be_roster->FindApp(kSignature, &ref); 964 deskbar.AddItem(&ref); 965 } 966 break; 967 } 968 969 default: 970 BWindow::MessageReceived(message); 971 break; 972 } 973 } 974 975 976 bool 977 WorkspacesWindow::QuitRequested() 978 { 979 be_app->PostMessage(B_QUIT_REQUESTED); 980 return true; 981 } 982 983 984 void 985 WorkspacesWindow::SetAutoRaise(bool enable) 986 { 987 fSettings->SetAutoRaising(enable); 988 989 if (enable) 990 ChildAt(0)->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 991 else 992 ChildAt(0)->SetEventMask(0); 993 } 994 995 996 // #pragma mark - WorkspacesApp 997 998 999 WorkspacesApp::WorkspacesApp() 1000 : BApplication(kSignature) 1001 { 1002 fWindow = new WorkspacesWindow(new WorkspacesSettings()); 1003 } 1004 1005 1006 WorkspacesApp::~WorkspacesApp() 1007 { 1008 } 1009 1010 1011 void 1012 WorkspacesApp::AboutRequested() 1013 { 1014 fWindow->PostMessage(B_ABOUT_REQUESTED); 1015 } 1016 1017 1018 void 1019 WorkspacesApp::Usage(const char *programName) 1020 { 1021 printf(B_TRANSLATE("Usage: %s [options] [workspace]\n" 1022 "where \"options\" are:\n" 1023 " --notitle\t\ttitle bar removed, border and resize kept\n" 1024 " --noborder\t\ttitle, border, and resize removed\n" 1025 " --avoidfocus\t\tprevents the window from being the target of " 1026 "keyboard events\n" 1027 " --alwaysontop\t\tkeeps window on top\n" 1028 " --notmovable\t\twindow can't be moved around\n" 1029 " --autoraise\t\tauto-raise the workspace window when it's at the " 1030 "screen edge\n" 1031 " --help\t\tdisplay this help and exit\n" 1032 "and \"workspace\" is the number of the Workspace to which to switch " 1033 "(0-31)\n"), 1034 programName); 1035 1036 // quit only if we aren't running already 1037 if (IsLaunching()) 1038 Quit(); 1039 } 1040 1041 1042 void 1043 WorkspacesApp::ArgvReceived(int32 argc, char **argv) 1044 { 1045 for (int i = 1; i < argc; i++) { 1046 if (argv[i][0] == '-' && argv[i][1] == '-') { 1047 // evaluate --arguments 1048 if (!strcmp(argv[i], "--notitle")) 1049 fWindow->SetLook(B_MODAL_WINDOW_LOOK); 1050 else if (!strcmp(argv[i], "--noborder")) 1051 fWindow->SetLook(B_NO_BORDER_WINDOW_LOOK); 1052 else if (!strcmp(argv[i], "--avoidfocus")) 1053 fWindow->SetFlags(fWindow->Flags() | B_AVOID_FOCUS); 1054 else if (!strcmp(argv[i], "--notmovable")) 1055 fWindow->SetFlags(fWindow->Flags() | B_NOT_MOVABLE); 1056 else if (!strcmp(argv[i], "--alwaysontop")) 1057 fWindow->SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 1058 else if (!strcmp(argv[i], "--autoraise")) 1059 fWindow->SetAutoRaise(true); 1060 else { 1061 const char *programName = strrchr(argv[0], '/'); 1062 programName = programName ? programName + 1 : argv[0]; 1063 1064 Usage(programName); 1065 } 1066 } else if (isdigit(*argv[i])) { 1067 // check for a numeric arg, if not already given 1068 activate_workspace(atoi(argv[i])); 1069 1070 // if the app is running, don't quit 1071 // but if it isn't, cancel the complete run, so it doesn't 1072 // open any window 1073 if (IsLaunching()) 1074 Quit(); 1075 } else if (!strcmp(argv[i], "-")) { 1076 activate_workspace(current_workspace() - 1); 1077 1078 if (IsLaunching()) 1079 Quit(); 1080 } else if (!strcmp(argv[i], "+")) { 1081 activate_workspace(current_workspace() + 1); 1082 1083 if (IsLaunching()) 1084 Quit(); 1085 } else { 1086 // some unknown arguments were specified 1087 fprintf(stderr, B_TRANSLATE("Invalid argument: %s\n"), argv[i]); 1088 1089 if (IsLaunching()) 1090 Quit(); 1091 } 1092 } 1093 } 1094 1095 1096 void 1097 WorkspacesApp::ReadyToRun() 1098 { 1099 fWindow->Show(); 1100 } 1101 1102 1103 // #pragma mark - 1104 1105 1106 BView* 1107 instantiate_deskbar_item(float maxWidth, float maxHeight) 1108 { 1109 // Calculate the correct size of the Deskbar replicant first 1110 1111 BScreen screen; 1112 float screenWidth = screen.Frame().Width(); 1113 float screenHeight = screen.Frame().Height(); 1114 float aspectRatio = screenWidth / screenHeight; 1115 uint32 columns, rows; 1116 BPrivate::get_workspaces_layout(&columns, &rows); 1117 1118 // We use 1px for the top and left borders (shown as double) 1119 // and divide the remainder equally. However, we keep in mind 1120 // that the actual width and height of each workspace is smaller 1121 // by 1px, because of bottom/right borders (shown as single). 1122 // When calculating workspace width, we must ensure that the assumed 1123 // actual workspace height is not negative. Zero is OK. 1124 1125 float height = maxHeight; 1126 float rowHeight = floor((height - 1) / rows); 1127 if (rowHeight < 1) 1128 rowHeight = 1; 1129 1130 float columnWidth = floor((rowHeight - 1) * aspectRatio) + 1; 1131 1132 float width = columnWidth * columns + 1; 1133 if (width > maxWidth) 1134 width = maxWidth; 1135 1136 return new WorkspacesView(BRect (0, 0, width - 1, height - 1), false); 1137 } 1138 1139 1140 // #pragma mark - 1141 1142 1143 int 1144 main(int argc, char **argv) 1145 { 1146 WorkspacesApp app; 1147 app.Run(); 1148 1149 return 0; 1150 } 1151