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