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
35const char* kSignature = "application/x-vnd.Haiku-FileTypes";
36
37static const uint32 kMsgFileTypesSettings = 'FTst';
38static const uint32 kCascadeOffset = 20;
39
40
41class Settings {
42public:
43								Settings();
44								~Settings();
45
46			const BMessage&		Message() const { return fMessage; }
47			void				UpdateFrom(BMessage* message);
48
49private:
50			void				_SetDefaults();
51			status_t			_Open(BFile* file, int32 mode);
52
53			BMessage			fMessage;
54			bool				fUpdated;
55};
56
57class FileTypes : public BApplication {
58public:
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
70private:
71			void				_WindowClosed();
72
73			// Add one degree of offset to the starting position of the next ApplicationTypeWindow
74			void				_AppTypeCascade(BRect lastFrame);
75
76private:
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
88Settings::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
109Settings::~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
123void
124Settings::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
156void
157Settings::_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
170status_t
171Settings::_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
186FileTypes::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
199FileTypes::~FileTypes()
200{
201	delete fFilePanel;
202}
203
204
205void
206FileTypes::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
217void
218FileTypes::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
289void
290FileTypes::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
330void
331FileTypes::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
440bool
441FileTypes::QuitRequested()
442{
443	return true;
444}
445
446
447void
448FileTypes::_WindowClosed()
449{
450	if (--fWindowCount == 0 && !fFilePanel->IsShowing())
451		PostMessage(B_QUIT_REQUESTED);
452}
453
454
455void
456FileTypes::_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
486bool
487is_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
502bool
503is_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
519void
520error_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
536int
537main(int argc, char** argv)
538{
539	FileTypes probe;
540	probe.Run();
541	return 0;
542}
543