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