xref: /haiku/src/preferences/filetypes/FileTypes.cpp (revision f5dd7d3c7b1349b41bec520e7aaae4cda1ea2b3d)
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 
46 			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 
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 
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
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
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
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 
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 
199 FileTypes::~FileTypes()
200 {
201 	delete fFilePanel;
202 }
203 
204 
205 void
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
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
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(&currentDirectory, 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
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
441 FileTypes::QuitRequested()
442 {
443 	return true;
444 }
445 
446 
447 void
448 FileTypes::_WindowClosed()
449 {
450 	if (--fWindowCount == 0 && !fFilePanel->IsShowing())
451 		PostMessage(B_QUIT_REQUESTED);
452 }
453 
454 
455 void
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
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
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
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
537 main(int argc, char** argv)
538 {
539 	FileTypes probe;
540 	probe.Run();
541 	return 0;
542 }
543