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