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 <string.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 | B_DIRECTORY_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			if (entry.GetRef(&ref) == B_OK)
242				message->ReplaceRef("refs", index - 1, &ref);
243			continue;
244		}
245
246		// remove application from list
247		message->RemoveData("refs", --index);
248
249		// There are some refs left that want to be handled by the type window
250		BPoint point(100.0f + kCascadeOffset * fTypeWindowCount,
251			110.0f + kCascadeOffset * fTypeWindowCount);
252
253		BWindow* window = new ApplicationTypeWindow(point, entry);
254		window->Show();
255
256		fTypeWindowCount++;
257		fWindowCount++;
258	}
259
260	if (message->FindRef("refs", &ref) != B_OK)
261		return;
262
263	// There are some refs left that want to be handled by the type window
264	BPoint point(100.0f + kCascadeOffset * fTypeWindowCount,
265		110.0f + kCascadeOffset * fTypeWindowCount);
266
267	BWindow* window = new FileTypeWindow(point, *message);
268	window->Show();
269
270	fTypeWindowCount++;
271	fWindowCount++;
272}
273
274
275void
276FileTypes::ArgvReceived(int32 argc, char** argv)
277{
278	if (argc == 3 && strcmp(argv[1], "-type") == 0) {
279		fArgvType = argv[2];
280		return;
281	}
282
283	BMessage* message = CurrentMessage();
284
285	BDirectory currentDirectory;
286	if (message != NULL)
287		currentDirectory.SetTo(message->FindString("cwd"));
288
289	BMessage refs;
290
291	for (int i = 1 ; i < argc ; i++) {
292		BPath path;
293		if (argv[i][0] == '/')
294			path.SetTo(argv[i]);
295		else
296			path.SetTo(&currentDirectory, argv[i]);
297
298		status_t status;
299		entry_ref ref;
300		BEntry entry;
301
302		if ((status = entry.SetTo(path.Path(), false)) != B_OK
303			|| (status = entry.GetRef(&ref)) != B_OK) {
304			fprintf(stderr, "Could not open file \"%s\": %s\n",
305				path.Path(), strerror(status));
306			continue;
307		}
308
309		refs.AddRef("refs", &ref);
310	}
311
312	RefsReceived(&refs);
313}
314
315
316void
317FileTypes::MessageReceived(BMessage* message)
318{
319	switch (message->what) {
320		case kMsgSettingsChanged:
321			fSettings.UpdateFrom(message);
322			break;
323
324		case kMsgOpenTypesWindow:
325			if (fTypesWindow == NULL) {
326				fTypesWindow = new FileTypesWindow(fSettings.Message());
327				if (fArgvType.Length() > 0) {
328					// Set the window to the type that was requested on the
329					// command line (-type), we do this only once, if we
330					// ever opened more than one FileTypesWindow.
331					fTypesWindow->SelectType(fArgvType.String());
332					fArgvType = "";
333				}
334				fTypesWindow->Show();
335				fWindowCount++;
336			} else
337				fTypesWindow->Activate(true);
338			break;
339		case kMsgTypesWindowClosed:
340			fTypesWindow = NULL;
341			_WindowClosed();
342			break;
343
344		case kMsgOpenApplicationTypesWindow:
345			if (fApplicationTypesWindow == NULL) {
346				fApplicationTypesWindow = new ApplicationTypesWindow(
347					fSettings.Message());
348				fApplicationTypesWindow->Show();
349				fWindowCount++;
350			} else
351				fApplicationTypesWindow->Activate(true);
352			break;
353		case kMsgApplicationTypesWindowClosed:
354			fApplicationTypesWindow = NULL;
355			_WindowClosed();
356			break;
357
358		case kMsgTypeWindowClosed:
359			fTypeWindowCount--;
360			// supposed to fall through
361
362		case kMsgWindowClosed:
363			_WindowClosed();
364			break;
365
366
367		case kMsgOpenFilePanel:
368		{
369			// the open file panel sends us a message when it's done
370			const char* subTitle;
371			if (message->FindString("title", &subTitle) != B_OK)
372				subTitle = B_TRANSLATE("Open file");
373
374			int32 what;
375			if (message->FindInt32("message", &what) != B_OK)
376				what = B_REFS_RECEIVED;
377
378			BMessenger target;
379			if (message->FindMessenger("target", &target) != B_OK)
380				target = be_app_messenger;
381
382			BString title = B_TRANSLATE("FileTypes");
383			if (subTitle != NULL && subTitle[0]) {
384				title.Append(": ");
385				title.Append(subTitle);
386			}
387
388			fFilePanel->SetMessage(new BMessage(what));
389			fFilePanel->Window()->SetTitle(title.String());
390			fFilePanel->SetTarget(target);
391
392			if (!fFilePanel->IsShowing())
393				fFilePanel->Show();
394			break;
395		}
396
397		case B_SILENT_RELAUNCH:
398			// In case we were launched via the add-on, there is no types
399			// window yet.
400			if (fTypesWindow == NULL)
401				PostMessage(kMsgOpenTypesWindow);
402			break;
403
404		case B_CANCEL:
405			if (fWindowCount == 0)
406				PostMessage(B_QUIT_REQUESTED);
407			break;
408
409		case B_SIMPLE_DATA:
410			RefsReceived(message);
411			break;
412
413		default:
414			BApplication::MessageReceived(message);
415			break;
416	}
417}
418
419
420bool
421FileTypes::QuitRequested()
422{
423	return true;
424}
425
426
427void
428FileTypes::_WindowClosed()
429{
430	if (--fWindowCount == 0 && !fFilePanel->IsShowing())
431		PostMessage(B_QUIT_REQUESTED);
432}
433
434
435//	#pragma mark -
436
437
438bool
439is_application(BFile& file)
440{
441	BAppFileInfo appInfo(&file);
442	if (appInfo.InitCheck() != B_OK)
443		return false;
444
445	char type[B_MIME_TYPE_LENGTH];
446	if (appInfo.GetType(type) != B_OK
447		|| strcasecmp(type, B_APP_MIME_TYPE))
448		return false;
449
450	return true;
451}
452
453
454bool
455is_resource(BFile& file)
456{
457	BResources resources(&file);
458	if (resources.InitCheck() != B_OK)
459		return false;
460
461	BNodeInfo nodeInfo(&file);
462	char type[B_MIME_TYPE_LENGTH];
463	if (nodeInfo.GetType(type) != B_OK
464		|| strcasecmp(type, B_RESOURCE_MIME_TYPE))
465		return false;
466
467	return true;
468}
469
470
471void
472error_alert(const char* message, status_t status, alert_type type)
473{
474	char warning[512];
475	if (status != B_OK) {
476		snprintf(warning, sizeof(warning), "%s:\n\t%s\n", message,
477			strerror(status));
478	}
479
480	BAlert* alert = new BAlert(B_TRANSLATE("FileTypes request"),
481		status == B_OK ? message : warning,
482		B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, type);
483		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
484		alert->Go();
485}
486
487
488int
489main(int argc, char** argv)
490{
491	FileTypes probe;
492	probe.Run();
493	return 0;
494}
495