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