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
OpenSettingsFile(BFile & file,int mode)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
WindowFrame() const96 BRect WindowFrame() const { return fWindowFrame; }
ScreenFrame() const97 BRect ScreenFrame() const { return fScreenFrame; }
98
AutoRaising() const99 bool AutoRaising() const { return fAutoRaising; }
AlwaysOnTop() const100 bool AlwaysOnTop() const { return fAlwaysOnTop; }
HasTitle() const101 bool HasTitle() const { return fHasTitle; }
HasBorder() const102 bool HasBorder() const { return fHasBorder; }
SettingsLoaded() const103 bool SettingsLoaded() const { return fLoaded; }
104
105 void UpdateFramesForScreen(BRect screenFrame);
106 void UpdateScreenFrame();
107
108 void SetWindowFrame(BRect);
SetAutoRaising(bool enable)109 void SetAutoRaising(bool enable) { fAutoRaising = enable; }
SetAlwaysOnTop(bool enable)110 void SetAlwaysOnTop(bool enable) { fAlwaysOnTop = enable; }
SetHasTitle(bool enable)111 void SetHasTitle(bool enable) { fHasTitle = enable; }
SetHasBorder(bool 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
SwitchOnWheel() const142 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);
IsAutoRaising() const176 bool IsAutoRaising() const { return fSettings->AutoRaising(); }
177
GetTabHeight()178 float GetTabHeight() { return fSettings->HasTitle() ? fTabHeight : 0; }
GetBorderWidth()179 float GetBorderWidth() { return fBorderWidth; }
GetScreenBorderOffset()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
WorkspacesSettings()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
~WorkspacesSettings()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
UpdateFramesForScreen(BRect newScreenFrame)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
UpdateScreenFrame()309 WorkspacesSettings::UpdateScreenFrame()
310 {
311 BScreen screen;
312 fScreenFrame = screen.Frame();
313 }
314
315
316 void
SetWindowFrame(BRect frame)317 WorkspacesSettings::SetWindowFrame(BRect frame)
318 {
319 fWindowFrame = frame;
320 }
321
322
323 // #pragma mark - WorkspacesView
324
325
WorkspacesView(BRect frame,bool showDragger=true)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
WorkspacesView(BMessage * archive)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
~WorkspacesView()365 WorkspacesView::~WorkspacesView()
366 {
367 _SaveSettings();
368 }
369
370
371 /*static*/ WorkspacesView*
Instantiate(BMessage * archive)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
Archive(BMessage * archive,bool deep) const382 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
_AboutRequested()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
AttachedToWindow()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
DetachedFromWindow()437 WorkspacesView::DetachedFromWindow()
438 {
439 if (fParentWhichDrawsOnChildren != NULL)
440 _CleanupParentClipping();
441 }
442
443
444 void
FrameMoved(BPoint newPosition)445 WorkspacesView::FrameMoved(BPoint newPosition)
446 {
447 _UpdateParentClipping();
448 }
449
450
451 void
FrameResized(float newWidth,float newHeight)452 WorkspacesView::FrameResized(float newWidth, float newHeight)
453 {
454 _UpdateParentClipping();
455 }
456
457
458 void
_UpdateParentClipping()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
_ExcludeFromParentClipping()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
_CleanupParentClipping()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
_LoadSettings()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
_SaveSettings()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
MessageReceived(BMessage * message)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
MouseMoved(BPoint where,uint32 transit,const BMessage * dragMessage)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
MouseDown(BPoint where)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
SetSwitchOnWheel(bool enable)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
WorkspacesWindow(WorkspacesSettings * settings)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
~WorkspacesWindow()794 WorkspacesWindow::~WorkspacesWindow()
795 {
796 delete fSettings;
797 }
798
799
800 void
ScreenChanged(BRect rect,color_space mode)801 WorkspacesWindow::ScreenChanged(BRect rect, color_space mode)
802 {
803 fSettings->UpdateFramesForScreen(rect);
804 MoveTo(fSettings->WindowFrame().LeftTop());
805 }
806
807
808 void
FrameMoved(BPoint origin)809 WorkspacesWindow::FrameMoved(BPoint origin)
810 {
811 fSettings->SetWindowFrame(Frame());
812 }
813
814
815 void
FrameResized(float width,float height)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
Zoom(BPoint origin,float width,float height)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
MessageReceived(BMessage * message)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
QuitRequested()977 WorkspacesWindow::QuitRequested()
978 {
979 be_app->PostMessage(B_QUIT_REQUESTED);
980 return true;
981 }
982
983
984 void
SetAutoRaise(bool enable)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
WorkspacesApp()999 WorkspacesApp::WorkspacesApp()
1000 : BApplication(kSignature)
1001 {
1002 fWindow = new WorkspacesWindow(new WorkspacesSettings());
1003 }
1004
1005
~WorkspacesApp()1006 WorkspacesApp::~WorkspacesApp()
1007 {
1008 }
1009
1010
1011 void
AboutRequested()1012 WorkspacesApp::AboutRequested()
1013 {
1014 fWindow->PostMessage(B_ABOUT_REQUESTED);
1015 }
1016
1017
1018 void
Usage(const char * programName)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
ArgvReceived(int32 argc,char ** argv)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
ReadyToRun()1097 WorkspacesApp::ReadyToRun()
1098 {
1099 fWindow->Show();
1100 }
1101
1102
1103 // #pragma mark -
1104
1105
1106 BView*
instantiate_deskbar_item(float maxWidth,float maxHeight)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
main(int argc,char ** argv)1144 main(int argc, char **argv)
1145 {
1146 WorkspacesApp app;
1147 app.Run();
1148
1149 return 0;
1150 }
1151