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