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
45static const char* kDeskbarItemName = "workspaces";
46static const char* kSignature = "application/x-vnd.Be-WORK";
47static const char* kDeskbarSignature = "application/x-vnd.Be-TSKB";
48static const char* kScreenPrefletSignature = "application/x-vnd.Haiku-Screen";
49static const char* kOldSettingFile = "Workspace_data";
50static const char* kSettingsFile = "Workspaces_settings";
51
52static const uint32 kMsgChangeCount = 'chWC';
53static const uint32 kMsgToggleTitle = 'tgTt';
54static const uint32 kMsgToggleBorder = 'tgBd';
55static const uint32 kMsgToggleAutoRaise = 'tgAR';
56static const uint32 kMsgToggleAlwaysOnTop = 'tgAT';
57static const uint32 kMsgToggleLiveInDeskbar = 'tgDb';
58static const uint32 kMsgToggleSwitchOnWheel = 'tgWh';
59
60
61extern "C" _EXPORT BView* instantiate_deskbar_item(float maxWidth,
62	float maxHeight);
63
64
65static status_t
66OpenSettingsFile(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
91class WorkspacesSettings {
92	public:
93		WorkspacesSettings();
94		virtual ~WorkspacesSettings();
95
96		BRect WindowFrame() const { return fWindowFrame; }
97		BRect ScreenFrame() const { return fScreenFrame; }
98
99		bool AutoRaising() const { return fAutoRaising; }
100		bool AlwaysOnTop() const { return fAlwaysOnTop; }
101		bool HasTitle() const { return fHasTitle; }
102		bool HasBorder() const { return fHasBorder; }
103		bool SettingsLoaded() const { return fLoaded; }
104
105		void UpdateFramesForScreen(BRect screenFrame);
106		void UpdateScreenFrame();
107
108		void SetWindowFrame(BRect);
109		void SetAutoRaising(bool enable) { fAutoRaising = enable; }
110		void SetAlwaysOnTop(bool enable) { fAlwaysOnTop = enable; }
111		void SetHasTitle(bool enable) { fHasTitle = 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
124class 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
142		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
162class 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);
176		bool IsAutoRaising() const { return fSettings->AutoRaising(); }
177
178		float GetTabHeight() { return fSettings->HasTitle() ? fTabHeight : 0; }
179		float GetBorderWidth() { return fBorderWidth; }
180		float GetScreenBorderOffset() { return 2.0 * fBorderWidth; }
181
182	private:
183		WorkspacesSettings *fSettings;
184		WorkspacesView *fWorkspacesView;
185		float	fTabHeight;
186		float	fBorderWidth;
187};
188
189class 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
208WorkspacesSettings::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
263WorkspacesSettings::~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
285void
286WorkspacesSettings::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
308void
309WorkspacesSettings::UpdateScreenFrame()
310{
311	BScreen screen;
312	fScreenFrame = screen.Frame();
313}
314
315
316void
317WorkspacesSettings::SetWindowFrame(BRect frame)
318{
319	fWindowFrame = frame;
320}
321
322
323//	#pragma mark - WorkspacesView
324
325
326WorkspacesView::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
347WorkspacesView::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
365WorkspacesView::~WorkspacesView()
366{
367	_SaveSettings();
368}
369
370
371/*static*/ WorkspacesView*
372WorkspacesView::Instantiate(BMessage* archive)
373{
374	if (!validate_instantiation(archive, "WorkspacesView"))
375		return NULL;
376
377	return new WorkspacesView(archive);
378}
379
380
381status_t
382WorkspacesView::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
394void
395WorkspacesView::_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
425void
426WorkspacesView::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
436void
437WorkspacesView::DetachedFromWindow()
438{
439	if (fParentWhichDrawsOnChildren != NULL)
440		_CleanupParentClipping();
441}
442
443
444void
445WorkspacesView::FrameMoved(BPoint newPosition)
446{
447	_UpdateParentClipping();
448}
449
450
451void
452WorkspacesView::FrameResized(float newWidth, float newHeight)
453{
454	_UpdateParentClipping();
455}
456
457
458void
459WorkspacesView::_UpdateParentClipping()
460{
461	if (fParentWhichDrawsOnChildren != NULL) {
462		_CleanupParentClipping();
463		_ExcludeFromParentClipping();
464		fParentWhichDrawsOnChildren->Invalidate(fCurrentFrame);
465		fCurrentFrame = Frame();
466	}
467}
468
469
470void
471WorkspacesView::_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
483void
484WorkspacesView::_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
494void
495WorkspacesView::_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
506void
507WorkspacesView::_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
526void
527WorkspacesView::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
574void
575WorkspacesView::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
604void
605WorkspacesView::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
701void
702WorkspacesView::SetSwitchOnWheel(bool enable)
703{
704	if (enable == fSwitchOnWheel)
705		return;
706
707	fSwitchOnWheel = enable;
708}
709
710
711//	#pragma mark - WorkspacesWindow
712
713
714WorkspacesWindow::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
794WorkspacesWindow::~WorkspacesWindow()
795{
796	delete fSettings;
797}
798
799
800void
801WorkspacesWindow::ScreenChanged(BRect rect, color_space mode)
802{
803	fSettings->UpdateFramesForScreen(rect);
804	MoveTo(fSettings->WindowFrame().LeftTop());
805}
806
807
808void
809WorkspacesWindow::FrameMoved(BPoint origin)
810{
811	fSettings->SetWindowFrame(Frame());
812}
813
814
815void
816WorkspacesWindow::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
842void
843WorkspacesWindow::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
881void
882WorkspacesWindow::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
976bool
977WorkspacesWindow::QuitRequested()
978{
979	be_app->PostMessage(B_QUIT_REQUESTED);
980	return true;
981}
982
983
984void
985WorkspacesWindow::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
999WorkspacesApp::WorkspacesApp()
1000	: BApplication(kSignature)
1001{
1002	fWindow = new WorkspacesWindow(new WorkspacesSettings());
1003}
1004
1005
1006WorkspacesApp::~WorkspacesApp()
1007{
1008}
1009
1010
1011void
1012WorkspacesApp::AboutRequested()
1013{
1014	fWindow->PostMessage(B_ABOUT_REQUESTED);
1015}
1016
1017
1018void
1019WorkspacesApp::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
1042void
1043WorkspacesApp::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
1096void
1097WorkspacesApp::ReadyToRun()
1098{
1099	fWindow->Show();
1100}
1101
1102
1103//	#pragma mark -
1104
1105
1106BView*
1107instantiate_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
1143int
1144main(int argc, char **argv)
1145{
1146	WorkspacesApp app;
1147	app.Run();
1148
1149	return 0;
1150}
1151