Workspaces.cpp revision badcd9dbc731ee07918d19de5d834eb99d563e65
1/*
2 * Copyright 2002-2008, Haiku, Inc.
3 * Copyright 2002, Fran��ois Revol, revol@free.fr.
4 * This file is distributed under the terms of the MIT License.
5 *
6 * Authors:
7 *		Fran��ois Revol, revol@free.fr
8 *		Axel D��rfler, axeld@pinc-software.de
9 *		Oliver "Madison" Kohl,
10 *		Matt Madia
11 */
12
13
14#include <Alert.h>
15#include <Application.h>
16#include <Dragger.h>
17#include <Entry.h>
18#include <File.h>
19#include <FindDirectory.h>
20#include <MenuItem.h>
21#include <Path.h>
22#include <PopUpMenu.h>
23#include <Roster.h>
24#include <Screen.h>
25#include <TextView.h>
26#include <Window.h>
27
28#include <ctype.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32
33#include <ViewPrivate.h>
34#include <WindowPrivate.h>
35
36
37static const char* kSignature = "application/x-vnd.Be-WORK";
38static const char* kOldSettingFile = "Workspace_data";
39static const char* kSettingsFile = "Workspaces_settings";
40
41static const uint32 kMsgChangeCount = 'chWC';
42static const uint32 kMsgToggleTitle = 'tgTt';
43static const uint32 kMsgToggleBorder = 'tgBd';
44static const uint32 kMsgToggleAutoRaise = 'tgAR';
45static const uint32 kMsgToggleAlwaysOnTop = 'tgAT';
46
47static const float kScreenBorderOffset = 10.0;
48
49
50class WorkspacesSettings {
51	public:
52		WorkspacesSettings();
53		virtual ~WorkspacesSettings();
54
55		BRect WindowFrame() const { return fWindowFrame; }
56		BRect ScreenFrame() const { return fScreenFrame; }
57
58		bool AutoRaising() const { return fAutoRaising; }
59		bool AlwaysOnTop() const { return fAlwaysOnTop; }
60		bool HasTitle() const { return fHasTitle; }
61		bool HasBorder() const { return fHasBorder; }
62
63		void UpdateFramesForScreen(BRect screenFrame);
64		void UpdateScreenFrame();
65
66		void SetWindowFrame(BRect);
67		void SetAutoRaising(bool enable) { fAutoRaising = enable; }
68		void SetAlwaysOnTop(bool enable) { fAlwaysOnTop = enable; }
69		void SetHasTitle(bool enable) { fHasTitle = enable; }
70		void SetHasBorder(bool enable) { fHasBorder = enable; }
71
72	private:
73		status_t _Open(BFile& file, int mode);
74
75		BRect	fWindowFrame;
76		BRect	fScreenFrame;
77		bool	fAutoRaising;
78		bool	fAlwaysOnTop;
79		bool	fHasTitle;
80		bool	fHasBorder;
81};
82
83class WorkspacesView : public BView {
84	public:
85		WorkspacesView(BRect frame);
86		WorkspacesView(BMessage* archive);
87		~WorkspacesView();
88
89		static	WorkspacesView* Instantiate(BMessage* archive);
90		virtual	status_t Archive(BMessage* archive, bool deep = true) const;
91
92		virtual void MessageReceived(BMessage* message);
93		virtual void MouseMoved(BPoint where, uint32 transit,
94			const BMessage* dragMessage);
95		virtual void MouseDown(BPoint where);
96
97	private:
98		void _AboutRequested();
99};
100
101class WorkspacesWindow : public BWindow {
102	public:
103		WorkspacesWindow(WorkspacesSettings *settings);
104		virtual ~WorkspacesWindow();
105
106		virtual void ScreenChanged(BRect frame, color_space mode);
107		virtual void FrameMoved(BPoint origin);
108		virtual void FrameResized(float width, float height);
109		virtual void Zoom(BPoint origin, float width, float height);
110
111		virtual void MessageReceived(BMessage *msg);
112		virtual bool QuitRequested();
113
114		void SetAutoRaise(bool enable);
115		bool IsAutoRaising() const { return fAutoRaising; }
116
117	private:
118		WorkspacesSettings *fSettings;
119		BRect	fPreviousFrame;
120		bool	fAutoRaising;
121};
122
123class WorkspacesApp : public BApplication {
124	public:
125		WorkspacesApp();
126		virtual ~WorkspacesApp();
127
128		virtual void AboutRequested();
129		virtual void ArgvReceived(int32 argc, char **argv);
130		virtual void ReadyToRun();
131
132		void Usage(const char *programName);
133
134	private:
135		WorkspacesWindow*	fWindow;
136};
137
138
139WorkspacesSettings::WorkspacesSettings()
140	:
141	fAutoRaising(false),
142	fAlwaysOnTop(false),
143	fHasTitle(true),
144	fHasBorder(true)
145{
146	UpdateScreenFrame();
147
148	bool loaded = false;
149	BScreen screen;
150
151	BFile file;
152	if (_Open(file, B_READ_ONLY) == B_OK) {
153		BMessage settings;
154		if (settings.Unflatten(&file) == B_OK) {
155			if (settings.FindRect("window", &fWindowFrame) == B_OK
156				&& settings.FindRect("screen", &fScreenFrame) == B_OK)
157				loaded = true;
158
159			settings.FindBool("auto-raise", &fAutoRaising);
160			settings.FindBool("always on top", &fAlwaysOnTop);
161
162			if (settings.FindBool("has title", &fHasTitle) != B_OK)
163				fHasTitle = true;
164			if (settings.FindBool("has border", &fHasBorder) != B_OK)
165				fHasBorder = true;
166		}
167	} else {
168		// try reading BeOS compatible settings
169		BPath path;
170		if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
171			path.Append(kOldSettingFile);
172			BFile file(path.Path(), B_READ_ONLY);
173			if (file.InitCheck() == B_OK
174				&& file.Read(&fWindowFrame, sizeof(BRect)) == sizeof(BRect)) {
175				// we now also store the frame of the screen to know
176				// in which context the window frame has been chosen
177				BRect frame;
178				if (file.Read(&frame, sizeof(BRect)) == sizeof(BRect))
179					fScreenFrame = frame;
180				else
181					fScreenFrame = screen.Frame();
182
183				loaded = true;
184			}
185		}
186	}
187
188	if (loaded) {
189		// if the current screen frame is different from the one
190		// just loaded, we need to alter the window frame accordingly
191		if (fScreenFrame != screen.Frame())
192			UpdateFramesForScreen(screen.Frame());
193	}
194
195	if (!loaded
196		|| !(screen.Frame().right + 5 >= fWindowFrame.right
197			&& screen.Frame().bottom + 5 >= fWindowFrame.bottom
198			&& screen.Frame().left - 5 <= fWindowFrame.left
199			&& screen.Frame().top - 5 <= fWindowFrame.top)) {
200		// set to some usable defaults
201		fWindowFrame = fScreenFrame;
202		fWindowFrame.OffsetBy(-kScreenBorderOffset, -kScreenBorderOffset);
203		fWindowFrame.left = fWindowFrame.right - 160;
204		fWindowFrame.top = fWindowFrame.bottom - 140;
205	}
206}
207
208
209WorkspacesSettings::~WorkspacesSettings()
210{
211	// write settings file
212	BFile file;
213	if (_Open(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK)
214		return;
215
216	BMessage settings('wksp');
217
218	if (settings.AddRect("window", fWindowFrame) == B_OK
219		&& settings.AddRect("screen", fScreenFrame) == B_OK
220		&& settings.AddBool("auto-raise", fAutoRaising) == B_OK
221		&& settings.AddBool("always on top", fAlwaysOnTop) == B_OK
222		&& settings.AddBool("has title", fHasTitle) == B_OK
223		&& settings.AddBool("has border", fHasBorder) == B_OK)
224		settings.Flatten(&file);
225}
226
227
228status_t
229WorkspacesSettings::_Open(BFile& file, int mode)
230{
231	BPath path;
232	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
233	 if (status != B_OK)
234		status = find_directory(B_COMMON_SETTINGS_DIRECTORY, &path);
235	 if (status != B_OK)
236		return status;
237
238	path.Append(kSettingsFile);
239
240	status = file.SetTo(path.Path(), mode);
241	if (mode == B_READ_ONLY && status == B_ENTRY_NOT_FOUND) {
242		if (find_directory(B_COMMON_SETTINGS_DIRECTORY, &path) == B_OK) {
243			path.Append(kSettingsFile);
244			status = file.SetTo(path.Path(), mode);
245		}
246	}
247
248	return status;
249}
250
251
252void
253WorkspacesSettings::UpdateFramesForScreen(BRect newScreenFrame)
254{
255	// don't change the position if the screen frame hasn't changed
256	if (newScreenFrame == fScreenFrame)
257		return;
258
259	// adjust horizontal position
260	if (fWindowFrame.right > fScreenFrame.right / 2)
261		fWindowFrame.OffsetTo(newScreenFrame.right
262			- (fScreenFrame.right - fWindowFrame.left), fWindowFrame.top);
263
264	// adjust vertical position
265	if (fWindowFrame.bottom > fScreenFrame.bottom / 2)
266		fWindowFrame.OffsetTo(fWindowFrame.left,
267			newScreenFrame.bottom - (fScreenFrame.bottom - fWindowFrame.top));
268
269	fScreenFrame = newScreenFrame;
270}
271
272
273void
274WorkspacesSettings::UpdateScreenFrame()
275{
276	BScreen screen;
277	fScreenFrame = screen.Frame();
278}
279
280
281void
282WorkspacesSettings::SetWindowFrame(BRect frame)
283{
284	fWindowFrame = frame;
285}
286
287
288//	#pragma mark -
289
290
291WorkspacesView::WorkspacesView(BRect frame)
292	: BView(frame, "workspaces", B_FOLLOW_ALL, kWorkspacesViewFlag)
293{
294	frame.OffsetTo(B_ORIGIN);
295	frame.top = frame.bottom - 7;
296	frame.left = frame.right - 7;
297	BDragger* dragger = new BDragger(frame, this,
298		B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
299	AddChild(dragger);
300}
301
302
303WorkspacesView::WorkspacesView(BMessage* archive)
304	: BView(archive)
305{
306}
307
308
309WorkspacesView::~WorkspacesView()
310{
311}
312
313
314/*static*/ WorkspacesView*
315WorkspacesView::Instantiate(BMessage* archive)
316{
317	if (!validate_instantiation(archive, "WorkspacesView"))
318		return NULL;
319
320	return new WorkspacesView(archive);
321}
322
323
324status_t
325WorkspacesView::Archive(BMessage* archive, bool deep) const
326{
327	status_t status = BView::Archive(archive, deep);
328	if (status == B_OK)
329		status = archive->AddString("add_on", kSignature);
330	if (status == B_OK)
331		status = archive->AddString("class", "WorkspacesView");
332
333	return status;
334}
335
336
337void
338WorkspacesView::_AboutRequested()
339{
340	BAlert *alert = new BAlert("about", "Workspaces\n"
341		"written by Fran��ois Revol, Axel D��rfler, and Matt Madia.\n\n"
342		"Copyright 2002-2008, Haiku.\n\n"
343		"Send windows behind using the Option key. "
344		"Move windows to front using the Control key.\n", "Ok");
345	BTextView *view = alert->TextView();
346	BFont font;
347
348	view->SetStylable(true);
349
350	view->GetFont(&font);
351	font.SetSize(18);
352	font.SetFace(B_BOLD_FACE);
353	view->SetFontAndColor(0, 10, &font);
354
355	alert->Go();
356}
357
358
359void
360WorkspacesView::MessageReceived(BMessage* message)
361{
362	switch (message->what) {
363		case B_ABOUT_REQUESTED:
364			_AboutRequested();
365			break;
366
367		case kMsgChangeCount:
368			be_roster->Launch("application/x-vnd.Be-SCRN");
369			break;
370
371		default:
372			BView::MessageReceived(message);
373			break;
374	}
375}
376
377
378void
379WorkspacesView::MouseMoved(BPoint where, uint32 transit,
380	const BMessage* dragMessage)
381{
382	if (Window() == NULL || EventMask() == 0)
383		return;
384
385	// Auto-Raise
386
387	where = ConvertToScreen(where);
388	BScreen screen(Window());
389	BRect frame = screen.Frame();
390	if (where.x == frame.left || where.x == frame.right
391		|| where.y == frame.top || where.y == frame.bottom) {
392		// cursor is on screen edge
393		if (Window()->Frame().Contains(where))
394			Window()->Activate();
395	}
396}
397
398
399void
400WorkspacesView::MouseDown(BPoint where)
401{
402	int32 buttons = 0;
403	if (Window() != NULL && Window()->CurrentMessage() != NULL)
404		Window()->CurrentMessage()->FindInt32("buttons", &buttons);
405
406	if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0)
407		return;
408
409	// open context menu
410
411	BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
412	menu->SetFont(be_plain_font);
413
414	// TODO: alternatively change the count here directly?
415	BMenuItem* changeItem = new BMenuItem("Change Workspace Count"
416		B_UTF8_ELLIPSIS, new BMessage(kMsgChangeCount));
417	menu->AddItem(changeItem);
418
419	WorkspacesWindow* window = dynamic_cast<WorkspacesWindow*>(Window());
420	if (window != NULL) {
421		BMenuItem* item;
422
423		menu->AddSeparatorItem();
424		menu->AddItem(item = new BMenuItem("No title",
425			new BMessage(kMsgToggleTitle)));
426		if (window->Look() == B_MODAL_WINDOW_LOOK)
427			item->SetMarked(true);
428		menu->AddItem(item = new BMenuItem("No Border",
429			new BMessage(kMsgToggleBorder)));
430		if (window->Look() == B_NO_BORDER_WINDOW_LOOK)
431			item->SetMarked(true);
432
433		menu->AddSeparatorItem();
434		menu->AddItem(item = new BMenuItem("Always On Top",
435			new BMessage(kMsgToggleAlwaysOnTop)));
436		if (window->Feel() == B_FLOATING_ALL_WINDOW_FEEL)
437			item->SetMarked(true);
438		menu->AddItem(item = new BMenuItem("Auto-Raise",
439			new BMessage(kMsgToggleAutoRaise)));
440		if (window->IsAutoRaising())
441			item->SetMarked(true);
442
443		menu->AddSeparatorItem();
444		menu->AddItem(new BMenuItem("About" B_UTF8_ELLIPSIS,
445			new BMessage(B_ABOUT_REQUESTED)));
446		menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED)));
447		menu->SetTargetForItems(window);
448	}
449
450	changeItem->SetTarget(this);
451	ConvertToScreen(&where);
452	menu->Go(where, true, true, true);
453}
454
455
456//	#pragma mark -
457
458
459WorkspacesWindow::WorkspacesWindow(WorkspacesSettings *settings)
460	: BWindow(settings->WindowFrame(), "Workspaces", B_TITLED_WINDOW_LOOK,
461 			B_NORMAL_WINDOW_FEEL, B_AVOID_FRONT | B_WILL_ACCEPT_FIRST_CLICK,
462 			B_ALL_WORKSPACES),
463 	fSettings(settings),
464 	fAutoRaising(false)
465{
466	AddChild(new WorkspacesView(Bounds()));
467	fPreviousFrame = Frame();
468
469	if (!fSettings->HasTitle())
470		SetLook(B_MODAL_WINDOW_LOOK);
471	else if (!fSettings->HasBorder())
472		SetLook(B_NO_BORDER_WINDOW_LOOK);
473
474	if (fSettings->AlwaysOnTop())
475		SetFeel(B_FLOATING_ALL_WINDOW_FEEL);
476	else
477		SetAutoRaise(fSettings->AutoRaising());
478}
479
480
481WorkspacesWindow::~WorkspacesWindow()
482{
483	delete fSettings;
484}
485
486
487void
488WorkspacesWindow::ScreenChanged(BRect rect, color_space mode)
489{
490	fPreviousFrame = fSettings->WindowFrame();
491		// work-around for a bug in BeOS, see explanation in FrameMoved()
492
493	fSettings->UpdateFramesForScreen(rect);
494	MoveTo(fSettings->WindowFrame().LeftTop());
495}
496
497
498void
499WorkspacesWindow::FrameMoved(BPoint origin)
500{
501	if (origin == fPreviousFrame.LeftTop()) {
502		// This works around a bug in BeOS; when you change the window
503		// position in WorkspaceActivated() or ScreenChanged(), it will
504		// send an old repositioning message *after* the FrameMoved()
505		// that originated your change has arrived
506		return;
507	}
508
509	fSettings->SetWindowFrame(Frame());
510}
511
512
513void
514WorkspacesWindow::FrameResized(float width, float height)
515{
516	fSettings->SetWindowFrame(Frame());
517}
518
519
520void
521WorkspacesWindow::Zoom(BPoint origin, float width, float height)
522{
523	BScreen screen;
524	origin = screen.Frame().RightBottom();
525	origin.x -= kScreenBorderOffset + fSettings->WindowFrame().Width();
526	origin.y -= kScreenBorderOffset + fSettings->WindowFrame().Height();
527
528	MoveTo(origin);
529}
530
531
532void
533WorkspacesWindow::MessageReceived(BMessage *message)
534{
535	switch (message->what) {
536		case B_SIMPLE_DATA:
537		{
538			// Drop from Tracker
539			entry_ref ref;
540			for (int i = 0; (message->FindRef("refs", i, &ref) == B_OK); i++)
541				be_roster->Launch(&ref);
542			break;
543		}
544
545		case B_ABOUT_REQUESTED:
546			PostMessage(message, ChildAt(0));
547			break;
548
549		case kMsgToggleBorder:
550		{
551			bool enable = false;
552			if (Look() == B_NO_BORDER_WINDOW_LOOK)
553				enable = true;
554
555			if (enable)
556				SetLook(B_TITLED_WINDOW_LOOK);
557			else {
558				SetLook(B_NO_BORDER_WINDOW_LOOK);
559				fSettings->SetHasTitle(true);
560			}
561
562			fSettings->SetHasBorder(enable);
563			break;
564		}
565
566		case kMsgToggleTitle:
567		{
568			bool enable = false;
569			if (Look() == B_MODAL_WINDOW_LOOK)
570				enable = true;
571
572			if (enable)
573				SetLook(B_TITLED_WINDOW_LOOK);
574			else {
575				SetLook(B_MODAL_WINDOW_LOOK);
576				fSettings->SetHasBorder(true);
577			}
578
579			fSettings->SetHasTitle(enable);
580			break;
581		}
582
583		case kMsgToggleAutoRaise:
584			SetAutoRaise(!IsAutoRaising());
585			SetFeel(B_NORMAL_WINDOW_FEEL);
586			break;
587
588		case kMsgToggleAlwaysOnTop:
589		{
590			bool enable = false;
591			if (Feel() != B_FLOATING_ALL_WINDOW_FEEL)
592				enable = true;
593
594			if (enable)
595				SetFeel(B_FLOATING_ALL_WINDOW_FEEL);
596			else
597				SetFeel(B_NORMAL_WINDOW_FEEL);
598
599			fSettings->SetAlwaysOnTop(enable);
600			break;
601		}
602
603		default:
604			BWindow::MessageReceived(message);
605			break;
606	}
607}
608
609
610bool
611WorkspacesWindow::QuitRequested()
612{
613	be_app->PostMessage(B_QUIT_REQUESTED);
614	return true;
615}
616
617
618void
619WorkspacesWindow::SetAutoRaise(bool enable)
620{
621	if (enable == fAutoRaising)
622		return;
623
624	fAutoRaising = enable;
625	fSettings->SetAutoRaising(enable);
626
627	if (enable)
628		ChildAt(0)->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
629	else
630		ChildAt(0)->SetEventMask(0);
631}
632
633
634//	#pragma mark -
635
636
637WorkspacesApp::WorkspacesApp()
638	: BApplication(kSignature)
639{
640	fWindow = new WorkspacesWindow(new WorkspacesSettings());
641}
642
643
644WorkspacesApp::~WorkspacesApp()
645{
646}
647
648
649void
650WorkspacesApp::AboutRequested()
651{
652	fWindow->PostMessage(B_ABOUT_REQUESTED);
653}
654
655
656void
657WorkspacesApp::Usage(const char *programName)
658{
659	printf("Usage: %s [options] [workspace]\n"
660		"where \"options\" is one of:\n"
661		"  --notitle\t\ttitle bar removed.  border and resize kept.\n"
662		"  --noborder\t\ttitle, border, and resize removed.\n"
663		"  --avoidfocus\t\tprevents the window from being the target of keyboard events.\n"
664		"  --alwaysontop\t\tkeeps window on top\n"
665		"  --notmovable\t\twindow can't be moved around\n"
666		"  --autoraise\t\tauto-raise the workspace window when it's at the screen corner\n"
667		"  --help\t\tdisplay this help and exit\n"
668		"and \"workspace\" is the number of the Workspace to which to switch (0-31)\n",
669		programName);
670
671	// quit only if we aren't running already
672	if (IsLaunching())
673		Quit();
674}
675
676
677void
678WorkspacesApp::ArgvReceived(int32 argc, char **argv)
679{
680	for (int i = 1;  i < argc;  i++) {
681		if (argv[i][0] == '-' && argv[i][1] == '-') {
682			// evaluate --arguments
683			if (!strcmp(argv[i], "--notitle"))
684				fWindow->SetLook(B_MODAL_WINDOW_LOOK);
685			else if (!strcmp(argv[i], "--noborder"))
686				fWindow->SetLook(B_NO_BORDER_WINDOW_LOOK);
687			else if (!strcmp(argv[i], "--avoidfocus"))
688				fWindow->SetFlags(fWindow->Flags() | B_AVOID_FOCUS);
689			else if (!strcmp(argv[i], "--notmovable"))
690				fWindow->SetFlags(fWindow->Flags() | B_NOT_MOVABLE);
691			else if (!strcmp(argv[i], "--alwaysontop"))
692				fWindow->SetFeel(B_FLOATING_ALL_WINDOW_FEEL);
693			else if (!strcmp(argv[i], "--autoraise"))
694				fWindow->SetAutoRaise(true);
695			else {
696				const char *programName = strrchr(argv[0], '/');
697				programName = programName ? programName + 1 : argv[0];
698
699				Usage(programName);
700			}
701		} else if (isdigit(*argv[i])) {
702			// check for a numeric arg, if not already given
703			activate_workspace(atoi(argv[i]));
704
705			// if the app is running, don't quit
706			// but if it isn't, cancel the complete run, so it doesn't
707			// open any window
708			if (IsLaunching())
709				Quit();
710		} else if (!strcmp(argv[i], "-")) {
711			activate_workspace(current_workspace() - 1);
712
713			if (IsLaunching())
714				Quit();
715		} else if (!strcmp(argv[i], "+")) {
716			activate_workspace(current_workspace() + 1);
717
718			if (IsLaunching())
719				Quit();
720		} else {
721			// some unknown arguments were specified
722			fprintf(stderr, "Invalid argument: %s\n", argv[i]);
723
724			if (IsLaunching())
725				Quit();
726		}
727	}
728}
729
730
731void
732WorkspacesApp::ReadyToRun()
733{
734	fWindow->Show();
735}
736
737
738//	#pragma mark -
739
740
741int
742main(int32 argc, char **argv)
743{
744	WorkspacesApp app;
745	app.Run();
746
747	return 0;
748}
749