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
34const char* kSignature = "application/x-vnd.Haiku-FileTypes";
35
36static const uint32 kMsgFileTypesSettings = 'FTst';
37static const uint32 kCascadeOffset = 20;
38
39
40class Settings {
41public:
42								Settings();
43								~Settings();
44
45			const BMessage&		Message() const { return fMessage; }
46			void				UpdateFrom(BMessage* message);
47
48private:
49			void				_SetDefaults();
50			status_t			_Open(BFile* file, int32 mode);
51
52			BMessage			fMessage;
53			bool				fUpdated;
54};
55
56class FileTypes : public BApplication {
57public:
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
69private:
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
83Settings::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
104Settings::~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
118void
119Settings::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
146void
147Settings::_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
158status_t
159Settings::_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
174FileTypes::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
187FileTypes::~FileTypes()
188{
189	delete fFilePanel;
190}
191
192
193void
194FileTypes::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
205void
206FileTypes::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
276void
277FileTypes::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
317void
318FileTypes::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
427bool
428FileTypes::QuitRequested()
429{
430	return true;
431}
432
433
434void
435FileTypes::_WindowClosed()
436{
437	if (--fWindowCount == 0 && !fFilePanel->IsShowing())
438		PostMessage(B_QUIT_REQUESTED);
439}
440
441
442//	#pragma mark -
443
444
445bool
446is_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
461bool
462is_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
478void
479error_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
495int
496main(int argc, char** argv)
497{
498	FileTypes probe;
499	probe.Run();
500	return 0;
501}
502