1 /* 2 * Copyright 2002-2009, 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 <Alert.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_COMMON_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_COMMON_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 BString text = B_TRANSLATE("Workspaces\n" 404 "written by %1, and %2.\n\n" 405 "Copyright %3, Haiku.\n\n" 406 "Send windows behind using the Option key. " 407 "Move windows to front using the Control key.\n"); 408 text.ReplaceFirst("%1", "François Revol, Axel Dörfler"); 409 text.ReplaceFirst("%2", "Matt Madia"); 410 text.ReplaceFirst("%3", "2002-2008"); 411 412 BAlert *alert = new BAlert("about", text.String(), B_TRANSLATE("OK")); 413 BTextView *view = alert->TextView(); 414 BFont font; 415 416 view->SetStylable(true); 417 418 view->GetFont(&font); 419 font.SetSize(18); 420 font.SetFace(B_BOLD_FACE); 421 view->SetFontAndColor(0, 10, &font); 422 423 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 424 alert->Go(); 425 } 426 427 428 void 429 WorkspacesView::AttachedToWindow() 430 { 431 BView* parent = Parent(); 432 if (parent != NULL && (parent->Flags() & B_DRAW_ON_CHILDREN) != 0) { 433 fParentWhichDrawsOnChildren = parent; 434 _ExcludeFromParentClipping(); 435 } 436 } 437 438 439 void 440 WorkspacesView::DetachedFromWindow() 441 { 442 if (fParentWhichDrawsOnChildren != NULL) 443 _CleanupParentClipping(); 444 } 445 446 447 void 448 WorkspacesView::FrameMoved(BPoint newPosition) 449 { 450 _UpdateParentClipping(); 451 } 452 453 454 void 455 WorkspacesView::FrameResized(float newWidth, float newHeight) 456 { 457 _UpdateParentClipping(); 458 } 459 460 461 void 462 WorkspacesView::_UpdateParentClipping() 463 { 464 if (fParentWhichDrawsOnChildren != NULL) { 465 _CleanupParentClipping(); 466 _ExcludeFromParentClipping(); 467 fParentWhichDrawsOnChildren->Invalidate(fCurrentFrame); 468 fCurrentFrame = Frame(); 469 } 470 } 471 472 473 void 474 WorkspacesView::_ExcludeFromParentClipping() 475 { 476 // Prevent the parent view to draw over us. Do so in a way that allows 477 // restoring the parent to the previous state. 478 fParentWhichDrawsOnChildren->PushState(); 479 480 BRegion clipping(fParentWhichDrawsOnChildren->Bounds()); 481 clipping.Exclude(Frame()); 482 fParentWhichDrawsOnChildren->ConstrainClippingRegion(&clipping); 483 } 484 485 486 void 487 WorkspacesView::_CleanupParentClipping() 488 { 489 // Restore the previous parent state. NOTE: This relies on views 490 // being detached in exactly the opposite order as them being 491 // attached. Otherwise we would mess up states if a sibbling view did 492 // the same thing we did in AttachedToWindow()... 493 fParentWhichDrawsOnChildren->PopState(); 494 } 495 496 497 void 498 WorkspacesView::MessageReceived(BMessage* message) 499 { 500 switch (message->what) { 501 case B_ABOUT_REQUESTED: 502 _AboutRequested(); 503 break; 504 505 case kMsgChangeCount: 506 be_roster->Launch(kScreenPrefletSignature); 507 break; 508 509 case kMsgToggleLiveInDeskbar: 510 { 511 // only actually used from the replicant itself 512 // since HasItem() locks up we just remove directly. 513 BDeskbar deskbar; 514 // we shouldn't do this here actually, but it works for now... 515 deskbar.RemoveItem (kDeskbarItemName); 516 break; 517 } 518 519 default: 520 BView::MessageReceived(message); 521 break; 522 } 523 } 524 525 526 void 527 WorkspacesView::MouseMoved(BPoint where, uint32 transit, 528 const BMessage* dragMessage) 529 { 530 WorkspacesWindow* window = dynamic_cast<WorkspacesWindow*>(Window()); 531 if (window == NULL || !window->IsAutoRaising()) 532 return; 533 534 // Auto-Raise 535 536 where = ConvertToScreen(where); 537 BScreen screen(window); 538 BRect frame = screen.Frame(); 539 if (where.x == frame.left || where.x == frame.right 540 || where.y == frame.top || where.y == frame.bottom) { 541 // cursor is on screen edge 542 if (window->Frame().Contains(where)) 543 window->Activate(); 544 } 545 } 546 547 548 void 549 WorkspacesView::MouseDown(BPoint where) 550 { 551 // With enabled auto-raise feature, we'll get mouse messages we don't 552 // want to handle here. 553 if (!Bounds().Contains(where)) 554 return; 555 556 int32 buttons = 0; 557 if (Window() != NULL && Window()->CurrentMessage() != NULL) 558 Window()->CurrentMessage()->FindInt32("buttons", &buttons); 559 560 if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0) 561 return; 562 563 // open context menu 564 565 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 566 menu->SetFont(be_plain_font); 567 568 // TODO: alternatively change the count here directly? 569 BMenuItem* changeItem = new BMenuItem(B_TRANSLATE("Change workspace count" 570 B_UTF8_ELLIPSIS), new BMessage(kMsgChangeCount)); 571 menu->AddItem(changeItem); 572 573 WorkspacesWindow* window = dynamic_cast<WorkspacesWindow*>(Window()); 574 if (window != NULL) { 575 // inside Workspaces app 576 BMenuItem* item; 577 578 menu->AddSeparatorItem(); 579 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show window tab"), 580 new BMessage(kMsgToggleTitle))); 581 if (window->Look() == B_TITLED_WINDOW_LOOK) 582 item->SetMarked(true); 583 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show window border"), 584 new BMessage(kMsgToggleBorder))); 585 if (window->Look() == B_TITLED_WINDOW_LOOK 586 || window->Look() == B_MODAL_WINDOW_LOOK) 587 item->SetMarked(true); 588 589 menu->AddSeparatorItem(); 590 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"), 591 new BMessage(kMsgToggleAlwaysOnTop))); 592 if (window->Feel() == B_FLOATING_ALL_WINDOW_FEEL) 593 item->SetMarked(true); 594 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Auto-raise"), 595 new BMessage(kMsgToggleAutoRaise))); 596 if (window->IsAutoRaising()) 597 item->SetMarked(true); 598 if (be_roster->IsRunning(kDeskbarSignature)) { 599 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Live in the Deskbar"), 600 new BMessage(kMsgToggleLiveInDeskbar))); 601 BDeskbar deskbar; 602 item->SetMarked(deskbar.HasItem(kDeskbarItemName)); 603 } 604 605 menu->AddSeparatorItem(); 606 menu->AddItem(new BMenuItem(B_TRANSLATE("About Workspaces" 607 B_UTF8_ELLIPSIS), new BMessage(B_ABOUT_REQUESTED))); 608 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 609 new BMessage(B_QUIT_REQUESTED))); 610 menu->SetTargetForItems(window); 611 } else { 612 // we're replicated in some way... 613 BMenuItem* item; 614 615 menu->AddSeparatorItem(); 616 617 // check which way 618 BDragger *dragger = dynamic_cast<BDragger*>(ChildAt(0)); 619 if (dragger) { 620 // replicant 621 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove replicant"), 622 new BMessage(B_TRASH_TARGET))); 623 item->SetTarget(dragger); 624 } else { 625 // Deskbar item 626 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove replicant"), 627 new BMessage(kMsgToggleLiveInDeskbar))); 628 item->SetTarget(this); 629 } 630 } 631 632 changeItem->SetTarget(this); 633 ConvertToScreen(&where); 634 menu->Go(where, true, true, true); 635 } 636 637 638 // #pragma mark - 639 640 641 WorkspacesWindow::WorkspacesWindow(WorkspacesSettings *settings) 642 : 643 BWindow(settings->WindowFrame(), B_TRANSLATE_SYSTEM_NAME("Workspaces"), 644 B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 645 B_AVOID_FRONT | B_WILL_ACCEPT_FIRST_CLICK, B_ALL_WORKSPACES), 646 fSettings(settings), 647 fAutoRaising(false) 648 { 649 AddChild(new WorkspacesView(Bounds())); 650 651 if (!fSettings->HasBorder()) 652 SetLook(B_NO_BORDER_WINDOW_LOOK); 653 else if (!fSettings->HasTitle()) 654 SetLook(B_MODAL_WINDOW_LOOK); 655 656 if (fSettings->AlwaysOnTop()) 657 SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 658 else 659 SetAutoRaise(fSettings->AutoRaising()); 660 } 661 662 663 WorkspacesWindow::~WorkspacesWindow() 664 { 665 delete fSettings; 666 } 667 668 669 void 670 WorkspacesWindow::ScreenChanged(BRect rect, color_space mode) 671 { 672 fSettings->UpdateFramesForScreen(rect); 673 MoveTo(fSettings->WindowFrame().LeftTop()); 674 } 675 676 677 void 678 WorkspacesWindow::FrameMoved(BPoint origin) 679 { 680 fSettings->SetWindowFrame(Frame()); 681 } 682 683 684 void 685 WorkspacesWindow::FrameResized(float width, float height) 686 { 687 if (!modifiers() & B_SHIFT_KEY) { 688 BWindow::FrameResized(width, height); 689 return; 690 } 691 692 uint32 columns, rows; 693 BPrivate::get_workspaces_layout(&columns, &rows); 694 695 BScreen screen; 696 float screenWidth = screen.Frame().Width(); 697 float screenHeight = screen.Frame().Height(); 698 699 float windowAspectRatio 700 = (columns * screenWidth) / (rows * screenHeight); 701 702 float newHeight = width / windowAspectRatio; 703 704 if (height != newHeight) 705 ResizeTo(width, newHeight); 706 707 fSettings->SetWindowFrame(Frame()); 708 } 709 710 711 void 712 WorkspacesWindow::Zoom(BPoint origin, float width, float height) 713 { 714 BScreen screen; 715 float screenWidth = screen.Frame().Width(); 716 float screenHeight = screen.Frame().Height(); 717 float aspectRatio = screenWidth / screenHeight; 718 719 uint32 columns, rows; 720 BPrivate::get_workspaces_layout(&columns, &rows); 721 722 float workspaceWidth = screenWidth / 10; 723 float workspaceHeight = workspaceWidth / aspectRatio; 724 725 width = floor(workspaceWidth * columns); 726 height = floor(workspaceHeight * rows); 727 728 float tabHeight = Frame().top - DecoratorFrame().top; 729 730 while (width + 2 * kScreenBorderOffset > screenWidth 731 || height + 2 * kScreenBorderOffset + tabHeight > screenHeight) { 732 width = floor(0.95 * width); 733 height = floor(0.95 * height); 734 } 735 736 ResizeTo(width, height); 737 738 origin = screen.Frame().RightBottom(); 739 origin.x -= kScreenBorderOffset + width; 740 origin.y -= kScreenBorderOffset + height; 741 742 MoveTo(origin); 743 } 744 745 746 void 747 WorkspacesWindow::MessageReceived(BMessage *message) 748 { 749 switch (message->what) { 750 case B_SIMPLE_DATA: 751 { 752 // Drop from Tracker 753 entry_ref ref; 754 for (int i = 0; (message->FindRef("refs", i, &ref) == B_OK); i++) 755 be_roster->Launch(&ref); 756 break; 757 } 758 759 case B_ABOUT_REQUESTED: 760 PostMessage(message, ChildAt(0)); 761 break; 762 763 case kMsgToggleBorder: 764 { 765 bool enable = false; 766 if (Look() == B_NO_BORDER_WINDOW_LOOK) 767 enable = true; 768 769 if (enable) 770 if (fSettings->HasTitle()) 771 SetLook(B_TITLED_WINDOW_LOOK); 772 else 773 SetLook(B_MODAL_WINDOW_LOOK); 774 else 775 SetLook(B_NO_BORDER_WINDOW_LOOK); 776 777 fSettings->SetHasBorder(enable); 778 break; 779 } 780 781 case kMsgToggleTitle: 782 { 783 bool enable = false; 784 if (Look() == B_MODAL_WINDOW_LOOK 785 || Look() == B_NO_BORDER_WINDOW_LOOK) 786 enable = true; 787 788 if (enable) 789 SetLook(B_TITLED_WINDOW_LOOK); 790 else 791 SetLook(B_MODAL_WINDOW_LOOK); 792 793 // No matter what the setting for title, 794 // we must force the border on 795 fSettings->SetHasBorder(true); 796 fSettings->SetHasTitle(enable); 797 break; 798 } 799 800 case kMsgToggleAutoRaise: 801 SetAutoRaise(!IsAutoRaising()); 802 SetFeel(B_NORMAL_WINDOW_FEEL); 803 break; 804 805 case kMsgToggleAlwaysOnTop: 806 { 807 bool enable = false; 808 if (Feel() != B_FLOATING_ALL_WINDOW_FEEL) 809 enable = true; 810 811 if (enable) 812 SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 813 else 814 SetFeel(B_NORMAL_WINDOW_FEEL); 815 816 fSettings->SetAlwaysOnTop(enable); 817 break; 818 } 819 820 case kMsgToggleLiveInDeskbar: 821 { 822 BDeskbar deskbar; 823 if (deskbar.HasItem (kDeskbarItemName)) 824 deskbar.RemoveItem (kDeskbarItemName); 825 else { 826 entry_ref ref; 827 be_roster->FindApp(kSignature, &ref); 828 deskbar.AddItem(&ref); 829 } 830 break; 831 } 832 833 default: 834 BWindow::MessageReceived(message); 835 break; 836 } 837 } 838 839 840 bool 841 WorkspacesWindow::QuitRequested() 842 { 843 be_app->PostMessage(B_QUIT_REQUESTED); 844 return true; 845 } 846 847 848 void 849 WorkspacesWindow::SetAutoRaise(bool enable) 850 { 851 if (enable == fAutoRaising) 852 return; 853 854 fAutoRaising = enable; 855 fSettings->SetAutoRaising(enable); 856 857 if (enable) 858 ChildAt(0)->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 859 else 860 ChildAt(0)->SetEventMask(0); 861 } 862 863 864 // #pragma mark - 865 866 867 WorkspacesApp::WorkspacesApp() 868 : BApplication(kSignature) 869 { 870 fWindow = new WorkspacesWindow(new WorkspacesSettings()); 871 } 872 873 874 WorkspacesApp::~WorkspacesApp() 875 { 876 } 877 878 879 void 880 WorkspacesApp::AboutRequested() 881 { 882 fWindow->PostMessage(B_ABOUT_REQUESTED); 883 } 884 885 886 void 887 WorkspacesApp::Usage(const char *programName) 888 { 889 printf(B_TRANSLATE("Usage: %s [options] [workspace]\n" 890 "where \"options\" are:\n" 891 " --notitle\t\ttitle bar removed, border and resize kept\n" 892 " --noborder\t\ttitle, border, and resize removed\n" 893 " --avoidfocus\t\tprevents the window from being the target of " 894 "keyboard events\n" 895 " --alwaysontop\t\tkeeps window on top\n" 896 " --notmovable\t\twindow can't be moved around\n" 897 " --autoraise\t\tauto-raise the workspace window when it's at the " 898 "screen edge\n" 899 " --help\t\tdisplay this help and exit\n" 900 "and \"workspace\" is the number of the Workspace to which to switch " 901 "(0-31)\n"), 902 programName); 903 904 // quit only if we aren't running already 905 if (IsLaunching()) 906 Quit(); 907 } 908 909 910 void 911 WorkspacesApp::ArgvReceived(int32 argc, char **argv) 912 { 913 for (int i = 1; i < argc; i++) { 914 if (argv[i][0] == '-' && argv[i][1] == '-') { 915 // evaluate --arguments 916 if (!strcmp(argv[i], "--notitle")) 917 fWindow->SetLook(B_MODAL_WINDOW_LOOK); 918 else if (!strcmp(argv[i], "--noborder")) 919 fWindow->SetLook(B_NO_BORDER_WINDOW_LOOK); 920 else if (!strcmp(argv[i], "--avoidfocus")) 921 fWindow->SetFlags(fWindow->Flags() | B_AVOID_FOCUS); 922 else if (!strcmp(argv[i], "--notmovable")) 923 fWindow->SetFlags(fWindow->Flags() | B_NOT_MOVABLE); 924 else if (!strcmp(argv[i], "--alwaysontop")) 925 fWindow->SetFeel(B_FLOATING_ALL_WINDOW_FEEL); 926 else if (!strcmp(argv[i], "--autoraise")) 927 fWindow->SetAutoRaise(true); 928 else { 929 const char *programName = strrchr(argv[0], '/'); 930 programName = programName ? programName + 1 : argv[0]; 931 932 Usage(programName); 933 } 934 } else if (isdigit(*argv[i])) { 935 // check for a numeric arg, if not already given 936 activate_workspace(atoi(argv[i])); 937 938 // if the app is running, don't quit 939 // but if it isn't, cancel the complete run, so it doesn't 940 // open any window 941 if (IsLaunching()) 942 Quit(); 943 } else if (!strcmp(argv[i], "-")) { 944 activate_workspace(current_workspace() - 1); 945 946 if (IsLaunching()) 947 Quit(); 948 } else if (!strcmp(argv[i], "+")) { 949 activate_workspace(current_workspace() + 1); 950 951 if (IsLaunching()) 952 Quit(); 953 } else { 954 // some unknown arguments were specified 955 fprintf(stderr, B_TRANSLATE("Invalid argument: %s\n"), argv[i]); 956 957 if (IsLaunching()) 958 Quit(); 959 } 960 } 961 } 962 963 964 BView* instantiate_deskbar_item() 965 { 966 // Calculate the correct size of the Deskbar replicant first 967 968 BScreen screen; 969 float screenWidth = screen.Frame().Width(); 970 float screenHeight = screen.Frame().Height(); 971 float aspectRatio = screenWidth / screenHeight; 972 uint32 columns, rows; 973 BPrivate::get_workspaces_layout(&columns, &rows); 974 975 // ╔═╤═╕ A Deskbar replicant can be 16px tall and 129px wide at most. 976 // ║ │ │ We use 1px for the top and left borders (shown as double) 977 // ╟─┼─┤ and divide the remainder equally. However, we keep in mind 978 // ║ │ │ that the actual width and height of each workspace is smaller 979 // ╙─┴─┘ by 1px, because of bottom/right borders (shown as single). 980 // When calculating workspace width, we must ensure that the assumed 981 // actual workspace height is not negative. Zero is OK. 982 983 float height = 16; 984 float rowHeight = floor((height - 1) / rows); 985 if (rowHeight < 1) 986 rowHeight = 1; 987 988 float columnWidth = floor((rowHeight - 1) * aspectRatio) + 1; 989 990 float width = columnWidth * columns + 1; 991 if (width > 129) 992 width = 129; 993 994 return new WorkspacesView(BRect (0, 0, width - 1, height - 1), false); 995 } 996 997 998 void 999 WorkspacesApp::ReadyToRun() 1000 { 1001 fWindow->Show(); 1002 } 1003 1004 1005 // #pragma mark - 1006 1007 1008 int 1009 main(int argc, char **argv) 1010 { 1011 WorkspacesApp app; 1012 app.Run(); 1013 1014 return 0; 1015 } 1016