xref: /haiku/src/apps/launchbox/MainWindow.cpp (revision 1c09002cbee8e797a0f8bbfc5678dfadd39ee1a7)
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 		default:
256 			BWindow::MessageReceived(message);
257 			break;
258 	}
259 }
260 
261 
262 void
263 MainWindow::Show()
264 {
265 	BWindow::Show();
266 	_GetLocation();
267 }
268 
269 
270 void
271 MainWindow::ScreenChanged(BRect frame, color_space format)
272 {
273 	_AdjustLocation(Frame());
274 }
275 
276 
277 void
278 MainWindow::WorkspaceActivated(int32 workspace, bool active)
279 {
280 	if (fShowOnAllWorkspaces) {
281 		if (!active)
282 			_GetLocation();
283 		else
284 			_AdjustLocation(Frame());
285 	}
286 }
287 
288 
289 void
290 MainWindow::FrameMoved(BPoint origin)
291 {
292 	if (IsActive()) {
293 		_GetLocation();
294 		_NotifySettingsChanged();
295 	}
296 }
297 
298 
299 void
300 MainWindow::FrameResized(float width, float height)
301 {
302 	if (IsActive()) {
303 		_GetLocation();
304 		_NotifySettingsChanged();
305 	}
306 	BWindow::FrameResized(width, height);
307 }
308 
309 
310 void
311 MainWindow::ToggleAutoRaise()
312 {
313 	fAutoRaise = !fAutoRaise;
314 	if (fAutoRaise)
315 		fPadView->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
316 	else
317 		fPadView->SetEventMask(0);
318 
319 	_NotifySettingsChanged();
320 }
321 
322 
323 bool
324 MainWindow::LoadSettings(const BMessage* message)
325 {
326 	// restore window positioning
327 	BPoint point;
328 	bool useAdjust = false;
329 	if (message->FindPoint("window position", &point) == B_OK) {
330 		fScreenPosition = point;
331 		useAdjust = true;
332 	}
333 	float borderDist;
334 	if (message->FindFloat("border distance", &borderDist) == B_OK) {
335 		fBorderDist = borderDist;
336 	}
337 	// restore window frame
338 	BRect frame;
339 	if (message->FindRect("window frame", &frame) == B_OK) {
340 		if (useAdjust) {
341 			_AdjustLocation(frame);
342 		} else {
343 			make_sure_frame_is_on_screen(frame, this);
344 			MoveTo(frame.LeftTop());
345 			ResizeTo(frame.Width(), frame.Height());
346 		}
347 	}
348 
349 	// restore name panel frame
350 	if (message->FindRect("name panel frame", &frame) == B_OK) {
351 		if (frame.IsValid()) {
352 			make_sure_frame_is_on_screen(frame, this);
353 			fNamePanelFrame = frame;
354 		}
355 	}
356 
357 	// restore window look
358 	window_look look;
359 	if (message->FindInt32("window look", (int32*)&look) == B_OK)
360 		SetLook(look);
361 
362 	// restore orientation
363 	int32 orientation;
364 	if (message->FindInt32("orientation", &orientation) == B_OK)
365 		fPadView->SetOrientation((enum orientation)orientation);
366 
367 	// restore icon size
368 	int32 iconSize;
369 	if (message->FindInt32("icon size", &iconSize) == B_OK)
370 		fPadView->SetIconSize(iconSize);
371 
372 	// restore ignore double click
373 	bool ignoreDoubleClick;
374 	if (message->FindBool("ignore double click", &ignoreDoubleClick) == B_OK)
375 		fPadView->SetIgnoreDoubleClick(ignoreDoubleClick);
376 
377 	// restore buttons
378 	const char* path;
379 	bool buttonAdded = false;
380 	for (int32 i = 0; message->FindString("path", i, &path) >= B_OK; i++) {
381 		LaunchButton* button = new LaunchButton("launch button", fLastID++,
382 			NULL, new BMessage(MSG_LAUNCH));
383 		fPadView->AddButton(button);
384 		BString signature;
385 		if (message->FindString("signature", i, &signature) >= B_OK
386 			&& signature.CountChars() > 0)  {
387 			button->SetTo(signature.String(), true);
388 		}
389 
390 		BEntry entry(path, true);
391 		entry_ref ref;
392 		if (entry.Exists() && entry.GetRef(&ref) == B_OK)
393 			button->SetTo(&ref);
394 
395 		const char* text;
396 		if (message->FindString("description", i, &text) >= B_OK)
397 			button->SetDescription(text);
398 		buttonAdded = true;
399 	}
400 
401 	// restore auto raise setting
402 	bool autoRaise;
403 	if (message->FindBool("auto raise", &autoRaise) == B_OK && autoRaise)
404 		ToggleAutoRaise();
405 
406 	// store workspace setting
407 	bool showOnAllWorkspaces;
408 	if (message->FindBool("all workspaces", &showOnAllWorkspaces) == B_OK)
409 		fShowOnAllWorkspaces = showOnAllWorkspaces;
410 		SetWorkspaces(showOnAllWorkspaces
411 			? B_ALL_WORKSPACES : 1L << current_workspace());
412 	if (!fShowOnAllWorkspaces) {
413 		uint32 workspaces;
414 		if (message->FindInt32("workspaces", (int32*)&workspaces) == B_OK)
415 			SetWorkspaces(workspaces);
416 	}
417 
418 	return buttonAdded;
419 }
420 
421 
422 void
423 MainWindow::SaveSettings(BMessage* message)
424 {
425 	// make sure the positioning info is correct
426 	_GetLocation();
427 	// store window position
428 	if (message->ReplacePoint("window position", fScreenPosition) != B_OK)
429 		message->AddPoint("window position", fScreenPosition);
430 
431 	if (message->ReplaceFloat("border distance", fBorderDist) != B_OK)
432 		message->AddFloat("border distance", fBorderDist);
433 
434 	// store window frame
435 	if (message->ReplaceRect("window frame", Frame()) != B_OK)
436 		message->AddRect("window frame", Frame());
437 
438 	// store name panel frame
439 	if (message->ReplaceRect("name panel frame", fNamePanelFrame) != B_OK)
440 		message->AddRect("name panel frame", fNamePanelFrame);
441 
442 	if (message->ReplaceInt32("window look", Look()) != B_OK)
443 		message->AddInt32("window look", Look());
444 
445 	// store orientation
446 	if (message->ReplaceInt32("orientation",
447 			(int32)fPadView->Orientation()) != B_OK)
448 		message->AddInt32("orientation", (int32)fPadView->Orientation());
449 
450 	// store icon size
451 	if (message->ReplaceInt32("icon size", fPadView->IconSize()) != B_OK)
452 		message->AddInt32("icon size", fPadView->IconSize());
453 
454 	// store ignore double click
455 	if (message->ReplaceBool("ignore double click",
456 			fPadView->IgnoreDoubleClick()) != B_OK) {
457 		message->AddBool("ignore double click", fPadView->IgnoreDoubleClick());
458 	}
459 
460 	// store buttons
461 	message->RemoveName("path");
462 	message->RemoveName("description");
463 	message->RemoveName("signature");
464 	for (int32 i = 0; LaunchButton* button = fPadView->ButtonAt(i); i++) {
465 		BPath path(button->Ref());
466 		if (path.InitCheck() >= B_OK)
467 			message->AddString("path", path.Path());
468 		else
469 			message->AddString("path", "");
470 		message->AddString("description", button->Description());
471 
472 		if (button->AppSignature())
473 			message->AddString("signature", button->AppSignature());
474 		else
475 			message->AddString("signature", "");
476 	}
477 
478 	// store auto raise setting
479 	if (message->ReplaceBool("auto raise", fAutoRaise) != B_OK)
480 		message->AddBool("auto raise", fAutoRaise);
481 
482 	// store workspace setting
483 	if (message->ReplaceBool("all workspaces", fShowOnAllWorkspaces) != B_OK)
484 		message->AddBool("all workspaces", fShowOnAllWorkspaces);
485 	if (message->ReplaceInt32("workspaces", Workspaces()) != B_OK)
486 		message->AddInt32("workspaces", Workspaces());
487 }
488 
489 
490 void
491 MainWindow::_GetLocation()
492 {
493 	BRect frame = Frame();
494 	BPoint origin = frame.LeftTop();
495 	BPoint center(origin.x + frame.Width() / 2.0,
496 		origin.y + frame.Height() / 2.0);
497 	BScreen screen(this);
498 	BRect screenFrame = screen.Frame();
499 	fScreenPosition.x = center.x / screenFrame.Width();
500 	fScreenPosition.y = center.y / screenFrame.Height();
501 	if (fabs(0.5 - fScreenPosition.x) > fabs(0.5 - fScreenPosition.y)) {
502 		// nearest to left or right border
503 		if (fScreenPosition.x < 0.5)
504 			fBorderDist = frame.left - screenFrame.left;
505 		else
506 			fBorderDist = screenFrame.right - frame.right;
507 	} else {
508 		// nearest to top or bottom border
509 		if (fScreenPosition.y < 0.5)
510 			fBorderDist = frame.top - screenFrame.top;
511 		else
512 			fBorderDist = screenFrame.bottom - frame.bottom;
513 	}
514 }
515 
516 
517 void
518 MainWindow::_AdjustLocation(BRect frame)
519 {
520 	BScreen screen(this);
521 	BRect screenFrame = screen.Frame();
522 	BPoint center(fScreenPosition.x * screenFrame.Width(),
523 		fScreenPosition.y * screenFrame.Height());
524 	BPoint frameCenter(frame.left + frame.Width() / 2.0,
525 		frame.top + frame.Height() / 2.0);
526 	frame.OffsetBy(center - frameCenter);
527 	// ignore border dist when distance too large
528 	if (fBorderDist < 10.0) {
529 		// see which border we mean depending on screen position
530 		BPoint offset(0.0, 0.0);
531 		if (fabs(0.5 - fScreenPosition.x) > fabs(0.5 - fScreenPosition.y)) {
532 			// left or right border
533 			if (fScreenPosition.x < 0.5)
534 				offset.x = (screenFrame.left + fBorderDist) - frame.left;
535 			else
536 				offset.x = (screenFrame.right - fBorderDist) - frame.right;
537 		} else {
538 			// top or bottom border
539 			if (fScreenPosition.y < 0.5)
540 				offset.y = (screenFrame.top + fBorderDist) - frame.top;
541 			else
542 				offset.y = (screenFrame.bottom - fBorderDist) - frame.bottom;
543 		}
544 		frame.OffsetBy(offset);
545 	}
546 
547 	make_sure_frame_is_on_screen(frame, this);
548 
549 	MoveTo(frame.LeftTop());
550 	ResizeTo(frame.Width(), frame.Height());
551 }
552 
553 
554 void
555 MainWindow::_AddDefaultButtons()
556 {
557 	// Mail
558 	LaunchButton* button = new LaunchButton("launch button", fLastID++, NULL,
559 		new BMessage(MSG_LAUNCH));
560 	fPadView->AddButton(button);
561 	button->SetTo("application/x-vnd.Be-MAIL", true);
562 
563 	// StyledEdit
564 	button = new LaunchButton("launch button", fLastID++, NULL,
565 		new BMessage(MSG_LAUNCH));
566 	fPadView->AddButton(button);
567 	button->SetTo("application/x-vnd.Haiku-StyledEdit", true);
568 
569 	// ShowImage
570 	button = new LaunchButton("launch button", fLastID++, NULL,
571 		new BMessage(MSG_LAUNCH));
572 	fPadView->AddButton(button);
573 	button->SetTo("application/x-vnd.Haiku-ShowImage", true);
574 
575 	// MediaPlayer
576 	button = new LaunchButton("launch button", fLastID++, NULL,
577 		new BMessage(MSG_LAUNCH));
578 	fPadView->AddButton(button);
579 	button->SetTo("application/x-vnd.Haiku-MediaPlayer", true);
580 
581 	// DeskCalc
582 	button = new LaunchButton("launch button", fLastID++, NULL,
583 		new BMessage(MSG_LAUNCH));
584 	fPadView->AddButton(button);
585 	button->SetTo("application/x-vnd.Haiku-DeskCalc", true);
586 
587 	// Terminal
588 	button = new LaunchButton("launch button", fLastID++, NULL,
589 		new BMessage(MSG_LAUNCH));
590 	fPadView->AddButton(button);
591 	button->SetTo("application/x-vnd.Haiku-Terminal", true);
592 }
593 
594 
595 void
596 MainWindow::_AddEmptyButtons()
597 {
598 	LaunchButton* button = new LaunchButton("launch button", fLastID++, NULL,
599 		new BMessage(MSG_LAUNCH));
600 	fPadView->AddButton(button);
601 
602 	button = new LaunchButton("launch button", fLastID++, NULL,
603 		new BMessage(MSG_LAUNCH));
604 	fPadView->AddButton(button);
605 
606 	button = new LaunchButton("launch button", fLastID++, NULL,
607 		new BMessage(MSG_LAUNCH));
608 	fPadView->AddButton(button);
609 }
610 
611 
612 void
613 MainWindow::_NotifySettingsChanged()
614 {
615 	be_app->PostMessage(MSG_SETTINGS_CHANGED);
616 }
617 
618