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