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