xref: /haiku/src/apps/launchbox/MainWindow.cpp (revision e2932f63b00ba76ab3769a9c217754a4d03868ca)
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 	fNamePanelFrame(-1000.0, -1000.0, -800.0, -900.0),
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 	fNamePanelFrame(-1000.0, -1000.0, -900.0, -900.0),
69 	fAutoRaise(false),
70 	fShowOnAllWorkspaces(true)
71 {
72 	if (!LoadSettings(settings))
73 		_AddEmptyButtons();
74 
75 	SetLayout(new BGroupLayout(B_HORIZONTAL));
76 	AddChild(fPadView);
77 }
78 
79 
80 MainWindow::~MainWindow()
81 {
82 	delete fSettings;
83 }
84 
85 
86 bool
87 MainWindow::QuitRequested()
88 {
89 	int32 padWindowCount = 0;
90 	for (int32 i = 0; BWindow* window = be_app->WindowAt(i); i++) {
91 		if (dynamic_cast<MainWindow*>(window))
92 			padWindowCount++;
93 	}
94 	if (padWindowCount == 1) {
95 		be_app->PostMessage(B_QUIT_REQUESTED);
96 		return false;
97 	} else {
98 		BAlert* alert = new BAlert(B_TRANSLATE("last chance"),
99 			B_TRANSLATE("Really close this pad?\n"
100 						"(The pad will not be remembered.)"),
101 			B_TRANSLATE("Close"), B_TRANSLATE("Cancel"), NULL);
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->Go(NULL);
177 			}
178 			break;
179 		}
180 		case MSG_ADD_SLOT: {
181 			LaunchButton* button;
182 			if (message->FindPointer("be:source", (void**)&button) >= B_OK) {
183 				fPadView->AddButton(new LaunchButton("launch button",
184 					NULL, new BMessage(MSG_LAUNCH)), button);
185 			}
186 			break;
187 		}
188 		case MSG_CLEAR_SLOT: {
189 			LaunchButton* button;
190 			if (message->FindPointer("be:source", (void**)&button) >= B_OK)
191 				button->SetTo((entry_ref*)NULL);
192 			break;
193 		}
194 		case MSG_REMOVE_SLOT: {
195 			LaunchButton* button;
196 			if (message->FindPointer("be:source", (void**)&button) >= B_OK) {
197 				if (fPadView->RemoveButton(button))
198 					delete button;
199 			}
200 			break;
201 		}
202 		case MSG_SET_DESCRIPTION: {
203 			LaunchButton* button;
204 			if (message->FindPointer("be:source", (void**)&button) >= B_OK) {
205 				const char* name;
206 				if (message->FindString("name", &name) >= B_OK) {
207 					// message comes from a previous name panel
208 					button->SetDescription(name);
209 					message->FindRect("frame", &fNamePanelFrame);
210 				} else {
211 					// message comes from pad view
212 					entry_ref* ref = button->Ref();
213 					if (ref) {
214 						BString helper(B_TRANSLATE("Description for '%3'"));
215 						helper.ReplaceFirst("%3", ref->name);
216 						make_sure_frame_is_on_screen(fNamePanelFrame, this);
217 						new NamePanel(helper.String(), button->Description(),
218 							this, this, new BMessage(*message),
219 							fNamePanelFrame);
220 					}
221 				}
222 			}
223 			break;
224 		}
225 		case MSG_ADD_WINDOW: {
226 			BMessage settings('sett');
227 			SaveSettings(&settings);
228 			message->AddMessage("window", &settings);
229 			be_app->PostMessage(message);
230 			break;
231 		}
232 		case MSG_SHOW_BORDER:
233 			SetLook(B_TITLED_WINDOW_LOOK);
234 			break;
235 		case MSG_HIDE_BORDER:
236 			SetLook(B_BORDERED_WINDOW_LOOK);
237 			break;
238 		case MSG_TOGGLE_AUTORAISE:
239 			ToggleAutoRaise();
240 			break;
241 		case MSG_SHOW_ON_ALL_WORKSPACES:
242 			fShowOnAllWorkspaces = !fShowOnAllWorkspaces;
243 			if (fShowOnAllWorkspaces)
244 				SetWorkspaces(B_ALL_WORKSPACES);
245 			else
246 				SetWorkspaces(1L << current_workspace());
247 			break;
248 		case B_SIMPLE_DATA:
249 		case B_REFS_RECEIVED:
250 		case B_PASTE:
251 		case B_MODIFIERS_CHANGED:
252 			break;
253 		default:
254 			BWindow::MessageReceived(message);
255 			break;
256 	}
257 }
258 
259 
260 void
261 MainWindow::Show()
262 {
263 	BWindow::Show();
264 	_GetLocation();
265 }
266 
267 
268 void
269 MainWindow::ScreenChanged(BRect frame, color_space format)
270 {
271 	_AdjustLocation(Frame());
272 }
273 
274 
275 void
276 MainWindow::WorkspaceActivated(int32 workspace, bool active)
277 {
278 	if (fShowOnAllWorkspaces) {
279 		if (!active)
280 			_GetLocation();
281 		else
282 			_AdjustLocation(Frame());
283 	}
284 }
285 
286 
287 void
288 MainWindow::FrameMoved(BPoint origin)
289 {
290 	if (IsActive()) {
291 		_GetLocation();
292 		_NotifySettingsChanged();
293 	}
294 }
295 
296 
297 void
298 MainWindow::FrameResized(float width, float height)
299 {
300 	if (IsActive()) {
301 		_GetLocation();
302 		_NotifySettingsChanged();
303 	}
304 	BWindow::FrameResized(width, height);
305 }
306 
307 
308 void
309 MainWindow::ToggleAutoRaise()
310 {
311 	fAutoRaise = !fAutoRaise;
312 	if (fAutoRaise)
313 		fPadView->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
314 	else
315 		fPadView->SetEventMask(0);
316 
317 	_NotifySettingsChanged();
318 }
319 
320 
321 bool
322 MainWindow::LoadSettings(const BMessage* message)
323 {
324 	// restore window positioning
325 	BPoint point;
326 	bool useAdjust = false;
327 	if (message->FindPoint("window position", &point) == B_OK) {
328 		fScreenPosition = point;
329 		useAdjust = true;
330 	}
331 	float borderDist;
332 	if (message->FindFloat("border distance", &borderDist) == B_OK) {
333 		fBorderDist = borderDist;
334 	}
335 	// restore window frame
336 	BRect frame;
337 	if (message->FindRect("window frame", &frame) == B_OK) {
338 		if (useAdjust) {
339 			_AdjustLocation(frame);
340 		} else {
341 			make_sure_frame_is_on_screen(frame, this);
342 			MoveTo(frame.LeftTop());
343 			ResizeTo(frame.Width(), frame.Height());
344 		}
345 	}
346 
347 	// restore name panel frame
348 	if (message->FindRect("name panel frame", &frame) == B_OK) {
349 		if (frame.IsValid()) {
350 			make_sure_frame_is_on_screen(frame, this);
351 			fNamePanelFrame = frame;
352 		}
353 	}
354 
355 	// restore window look
356 	window_look look;
357 	if (message->FindInt32("window look", (int32*)&look) == B_OK)
358 		SetLook(look);
359 
360 	// restore orientation
361 	int32 orientation;
362 	if (message->FindInt32("orientation", &orientation) == B_OK)
363 		fPadView->SetOrientation((enum orientation)orientation);
364 
365 	// restore icon size
366 	int32 iconSize;
367 	if (message->FindInt32("icon size", &iconSize) == B_OK)
368 		fPadView->SetIconSize(iconSize);
369 
370 	// restore ignore double click
371 	bool ignoreDoubleClick;
372 	if (message->FindBool("ignore double click", &ignoreDoubleClick) == B_OK)
373 		fPadView->SetIgnoreDoubleClick(ignoreDoubleClick);
374 
375 	// restore buttons
376 	const char* path;
377 	bool buttonAdded = false;
378 	for (int32 i = 0; message->FindString("path", i, &path) >= B_OK; i++) {
379 		LaunchButton* button = new LaunchButton("launch button",
380 			NULL, new BMessage(MSG_LAUNCH));
381 		fPadView->AddButton(button);
382 		BString signature;
383 		if (message->FindString("signature", i, &signature) >= B_OK
384 			&& signature.CountChars() > 0)  {
385 			button->SetTo(signature.String(), true);
386 		}
387 
388 		BEntry entry(path, true);
389 		entry_ref ref;
390 		if (entry.Exists() && entry.GetRef(&ref) == B_OK)
391 			button->SetTo(&ref);
392 
393 		const char* text;
394 		if (message->FindString("description", i, &text) >= B_OK)
395 			button->SetDescription(text);
396 		buttonAdded = true;
397 	}
398 
399 	// restore auto raise setting
400 	bool autoRaise;
401 	if (message->FindBool("auto raise", &autoRaise) == B_OK && autoRaise)
402 		ToggleAutoRaise();
403 
404 	// store workspace setting
405 	bool showOnAllWorkspaces;
406 	if (message->FindBool("all workspaces", &showOnAllWorkspaces) == B_OK)
407 		fShowOnAllWorkspaces = showOnAllWorkspaces;
408 		SetWorkspaces(showOnAllWorkspaces
409 			? B_ALL_WORKSPACES : 1L << current_workspace());
410 	if (!fShowOnAllWorkspaces) {
411 		uint32 workspaces;
412 		if (message->FindInt32("workspaces", (int32*)&workspaces) == B_OK)
413 			SetWorkspaces(workspaces);
414 	}
415 
416 	return buttonAdded;
417 }
418 
419 
420 void
421 MainWindow::SaveSettings(BMessage* message)
422 {
423 	// make sure the positioning info is correct
424 	_GetLocation();
425 	// store window position
426 	if (message->ReplacePoint("window position", fScreenPosition) != B_OK)
427 		message->AddPoint("window position", fScreenPosition);
428 
429 	if (message->ReplaceFloat("border distance", fBorderDist) != B_OK)
430 		message->AddFloat("border distance", fBorderDist);
431 
432 	// store window frame
433 	if (message->ReplaceRect("window frame", Frame()) != B_OK)
434 		message->AddRect("window frame", Frame());
435 
436 	// store name panel frame
437 	if (message->ReplaceRect("name panel frame", fNamePanelFrame) != B_OK)
438 		message->AddRect("name panel frame", fNamePanelFrame);
439 
440 	if (message->ReplaceInt32("window look", Look()) != B_OK)
441 		message->AddInt32("window look", Look());
442 
443 	// store orientation
444 	if (message->ReplaceInt32("orientation",
445 			(int32)fPadView->Orientation()) != B_OK)
446 		message->AddInt32("orientation", (int32)fPadView->Orientation());
447 
448 	// store icon size
449 	if (message->ReplaceInt32("icon size", fPadView->IconSize()) != B_OK)
450 		message->AddInt32("icon size", fPadView->IconSize());
451 
452 	// store ignore double click
453 	if (message->ReplaceBool("ignore double click",
454 			fPadView->IgnoreDoubleClick()) != B_OK) {
455 		message->AddBool("ignore double click", fPadView->IgnoreDoubleClick());
456 	}
457 
458 	// store buttons
459 	message->RemoveName("path");
460 	message->RemoveName("description");
461 	message->RemoveName("signature");
462 	for (int32 i = 0; LaunchButton* button = fPadView->ButtonAt(i); i++) {
463 		BPath path(button->Ref());
464 		if (path.InitCheck() >= B_OK)
465 			message->AddString("path", path.Path());
466 		else
467 			message->AddString("path", "");
468 		message->AddString("description", button->Description());
469 
470 		if (button->AppSignature())
471 			message->AddString("signature", button->AppSignature());
472 		else
473 			message->AddString("signature", "");
474 	}
475 
476 	// store auto raise setting
477 	if (message->ReplaceBool("auto raise", fAutoRaise) != B_OK)
478 		message->AddBool("auto raise", fAutoRaise);
479 
480 	// store workspace setting
481 	if (message->ReplaceBool("all workspaces", fShowOnAllWorkspaces) != B_OK)
482 		message->AddBool("all workspaces", fShowOnAllWorkspaces);
483 	if (message->ReplaceInt32("workspaces", Workspaces()) != B_OK)
484 		message->AddInt32("workspaces", Workspaces());
485 }
486 
487 
488 void
489 MainWindow::_GetLocation()
490 {
491 	BRect frame = Frame();
492 	BPoint origin = frame.LeftTop();
493 	BPoint center(origin.x + frame.Width() / 2.0,
494 		origin.y + frame.Height() / 2.0);
495 	BScreen screen(this);
496 	BRect screenFrame = screen.Frame();
497 	fScreenPosition.x = center.x / screenFrame.Width();
498 	fScreenPosition.y = center.y / screenFrame.Height();
499 	if (fabs(0.5 - fScreenPosition.x) > fabs(0.5 - fScreenPosition.y)) {
500 		// nearest to left or right border
501 		if (fScreenPosition.x < 0.5)
502 			fBorderDist = frame.left - screenFrame.left;
503 		else
504 			fBorderDist = screenFrame.right - frame.right;
505 	} else {
506 		// nearest to top or bottom border
507 		if (fScreenPosition.y < 0.5)
508 			fBorderDist = frame.top - screenFrame.top;
509 		else
510 			fBorderDist = screenFrame.bottom - frame.bottom;
511 	}
512 }
513 
514 
515 void
516 MainWindow::_AdjustLocation(BRect frame)
517 {
518 	BScreen screen(this);
519 	BRect screenFrame = screen.Frame();
520 	BPoint center(fScreenPosition.x * screenFrame.Width(),
521 		fScreenPosition.y * screenFrame.Height());
522 	BPoint frameCenter(frame.left + frame.Width() / 2.0,
523 		frame.top + frame.Height() / 2.0);
524 	frame.OffsetBy(center - frameCenter);
525 	// ignore border dist when distance too large
526 	if (fBorderDist < 10.0) {
527 		// see which border we mean depending on screen position
528 		BPoint offset(0.0, 0.0);
529 		if (fabs(0.5 - fScreenPosition.x) > fabs(0.5 - fScreenPosition.y)) {
530 			// left or right border
531 			if (fScreenPosition.x < 0.5)
532 				offset.x = (screenFrame.left + fBorderDist) - frame.left;
533 			else
534 				offset.x = (screenFrame.right - fBorderDist) - frame.right;
535 		} else {
536 			// top or bottom border
537 			if (fScreenPosition.y < 0.5)
538 				offset.y = (screenFrame.top + fBorderDist) - frame.top;
539 			else
540 				offset.y = (screenFrame.bottom - fBorderDist) - frame.bottom;
541 		}
542 		frame.OffsetBy(offset);
543 	}
544 
545 	make_sure_frame_is_on_screen(frame, this);
546 
547 	MoveTo(frame.LeftTop());
548 	ResizeTo(frame.Width(), frame.Height());
549 }
550 
551 
552 void
553 MainWindow::_AddDefaultButtons()
554 {
555 	// Mail
556 	LaunchButton* button = new LaunchButton("launch button", NULL,
557 		new BMessage(MSG_LAUNCH));
558 	fPadView->AddButton(button);
559 	button->SetTo("application/x-vnd.Be-MAIL", true);
560 
561 	// StyledEdit
562 	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
563 	fPadView->AddButton(button);
564 	button->SetTo("application/x-vnd.Haiku-StyledEdit", true);
565 
566 	// ShowImage
567 	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
568 	fPadView->AddButton(button);
569 	button->SetTo("application/x-vnd.Haiku-ShowImage", true);
570 
571 	// MediaPlayer
572 	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
573 	fPadView->AddButton(button);
574 	button->SetTo("application/x-vnd.Haiku-MediaPlayer", true);
575 
576 	// DeskCalc
577 	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
578 	fPadView->AddButton(button);
579 	button->SetTo("application/x-vnd.Haiku-DeskCalc", true);
580 
581 	// Terminal
582 	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
583 	fPadView->AddButton(button);
584 	button->SetTo("application/x-vnd.Haiku-Terminal", true);
585 }
586 
587 
588 void
589 MainWindow::_AddEmptyButtons()
590 {
591 	LaunchButton* button = new LaunchButton("launch button", NULL,
592 		new BMessage(MSG_LAUNCH));
593 	fPadView->AddButton(button);
594 
595 	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
596 	fPadView->AddButton(button);
597 
598 	button = new LaunchButton("launch button", NULL, new BMessage(MSG_LAUNCH));
599 	fPadView->AddButton(button);
600 }
601 
602 
603 void
604 MainWindow::_NotifySettingsChanged()
605 {
606 	be_app->PostMessage(MSG_SETTINGS_CHANGED);
607 }
608 
609