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