1/*
2 * Copyright 2006 - 2011, Stephan A��mus <superstippi@gmx.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6#include "MainWindow.h"
7
8#include <stdio.h>
9
10#include <Directory.h>
11#include <File.h>
12#include <Alert.h>
13#include <Application.h>
14#include <Catalog.h>
15#include <GroupLayout.h>
16#include <Menu.h>
17#include <MenuItem.h>
18#include <Messenger.h>
19#include <Path.h>
20#include <Roster.h>
21#include <Screen.h>
22
23#include "support.h"
24
25#include "App.h"
26#include "LaunchButton.h"
27#include "NamePanel.h"
28#include "PadView.h"
29
30
31#undef B_TRANSLATION_CONTEXT
32#define B_TRANSLATION_CONTEXT "LaunchBox"
33MainWindow::MainWindow(const char* name, BRect frame, bool addDefaultButtons)
34	:
35	BWindow(frame, name, B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
36		B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE
37		| B_WILL_ACCEPT_FIRST_CLICK | B_NO_WORKSPACE_ACTIVATION
38		| B_AUTO_UPDATE_SIZE_LIMITS | B_SAME_POSITION_IN_ALL_WORKSPACES,
39		B_ALL_WORKSPACES),
40	fSettings(new BMessage('sett')),
41	fPadView(new PadView("pad view")),
42	fAutoRaise(false),
43	fShowOnAllWorkspaces(true)
44{
45	bool buttonsAdded = false;
46	if (load_settings(fSettings, "main_settings", "LaunchBox") >= B_OK)
47		buttonsAdded = LoadSettings(fSettings);
48	if (!buttonsAdded) {
49		if (addDefaultButtons)
50			_AddDefaultButtons();
51		else
52			_AddEmptyButtons();
53	}
54	SetLayout(new BGroupLayout(B_HORIZONTAL));
55	AddChild(fPadView);
56}
57
58
59MainWindow::MainWindow(const char* name, BRect frame, BMessage* settings)
60	:
61	BWindow(frame, name,
62		B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL,
63		B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE
64		| B_WILL_ACCEPT_FIRST_CLICK | B_NO_WORKSPACE_ACTIVATION
65		| B_AUTO_UPDATE_SIZE_LIMITS | B_SAME_POSITION_IN_ALL_WORKSPACES,
66		B_ALL_WORKSPACES),
67	fSettings(settings),
68	fPadView(new PadView("pad view")),
69	fAutoRaise(false),
70	fShowOnAllWorkspaces(true)
71{
72	if (!LoadSettings(settings))
73		_AddEmptyButtons();
74
75	SetLayout(new BGroupLayout(B_HORIZONTAL));
76	AddChild(fPadView);
77}
78
79
80MainWindow::~MainWindow()
81{
82	delete fSettings;
83}
84
85
86bool
87MainWindow::QuitRequested()
88{
89	int32 padWindowCount = 0;
90	for (int32 i = 0; BWindow* window = be_app->WindowAt(i); i++) {
91		if (dynamic_cast<MainWindow*>(window))
92			padWindowCount++;
93	}
94	bool canClose = true;
95
96	if (padWindowCount == 1) {
97		be_app->PostMessage(B_QUIT_REQUESTED);
98		canClose = false;
99	} else {
100		BAlert* alert = new BAlert(B_TRANSLATE("last chance"),
101			B_TRANSLATE("Really close this pad?\n"
102							"(The pad will not be remembered.)"),
103			B_TRANSLATE("Close"), B_TRANSLATE("Cancel"), NULL);
104		alert->SetShortcut(1, B_ESCAPE);
105		if (alert->Go() == 1)
106			canClose = false;
107	}
108	return canClose;
109}
110
111
112void
113MainWindow::MessageReceived(BMessage* message)
114{
115	switch (message->what) {
116		case MSG_LAUNCH:
117		{
118			BView* pointer;
119			if (message->FindPointer("be:source", (void**)&pointer) < B_OK)
120				break;
121			LaunchButton* button = dynamic_cast<LaunchButton*>(pointer);
122			if (button == NULL)
123				break;
124			BString errorMessage;
125			bool launchedByRef = false;
126			if (button->Ref()) {
127				BEntry entry(button->Ref(), true);
128				if (entry.IsDirectory()) {
129					// open in Tracker
130					BMessenger messenger("application/x-vnd.Be-TRAK");
131					if (messenger.IsValid()) {
132						BMessage trackerMessage(B_REFS_RECEIVED);
133						trackerMessage.AddRef("refs", button->Ref());
134						status_t ret = messenger.SendMessage(&trackerMessage);
135						if (ret < B_OK) {
136							errorMessage = B_TRANSLATE("Failed to send "
137							"'open folder' command to Tracker.\n\nError: ");
138							errorMessage << strerror(ret);
139						} else
140							launchedByRef = true;
141					} else
142						errorMessage = ("Failed to open folder - is Tracker "
143							"running?");
144				} else {
145					status_t ret = be_roster->Launch(button->Ref());
146					if (ret < B_OK && ret != B_ALREADY_RUNNING) {
147						BString errStr(B_TRANSLATE("Failed to launch '%1'.\n"
148							"\nError:"));
149						BPath path(button->Ref());
150						if (path.InitCheck() >= B_OK)
151							errStr.ReplaceFirst("%1", path.Path());
152						else
153							errStr.ReplaceFirst("%1", button->Ref()->name);
154						errorMessage << errStr.String() << " ";
155						errorMessage << strerror(ret);
156					} else
157						launchedByRef = true;
158				}
159			}
160			if (!launchedByRef && button->AppSignature()) {
161				status_t ret = be_roster->Launch(button->AppSignature());
162				if (ret != B_OK && ret != B_ALREADY_RUNNING) {
163					BString errStr(B_TRANSLATE("\n\nFailed to launch application "
164						"with signature '%2'.\n\nError:"));
165					errStr.ReplaceFirst("%2", button->AppSignature());
166					errorMessage << errStr.String() << " ";
167					errorMessage << strerror(ret);
168				} else {
169					// clear error message on success (might have been
170					// filled when trying to launch by ref)
171					errorMessage = "";
172				}
173			} else if (!launchedByRef) {
174				errorMessage = B_TRANSLATE("Failed to launch 'something', "
175					"error in Pad data.");
176			}
177			if (errorMessage.Length() > 0) {
178				BAlert* alert = new BAlert("error", errorMessage.String(),
179					B_TRANSLATE("Bummer"), NULL, NULL, B_WIDTH_FROM_WIDEST);
180				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
181				alert->Go(NULL);
182			}
183			break;
184		}
185		case MSG_ADD_SLOT:
186		{
187			LaunchButton* button;
188			if (message->FindPointer("be:source", (void**)&button) >= B_OK) {
189				fPadView->AddButton(new LaunchButton("launch button",
190					NULL, new BMessage(MSG_LAUNCH)), button);
191			}
192			break;
193		}
194		case MSG_CLEAR_SLOT:
195		{
196			LaunchButton* button;
197			if (message->FindPointer("be:source", (void**)&button) >= B_OK)
198				button->SetTo((entry_ref*)NULL);
199			break;
200		}
201		case MSG_REMOVE_SLOT:
202		{
203			LaunchButton* button;
204			if (message->FindPointer("be:source", (void**)&button) >= B_OK) {
205				if (fPadView->RemoveButton(button))
206					delete button;
207			}
208			break;
209		}
210		case MSG_SET_DESCRIPTION:
211		{
212			LaunchButton* button;
213			if (message->FindPointer("be:source", (void**)&button) >= B_OK) {
214				const char* name;
215				if (message->FindString("name", &name) >= B_OK) {
216					// message comes from a previous name panel
217					button->SetDescription(name);
218					BRect namePanelFrame;
219					if (message->FindRect("frame", &namePanelFrame) == B_OK) {
220						((App*)be_app)->SetNamePanelSize(
221							namePanelFrame.Size());
222					}
223				} else {
224					// message comes from pad view
225					entry_ref* ref = button->Ref();
226					if (ref) {
227						BString helper(B_TRANSLATE("Description for '%3'"));
228						helper.ReplaceFirst("%3", ref->name);
229						// Place the name panel besides the pad, but give it
230						// the user configured size.
231						BPoint origin = B_ORIGIN;
232						BSize size = ((App*)be_app)->NamePanelSize();
233						NamePanel* panel = new NamePanel(helper.String(),
234							button->Description(), this, this,
235							new BMessage(*message), size);
236						panel->Layout(true);
237						size = panel->Frame().Size();
238						BScreen screen(this);
239						BPoint mousePos;
240						uint32 buttons;
241						fPadView->GetMouse(&mousePos, &buttons, false);
242						fPadView->ConvertToScreen(&mousePos);
243						if (fPadView->Orientation() == B_HORIZONTAL) {
244							// Place above or below the pad
245							origin.x = mousePos.x - size.width / 2;
246							if (screen.Frame().bottom - Frame().bottom
247									> size.height + 20) {
248								origin.y = Frame().bottom + 10;
249							} else {
250								origin.y = Frame().top - 10 - size.height;
251							}
252						} else {
253							// Place left or right of the pad
254							origin.y = mousePos.y - size.height / 2;
255							if (screen.Frame().right - Frame().right
256									> size.width + 20) {
257								origin.x = Frame().right + 10;
258							} else {
259								origin.x = Frame().left - 10 - size.width;
260							}
261						}
262						panel->MoveTo(origin);
263						panel->Show();
264					}
265				}
266			}
267			break;
268		}
269		case MSG_ADD_WINDOW:
270		{
271			BMessage settings('sett');
272			SaveSettings(&settings);
273			message->AddMessage("window", &settings);
274			be_app->PostMessage(message);
275			break;
276		}
277		case MSG_SHOW_BORDER:
278			SetLook(B_TITLED_WINDOW_LOOK);
279			break;
280		case MSG_HIDE_BORDER:
281			SetLook(B_BORDERED_WINDOW_LOOK);
282			break;
283		case MSG_TOGGLE_AUTORAISE:
284			ToggleAutoRaise();
285			break;
286		case MSG_SHOW_ON_ALL_WORKSPACES:
287			fShowOnAllWorkspaces = !fShowOnAllWorkspaces;
288			if (fShowOnAllWorkspaces)
289				SetWorkspaces(B_ALL_WORKSPACES);
290			else
291				SetWorkspaces(1L << current_workspace());
292			break;
293		case MSG_OPEN_CONTAINING_FOLDER:
294		{
295			LaunchButton* button;
296			if (message->FindPointer("be:source", (void**)&button) == B_OK && button->Ref() != NULL) {
297				entry_ref target = *button->Ref();
298				BEntry openTarget(&target);
299				BMessage openMsg(B_REFS_RECEIVED);
300				BMessenger tracker("application/x-vnd.Be-TRAK");
301				openTarget.GetParent(&openTarget);
302				openTarget.GetRef(&target);
303				openMsg.AddRef("refs",&target);
304				tracker.SendMessage(&openMsg);
305			}
306		}
307		break;
308		case B_SIMPLE_DATA:
309		case B_REFS_RECEIVED:
310		case B_PASTE:
311		case B_MODIFIERS_CHANGED:
312			break;
313		default:
314			BWindow::MessageReceived(message);
315			break;
316	}
317}
318
319
320void
321MainWindow::Show()
322{
323	BWindow::Show();
324	_GetLocation();
325}
326
327
328void
329MainWindow::ScreenChanged(BRect frame, color_space format)
330{
331	_AdjustLocation(Frame());
332}
333
334
335void
336MainWindow::WorkspaceActivated(int32 workspace, bool active)
337{
338	if (fShowOnAllWorkspaces) {
339		if (!active)
340			_GetLocation();
341		else
342			_AdjustLocation(Frame());
343	}
344}
345
346
347void
348MainWindow::FrameMoved(BPoint origin)
349{
350	if (IsActive()) {
351		_GetLocation();
352		_NotifySettingsChanged();
353	}
354}
355
356
357void
358MainWindow::FrameResized(float width, float height)
359{
360	if (IsActive()) {
361		_GetLocation();
362		_NotifySettingsChanged();
363	}
364	BWindow::FrameResized(width, height);
365}
366
367
368void
369MainWindow::ToggleAutoRaise()
370{
371	fAutoRaise = !fAutoRaise;
372	if (fAutoRaise)
373		fPadView->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
374	else
375		fPadView->SetEventMask(0);
376
377	_NotifySettingsChanged();
378}
379
380
381bool
382MainWindow::LoadSettings(const BMessage* message)
383{
384	// restore window positioning
385	BPoint point;
386	bool useAdjust = false;
387	if (message->FindPoint("window position", &point) == B_OK) {
388		fScreenPosition = point;
389		useAdjust = true;
390	}
391	float borderDist;
392	if (message->FindFloat("border distance", &borderDist) == B_OK) {
393		fBorderDist = borderDist;
394	}
395	// restore window frame
396	BRect frame;
397	if (message->FindRect("window frame", &frame) == B_OK) {
398		if (useAdjust) {
399			_AdjustLocation(frame);
400		} else {
401			make_sure_frame_is_on_screen(frame, this);
402			MoveTo(frame.LeftTop());
403			ResizeTo(frame.Width(), frame.Height());
404		}
405	}
406
407	// restore window look
408	window_look look;
409	if (message->FindInt32("window look", (int32*)&look) == B_OK)
410		SetLook(look);
411
412	// restore orientation
413	int32 orientation;
414	if (message->FindInt32("orientation", &orientation) == B_OK)
415		fPadView->SetOrientation((enum orientation)orientation);
416
417	// restore icon size
418	int32 iconSize;
419	if (message->FindInt32("icon size", &iconSize) == B_OK)
420		fPadView->SetIconSize(iconSize);
421
422	// restore ignore double click
423	bool ignoreDoubleClick;
424	if (message->FindBool("ignore double click", &ignoreDoubleClick) == B_OK)
425		fPadView->SetIgnoreDoubleClick(ignoreDoubleClick);
426
427	// restore buttons
428	const char* path;
429	bool buttonAdded = false;
430	for (int32 i = 0; message->FindString("path", i, &path) >= B_OK; i++) {
431		LaunchButton* button = new LaunchButton("launch button",
432			NULL, new BMessage(MSG_LAUNCH));
433		fPadView->AddButton(button);
434		BString signature;
435		if (message->FindString("signature", i, &signature) >= B_OK
436			&& signature.CountChars() > 0)  {
437			button->SetTo(signature.String(), true);
438		}
439
440		BEntry entry(path, true);
441		entry_ref ref;
442		if (entry.Exists() && entry.GetRef(&ref) == B_OK)
443			button->SetTo(&ref);
444
445		const char* text;
446		if (message->FindString("description", i, &text) >= B_OK)
447			button->SetDescription(text);
448		buttonAdded = true;
449	}
450
451	// restore auto raise setting
452	bool autoRaise;
453	if (message->FindBool("auto raise", &autoRaise) == B_OK && autoRaise)
454		ToggleAutoRaise();
455
456	// store workspace setting
457	bool showOnAllWorkspaces;
458	if (message->FindBool("all workspaces", &showOnAllWorkspaces) == B_OK) {
459		fShowOnAllWorkspaces = showOnAllWorkspaces;
460		SetWorkspaces(showOnAllWorkspaces
461			? B_ALL_WORKSPACES : 1L << current_workspace());
462	}
463	if (!fShowOnAllWorkspaces) {
464		uint32 workspaces;
465		if (message->FindInt32("workspaces", (int32*)&workspaces) == B_OK)
466			SetWorkspaces(workspaces);
467	}
468
469	return buttonAdded;
470}
471
472
473void
474MainWindow::SaveSettings(BMessage* message)
475{
476	// make sure the positioning info is correct
477	_GetLocation();
478	// store window position
479	if (message->ReplacePoint("window position", fScreenPosition) != B_OK)
480		message->AddPoint("window position", fScreenPosition);
481
482	if (message->ReplaceFloat("border distance", fBorderDist) != B_OK)
483		message->AddFloat("border distance", fBorderDist);
484
485	// store window frame and look
486	if (message->ReplaceRect("window frame", Frame()) != B_OK)
487		message->AddRect("window frame", Frame());
488
489	if (message->ReplaceInt32("window look", Look()) != B_OK)
490		message->AddInt32("window look", Look());
491
492	// store orientation
493	if (message->ReplaceInt32("orientation",
494			(int32)fPadView->Orientation()) != B_OK)
495		message->AddInt32("orientation", (int32)fPadView->Orientation());
496
497	// store icon size
498	if (message->ReplaceInt32("icon size", fPadView->IconSize()) != B_OK)
499		message->AddInt32("icon size", fPadView->IconSize());
500
501	// store ignore double click
502	if (message->ReplaceBool("ignore double click",
503			fPadView->IgnoreDoubleClick()) != B_OK) {
504		message->AddBool("ignore double click", fPadView->IgnoreDoubleClick());
505	}
506
507	// store buttons
508	message->RemoveName("path");
509	message->RemoveName("description");
510	message->RemoveName("signature");
511	for (int32 i = 0; LaunchButton* button = fPadView->ButtonAt(i); i++) {
512		BPath path(button->Ref());
513		if (path.InitCheck() >= B_OK)
514			message->AddString("path", path.Path());
515		else
516			message->AddString("path", "");
517		message->AddString("description", button->Description());
518
519		if (button->AppSignature())
520			message->AddString("signature", button->AppSignature());
521		else
522			message->AddString("signature", "");
523	}
524
525	// store auto raise setting
526	if (message->ReplaceBool("auto raise", fAutoRaise) != B_OK)
527		message->AddBool("auto raise", fAutoRaise);
528
529	// store workspace setting
530	if (message->ReplaceBool("all workspaces", fShowOnAllWorkspaces) != B_OK)
531		message->AddBool("all workspaces", fShowOnAllWorkspaces);
532	if (message->ReplaceInt32("workspaces", Workspaces()) != B_OK)
533		message->AddInt32("workspaces", Workspaces());
534}
535
536
537void
538MainWindow::_GetLocation()
539{
540	BRect frame = Frame();
541	BPoint origin = frame.LeftTop();
542	BPoint center(origin.x + frame.Width() / 2.0,
543		origin.y + frame.Height() / 2.0);
544	BScreen screen(this);
545	BRect screenFrame = screen.Frame();
546	fScreenPosition.x = center.x / screenFrame.Width();
547	fScreenPosition.y = center.y / screenFrame.Height();
548	if (fabs(0.5 - fScreenPosition.x) > fabs(0.5 - fScreenPosition.y)) {
549		// nearest to left or right border
550		if (fScreenPosition.x < 0.5)
551			fBorderDist = frame.left - screenFrame.left;
552		else
553			fBorderDist = screenFrame.right - frame.right;
554	} else {
555		// nearest to top or bottom border
556		if (fScreenPosition.y < 0.5)
557			fBorderDist = frame.top - screenFrame.top;
558		else
559			fBorderDist = screenFrame.bottom - frame.bottom;
560	}
561}
562
563
564void
565MainWindow::_AdjustLocation(BRect frame)
566{
567	BScreen screen(this);
568	BRect screenFrame = screen.Frame();
569	BPoint center(fScreenPosition.x * screenFrame.Width(),
570		fScreenPosition.y * screenFrame.Height());
571	BPoint frameCenter(frame.left + frame.Width() / 2.0,
572		frame.top + frame.Height() / 2.0);
573	frame.OffsetBy(center - frameCenter);
574	// ignore border dist when distance too large
575	if (fBorderDist < 10.0) {
576		// see which border we mean depending on screen position
577		BPoint offset(0.0, 0.0);
578		if (fabs(0.5 - fScreenPosition.x) > fabs(0.5 - fScreenPosition.y)) {
579			// left or right border
580			if (fScreenPosition.x < 0.5)
581				offset.x = (screenFrame.left + fBorderDist) - frame.left;
582			else
583				offset.x = (screenFrame.right - fBorderDist) - frame.right;
584		} else {
585			// top or bottom border
586			if (fScreenPosition.y < 0.5)
587				offset.y = (screenFrame.top + fBorderDist) - frame.top;
588			else
589				offset.y = (screenFrame.bottom - fBorderDist) - frame.bottom;
590		}
591		frame.OffsetBy(offset);
592	}
593
594	make_sure_frame_is_on_screen(frame, this);
595
596	MoveTo(frame.LeftTop());
597	ResizeTo(frame.Width(), frame.Height());
598}
599
600
601void
602MainWindow::_AddDefaultButtons()
603{
604	// Mail
605	LaunchButton* button = new LaunchButton("launch button", NULL,
606		new BMessage(MSG_LAUNCH));
607	fPadView->AddButton(button);
608	button->SetTo("application/x-vnd.Be-MAIL", true);
609
610	// StyledEdit
611	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
612	fPadView->AddButton(button);
613	button->SetTo("application/x-vnd.Haiku-StyledEdit", true);
614
615	// ShowImage
616	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
617	fPadView->AddButton(button);
618	button->SetTo("application/x-vnd.Haiku-ShowImage", true);
619
620	// MediaPlayer
621	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
622	fPadView->AddButton(button);
623	button->SetTo("application/x-vnd.Haiku-MediaPlayer", true);
624
625	// DeskCalc
626	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
627	fPadView->AddButton(button);
628	button->SetTo("application/x-vnd.Haiku-DeskCalc", true);
629
630	// Terminal
631	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
632	fPadView->AddButton(button);
633	button->SetTo("application/x-vnd.Haiku-Terminal", true);
634}
635
636
637void
638MainWindow::_AddEmptyButtons()
639{
640	LaunchButton* button = new LaunchButton("launch button", NULL,
641		new BMessage(MSG_LAUNCH));
642	fPadView->AddButton(button);
643
644	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
645	fPadView->AddButton(button);
646
647	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
648	fPadView->AddButton(button);
649}
650
651
652void
653MainWindow::_NotifySettingsChanged()
654{
655	be_app->PostMessage(MSG_SETTINGS_CHANGED);
656}