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