1 /*
2 * Copyright 2006-2007, Axel Dörfler, axeld@pinc-software.de.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6
7 #include "ApplicationTypesWindow.h"
8 #include "ApplicationTypeWindow.h"
9 #include "FileTypes.h"
10 #include "FileTypesWindow.h"
11 #include "FileTypeWindow.h"
12
13 #include <AppFileInfo.h>
14 #include <Application.h>
15 #include <Alert.h>
16 #include <Catalog.h>
17 #include <Locale.h>
18 #include <TextView.h>
19 #include <FilePanel.h>
20 #include <FindDirectory.h>
21 #include <Directory.h>
22 #include <Entry.h>
23 #include <Path.h>
24 #include <Resources.h>
25 #include <Screen.h>
26
27 #include <stdio.h>
28 #include <strings.h>
29
30
31 #undef B_TRANSLATION_CONTEXT
32 #define B_TRANSLATION_CONTEXT "FileTypes"
33
34
35 const char* kSignature = "application/x-vnd.Haiku-FileTypes";
36
37 static const uint32 kMsgFileTypesSettings = 'FTst';
38 static const uint32 kCascadeOffset = 20;
39
40
41 class Settings {
42 public:
43 Settings();
44 ~Settings();
45
Message() const46 const BMessage& Message() const { return fMessage; }
47 void UpdateFrom(BMessage* message);
48
49 private:
50 void _SetDefaults();
51 status_t _Open(BFile* file, int32 mode);
52
53 BMessage fMessage;
54 bool fUpdated;
55 };
56
57 class FileTypes : public BApplication {
58 public:
59 FileTypes();
60 virtual ~FileTypes();
61
62 virtual void ReadyToRun();
63
64 virtual void RefsReceived(BMessage* message);
65 virtual void ArgvReceived(int32 argc, char** argv);
66 virtual void MessageReceived(BMessage* message);
67
68 virtual bool QuitRequested();
69
70 private:
71 void _WindowClosed();
72
73 // Add one degree of offset to the starting position of the next ApplicationTypeWindow
74 void _AppTypeCascade(BRect lastFrame);
75
76 private:
77 Settings fSettings;
78 BFilePanel* fFilePanel;
79 BMessenger fFilePanelTarget;
80 FileTypesWindow* fTypesWindow;
81 BWindow* fApplicationTypesWindow;
82 uint32 fWindowCount;
83 uint32 fTypeWindowCount;
84 BString fArgvType;
85 };
86
87
Settings()88 Settings::Settings()
89 :
90 fMessage(kMsgFileTypesSettings),
91 fUpdated(false)
92 {
93 _SetDefaults();
94
95 BFile file;
96 if (_Open(&file, B_READ_ONLY) != B_OK)
97 return;
98
99 BMessage settings;
100 if (settings.Unflatten(&file) == B_OK) {
101 // We don't unflatten into our default message to make sure
102 // nothing is lost (because of old or corrupted on disk settings)
103 UpdateFrom(&settings);
104 fUpdated = false;
105 }
106 }
107
108
~Settings()109 Settings::~Settings()
110 {
111 // only save the settings if something has changed
112 if (!fUpdated)
113 return;
114
115 BFile file;
116 if (_Open(&file, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY) != B_OK)
117 return;
118
119 fMessage.Flatten(&file);
120 }
121
122
123 void
UpdateFrom(BMessage * message)124 Settings::UpdateFrom(BMessage* message)
125 {
126 BRect frame;
127 if (message->FindRect("file_types_frame", &frame) == B_OK)
128 fMessage.ReplaceRect("file_types_frame", frame);
129
130 if (message->FindRect("app_types_frame", &frame) == B_OK)
131 fMessage.ReplaceRect("app_types_frame", frame);
132
133 // "app_type_initial_frame" is omitted because it is not meant to be updated
134
135 if (message->FindRect("app_type_next_frame", &frame) == B_OK)
136 fMessage.ReplaceRect("app_type_next_frame", frame);
137
138 bool showIcons;
139 if (message->FindBool("show_icons", &showIcons) == B_OK)
140 fMessage.ReplaceBool("show_icons", showIcons);
141
142 bool showRule;
143 if (message->FindBool("show_rule", &showRule) == B_OK)
144 fMessage.ReplaceBool("show_rule", showRule);
145
146 float splitWeight;
147 if (message->FindFloat("left_split_weight", &splitWeight) == B_OK)
148 fMessage.ReplaceFloat("left_split_weight", splitWeight);
149 if (message->FindFloat("right_split_weight", &splitWeight) == B_OK)
150 fMessage.ReplaceFloat("right_split_weight", splitWeight);
151
152 fUpdated = true;
153 }
154
155
156 void
_SetDefaults()157 Settings::_SetDefaults()
158 {
159 fMessage.AddRect("file_types_frame", BRect(80.0f, 80.0f, 600.0f, 480.0f));
160 fMessage.AddRect("app_types_frame", BRect(100.0f, 100.0f, 540.0f, 480.0f));
161 fMessage.AddRect("app_type_initial_frame", BRect(100.0f, 110.0f, 250.0f, 340.0f));
162 fMessage.AddRect("app_type_next_frame", BRect(100.0f, 110.0f, 250.0f, 340.0f));
163 fMessage.AddBool("show_icons", true);
164 fMessage.AddBool("show_rule", false);
165 fMessage.AddFloat("left_split_weight", 0.2);
166 fMessage.AddFloat("right_split_weight", 0.8);
167 }
168
169
170 status_t
_Open(BFile * file,int32 mode)171 Settings::_Open(BFile* file, int32 mode)
172 {
173 BPath path;
174 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
175 return B_ERROR;
176
177 path.Append("FileTypes settings");
178
179 return file->SetTo(path.Path(), mode);
180 }
181
182
183 // #pragma mark -
184
185
FileTypes()186 FileTypes::FileTypes()
187 :
188 BApplication(kSignature),
189 fTypesWindow(NULL),
190 fApplicationTypesWindow(NULL),
191 fWindowCount(0),
192 fTypeWindowCount(0)
193 {
194 fFilePanel = new BFilePanel(B_OPEN_PANEL, NULL, NULL,
195 B_FILE_NODE, false);
196 }
197
198
~FileTypes()199 FileTypes::~FileTypes()
200 {
201 delete fFilePanel;
202 }
203
204
205 void
ReadyToRun()206 FileTypes::ReadyToRun()
207 {
208 // are there already windows open?
209 if (CountWindows() != 1)
210 return;
211
212 // if not, open the FileTypes window
213 PostMessage(kMsgOpenTypesWindow);
214 }
215
216
217 void
RefsReceived(BMessage * message)218 FileTypes::RefsReceived(BMessage* message)
219 {
220 bool traverseLinks = (modifiers() & B_SHIFT_KEY) == 0;
221
222 // filter out applications and entries we can't open
223 int32 index = 0;
224 entry_ref ref;
225 while (message->FindRef("refs", index++, &ref) == B_OK) {
226 BEntry entry;
227 BFile file;
228
229 status_t status = entry.SetTo(&ref, traverseLinks);
230 if (status == B_OK)
231 status = file.SetTo(&entry, B_READ_ONLY);
232
233 if (status != B_OK) {
234 // file cannot be opened
235
236 char buffer[1024];
237 snprintf(buffer, sizeof(buffer),
238 B_TRANSLATE("Could not open \"%s\":\n"
239 "%s"),
240 ref.name, strerror(status));
241
242 BAlert* alert = new BAlert(B_TRANSLATE("FileTypes request"),
243 buffer, B_TRANSLATE("OK"), NULL, NULL,
244 B_WIDTH_AS_USUAL, B_STOP_ALERT);
245 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
246 alert->Go();
247
248 message->RemoveData("refs", --index);
249 continue;
250 }
251
252 if (!is_application(file) && !is_resource(file)) {
253 entry_ref target;
254 if (entry.GetRef(&target) == B_OK && target != ref)
255 message->ReplaceRef("refs", index - 1, &ref);
256 continue;
257 }
258
259 // remove application from list
260 message->RemoveData("refs", --index);
261
262 // There are some refs left that want to be handled by the type window
263
264 BWindow* window = new ApplicationTypeWindow(fSettings.Message(), entry);
265 _AppTypeCascade(window->Frame());
266 // For accurate height and width, get the frame that results after layouting,
267 // instead of the initial frame that's stored in fSettings.
268 window->Show();
269
270 fTypeWindowCount++;
271 fWindowCount++;
272 }
273
274 if (message->FindRef("refs", &ref) != B_OK)
275 return;
276
277 // There are some refs left that want to be handled by the type window
278 BPoint point(100.0f + kCascadeOffset * fTypeWindowCount,
279 110.0f + kCascadeOffset * fTypeWindowCount);
280
281 BWindow* window = new FileTypeWindow(point, *message);
282 window->Show();
283
284 fTypeWindowCount++;
285 fWindowCount++;
286 }
287
288
289 void
ArgvReceived(int32 argc,char ** argv)290 FileTypes::ArgvReceived(int32 argc, char** argv)
291 {
292 if (argc == 3 && strcmp(argv[1], "-type") == 0) {
293 fArgvType = argv[2];
294 return;
295 }
296
297 BMessage* message = CurrentMessage();
298
299 BDirectory currentDirectory;
300 if (message != NULL)
301 currentDirectory.SetTo(message->FindString("cwd"));
302
303 BMessage refs;
304
305 for (int i = 1 ; i < argc ; i++) {
306 BPath path;
307 if (argv[i][0] == '/')
308 path.SetTo(argv[i]);
309 else
310 path.SetTo(¤tDirectory, argv[i]);
311
312 status_t status;
313 entry_ref ref;
314 BEntry entry;
315
316 if ((status = entry.SetTo(path.Path(), false)) != B_OK
317 || (status = entry.GetRef(&ref)) != B_OK) {
318 fprintf(stderr, "Could not open file \"%s\": %s\n",
319 path.Path(), strerror(status));
320 continue;
321 }
322
323 refs.AddRef("refs", &ref);
324 }
325
326 RefsReceived(&refs);
327 }
328
329
330 void
MessageReceived(BMessage * message)331 FileTypes::MessageReceived(BMessage* message)
332 {
333 switch (message->what) {
334 case kMsgSettingsChanged:
335 fSettings.UpdateFrom(message);
336 break;
337
338 case kMsgOpenTypesWindow:
339 if (fTypesWindow == NULL) {
340 fTypesWindow = new FileTypesWindow(fSettings.Message());
341 if (fArgvType.Length() > 0) {
342 // Set the window to the type that was requested on the
343 // command line (-type), we do this only once, if we
344 // ever opened more than one FileTypesWindow.
345 fTypesWindow->SelectType(fArgvType.String());
346 fArgvType = "";
347 }
348 fTypesWindow->Show();
349 fWindowCount++;
350 } else
351 fTypesWindow->Activate(true);
352 break;
353 case kMsgTypesWindowClosed:
354 fTypesWindow = NULL;
355 _WindowClosed();
356 break;
357
358 case kMsgOpenApplicationTypesWindow:
359 if (fApplicationTypesWindow == NULL) {
360 fApplicationTypesWindow = new ApplicationTypesWindow(
361 fSettings.Message());
362 fApplicationTypesWindow->Show();
363 fWindowCount++;
364 } else
365 fApplicationTypesWindow->Activate(true);
366 break;
367 case kMsgApplicationTypesWindowClosed:
368 fApplicationTypesWindow = NULL;
369 _WindowClosed();
370 break;
371
372 case kMsgTypeWindowClosed:
373 fTypeWindowCount--;
374 // supposed to fall through
375
376 case kMsgWindowClosed:
377 _WindowClosed();
378 break;
379
380
381 case kMsgOpenFilePanel:
382 {
383 // the open file panel sends us a message when it's done
384 const char* subTitle;
385 if (message->FindString("title", &subTitle) != B_OK)
386 subTitle = B_TRANSLATE("Open file");
387
388 int32 what;
389 if (message->FindInt32("message", &what) != B_OK)
390 what = B_REFS_RECEIVED;
391
392 BMessenger target;
393 if (message->FindMessenger("target", &target) != B_OK)
394 target = be_app_messenger;
395
396 BString title = B_TRANSLATE("FileTypes");
397 if (subTitle != NULL && subTitle[0]) {
398 title.Append(": ");
399 title.Append(subTitle);
400 }
401
402 uint32 flavors = B_FILE_NODE;
403 if (message->FindBool("allowDirs"))
404 flavors |= B_DIRECTORY_NODE;
405 fFilePanel->SetNodeFlavors(flavors);
406
407
408 fFilePanel->SetMessage(new BMessage(what));
409 fFilePanel->Window()->SetTitle(title.String());
410 fFilePanel->SetTarget(target);
411
412 if (!fFilePanel->IsShowing())
413 fFilePanel->Show();
414 break;
415 }
416
417 case B_SILENT_RELAUNCH:
418 // In case we were launched via the add-on, there is no types
419 // window yet.
420 if (fTypesWindow == NULL)
421 PostMessage(kMsgOpenTypesWindow);
422 break;
423
424 case B_CANCEL:
425 if (fWindowCount == 0)
426 PostMessage(B_QUIT_REQUESTED);
427 break;
428
429 case B_SIMPLE_DATA:
430 RefsReceived(message);
431 break;
432
433 default:
434 BApplication::MessageReceived(message);
435 break;
436 }
437 }
438
439
440 bool
QuitRequested()441 FileTypes::QuitRequested()
442 {
443 return true;
444 }
445
446
447 void
_WindowClosed()448 FileTypes::_WindowClosed()
449 {
450 if (--fWindowCount == 0 && !fFilePanel->IsShowing())
451 PostMessage(B_QUIT_REQUESTED);
452 }
453
454
455 void
_AppTypeCascade(BRect lastFrame)456 FileTypes::_AppTypeCascade(BRect lastFrame)
457 {
458 BScreen screen;
459 BRect screenBorder = screen.Frame();
460 BRect initFrame;
461
462 float left = lastFrame.left + kCascadeOffset;
463 if (left + lastFrame.Width() > screenBorder.right) {
464 // If about to cascade off the right edge of the screen, revert the horizontal
465 // position to that of the first window.
466 if (fSettings.Message().FindRect("app_type_initial_frame", &initFrame) == B_OK)
467 left = initFrame.LeftTop().x;
468 }
469
470 float top = lastFrame.top + kCascadeOffset;
471 if (top + lastFrame.Height() > screenBorder.bottom) {
472 if (fSettings.Message().FindRect("app_type_initial_frame", &initFrame) == B_OK)
473 top = initFrame.LeftTop().y;
474 }
475
476 lastFrame.OffsetTo(BPoint(left, top));
477 BMessage update(kMsgSettingsChanged);
478 update.AddRect("app_type_next_frame", lastFrame);
479 fSettings.UpdateFrom(&update);
480 }
481
482
483 // #pragma mark -
484
485
486 bool
is_application(BFile & file)487 is_application(BFile& file)
488 {
489 BAppFileInfo appInfo(&file);
490 if (appInfo.InitCheck() != B_OK)
491 return false;
492
493 char type[B_MIME_TYPE_LENGTH];
494 if (appInfo.GetType(type) != B_OK
495 || strcasecmp(type, B_APP_MIME_TYPE))
496 return false;
497
498 return true;
499 }
500
501
502 bool
is_resource(BFile & file)503 is_resource(BFile& file)
504 {
505 BResources resources(&file);
506 if (resources.InitCheck() != B_OK)
507 return false;
508
509 BNodeInfo nodeInfo(&file);
510 char type[B_MIME_TYPE_LENGTH];
511 if (nodeInfo.GetType(type) != B_OK
512 || strcasecmp(type, B_RESOURCE_MIME_TYPE))
513 return false;
514
515 return true;
516 }
517
518
519 void
error_alert(const char * message,status_t status,alert_type type)520 error_alert(const char* message, status_t status, alert_type type)
521 {
522 char warning[512];
523 if (status != B_OK) {
524 snprintf(warning, sizeof(warning), "%s:\n\t%s\n", message,
525 strerror(status));
526 }
527
528 BAlert* alert = new BAlert(B_TRANSLATE("FileTypes request"),
529 status == B_OK ? message : warning,
530 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, type);
531 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
532 alert->Go();
533 }
534
535
536 int
main(int argc,char ** argv)537 main(int argc, char** argv)
538 {
539 FileTypes probe;
540 probe.Run();
541 return 0;
542 }
543