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