1/*
2 * Copyright 2006-2010, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "FileTypes.h"
8#include "FileTypeWindow.h"
9#include "IconView.h"
10#include "PreferredAppMenu.h"
11#include "TypeListWindow.h"
12
13#include <Application.h>
14#include <Bitmap.h>
15#include <Box.h>
16#include <Button.h>
17#include <Catalog.h>
18#include <ControlLook.h>
19#include <File.h>
20#include <LayoutBuilder.h>
21#include <Locale.h>
22#include <MenuField.h>
23#include <MenuItem.h>
24#include <Mime.h>
25#include <NodeInfo.h>
26#include <PopUpMenu.h>
27#include <SpaceLayoutItem.h>
28#include <TextControl.h>
29
30#include <stdio.h>
31
32
33#undef B_TRANSLATION_CONTEXT
34#define B_TRANSLATION_CONTEXT "FileType Window"
35
36
37const uint32 kMsgTypeEntered = 'type';
38const uint32 kMsgSelectType = 'sltp';
39const uint32 kMsgTypeSelected = 'tpsd';
40const uint32 kMsgSameTypeAs = 'stpa';
41const uint32 kMsgSameTypeAsOpened = 'stpO';
42
43const uint32 kMsgPreferredAppChosen = 'papc';
44const uint32 kMsgSelectPreferredApp = 'slpa';
45const uint32 kMsgSamePreferredAppAs = 'spaa';
46const uint32 kMsgPreferredAppOpened = 'paOp';
47const uint32 kMsgSamePreferredAppAsOpened = 'spaO';
48
49
50FileTypeWindow::FileTypeWindow(BPoint position, const BMessage& refs)
51	:
52	BWindow(BRect(0.0f, 0.0f, 300.0f, 200.0f).OffsetBySelf(position),
53		B_TRANSLATE("File type"), B_TITLED_WINDOW,
54		B_NOT_V_RESIZABLE | B_NOT_ZOOMABLE
55			| B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS)
56{
57	float padding = be_control_look->DefaultItemSpacing();
58
59	// "File Type" group
60	BBox* fileTypeBox = new BBox("file type BBox");
61	fileTypeBox->SetLabel(B_TRANSLATE("File type"));
62
63	fTypeControl = new BTextControl("type", NULL, "Type Control",
64		new BMessage(kMsgTypeEntered));
65
66	// filter out invalid characters that can't be part of a MIME type name
67	BTextView* textView = fTypeControl->TextView();
68	const char* disallowedCharacters = "<>@,;:\"()[]?=";
69	for (int32 i = 0; disallowedCharacters[i]; i++) {
70		textView->DisallowChar(disallowedCharacters[i]);
71	}
72
73	fSelectTypeButton = new BButton("select type",
74		B_TRANSLATE("Select" B_UTF8_ELLIPSIS), new BMessage(kMsgSelectType));
75
76	fSameTypeAsButton = new BButton("same type as",
77		B_TRANSLATE_COMMENT("Same as" B_UTF8_ELLIPSIS,
78			"The same TYPE as ..."), new BMessage(kMsgSameTypeAs));
79
80	BLayoutBuilder::Grid<>(fileTypeBox, padding, padding / 2)
81		.SetInsets(padding, padding * 2, padding, padding)
82		.Add(fTypeControl, 0, 0, 3, 1)
83		.Add(fSelectTypeButton, 0, 1)
84		.Add(fSameTypeAsButton, 1, 1);
85
86	// "Icon" group
87
88	BBox* iconBox = new BBox("icon BBox");
89	iconBox->SetLabel(B_TRANSLATE("Icon"));
90	fIconView = new IconView("icon");
91	BLayoutBuilder::Group<>(iconBox, B_HORIZONTAL)
92		.SetInsets(padding, padding * 2, padding, padding)
93		.Add(fIconView);
94
95	// "Preferred Application" group
96
97	BBox* preferredBox = new BBox("preferred BBox");
98	preferredBox->SetLabel(B_TRANSLATE("Preferred application"));
99
100	BMenu* menu = new BPopUpMenu("preferred");
101	BMenuItem* item;
102	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Default application"),
103		new BMessage(kMsgPreferredAppChosen)));
104	item->SetMarked(true);
105
106	fPreferredField = new BMenuField("preferred", NULL, menu);
107
108	fSelectAppButton = new BButton("select app",
109		B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
110		new BMessage(kMsgSelectPreferredApp));
111
112	fSameAppAsButton = new BButton("same app as",
113		B_TRANSLATE_COMMENT("Same as" B_UTF8_ELLIPSIS,
114			"The same APPLICATION as ..."),
115			new BMessage(kMsgSamePreferredAppAs));
116
117	BLayoutBuilder::Grid<>(preferredBox, padding, padding / 2)
118		.SetInsets(padding, padding * 2, padding, padding)
119		.Add(fPreferredField, 0, 0, 2, 1)
120		.Add(fSelectAppButton, 0, 1)
121		.Add(fSameAppAsButton, 1, 1);
122
123	BLayoutBuilder::Grid<>(this)
124		.SetInsets(padding)
125		.Add(fileTypeBox, 0, 0, 2, 1)
126		.Add(preferredBox, 0, 1, 1, 1)
127		.Add(iconBox, 1, 1, 1, 1);
128
129	fTypeControl->MakeFocus(true);
130	BMimeType::StartWatching(this);
131	_SetTo(refs);
132}
133
134
135FileTypeWindow::~FileTypeWindow()
136{
137	BMimeType::StopWatching(this);
138}
139
140
141BString
142FileTypeWindow::_Title(const BMessage& refs)
143{
144	BString title;
145
146	entry_ref ref;
147	if (refs.FindRef("refs", 1, &ref) == B_OK) {
148		bool same = false;
149		BEntry entry, parent;
150		if (entry.SetTo(&ref) == B_OK
151			&& entry.GetParent(&parent) == B_OK) {
152			same = true;
153
154			// Check if all entries have the same parent directory
155			for (int32 i = 0; refs.FindRef("refs", i, &ref) == B_OK; i++) {
156				BEntry directory;
157				if (entry.SetTo(&ref) == B_OK
158					&& entry.GetParent(&directory) == B_OK) {
159					if (directory != parent) {
160						same = false;
161						break;
162					}
163				}
164			}
165		}
166
167		char name[B_FILE_NAME_LENGTH];
168		if (same && parent.GetName(name) == B_OK) {
169			char buffer[512];
170			snprintf(buffer, sizeof(buffer),
171				B_TRANSLATE("Multiple files from \"%s\" file type"), name);
172			title = buffer;
173		} else
174			title = B_TRANSLATE("[Multiple files] file types");
175	} else if (refs.FindRef("refs", 0, &ref) == B_OK) {
176		char buffer[512];
177		snprintf(buffer, sizeof(buffer), B_TRANSLATE("%s file type"), ref.name);
178		title = buffer;
179	}
180
181	return title;
182}
183
184
185void
186FileTypeWindow::_SetTo(const BMessage& refs)
187{
188	SetTitle(_Title(refs).String());
189
190	// get common info and icons
191
192	fCommonPreferredApp = "";
193	fCommonType = "";
194	entry_ref ref;
195	for (int32 i = 0; refs.FindRef("refs", i, &ref) == B_OK; i++) {
196		BNode node(&ref);
197		if (node.InitCheck() != B_OK)
198			continue;
199
200		BNodeInfo info(&node);
201		if (info.InitCheck() != B_OK)
202			continue;
203
204		// TODO: watch entries?
205
206		entry_ref* copiedRef = new entry_ref(ref);
207		fEntries.AddItem(copiedRef);
208
209		char type[B_MIME_TYPE_LENGTH];
210		if (info.GetType(type) != B_OK)
211			type[0] = '\0';
212
213		if (i > 0) {
214			if (fCommonType != type)
215				fCommonType = "";
216		} else
217			fCommonType = type;
218
219		char preferredApp[B_MIME_TYPE_LENGTH];
220		if (info.GetPreferredApp(preferredApp) != B_OK)
221			preferredApp[0] = '\0';
222
223		if (i > 0) {
224			if (fCommonPreferredApp != preferredApp)
225				fCommonPreferredApp = "";
226		} else
227			fCommonPreferredApp = preferredApp;
228
229		if (i == 0)
230			fIconView->SetTo(ref);
231	}
232
233	fTypeControl->SetText(fCommonType.String());
234	_UpdatePreferredApps();
235
236	fIconView->ShowIconHeap(fEntries.CountItems() != 1);
237}
238
239
240void
241FileTypeWindow::_AdoptType(BMessage* message)
242{
243	entry_ref ref;
244	if (message == NULL || message->FindRef("refs", &ref) != B_OK)
245		return;
246
247	BNode node(&ref);
248	status_t status = node.InitCheck();
249
250	char type[B_MIME_TYPE_LENGTH];
251
252	if (status == B_OK) {
253			// get type from file
254		BNodeInfo nodeInfo(&node);
255		status = nodeInfo.InitCheck();
256		if (status == B_OK) {
257			if (nodeInfo.GetType(type) != B_OK)
258				type[0] = '\0';
259		}
260	}
261
262	if (status != B_OK) {
263		error_alert(B_TRANSLATE("Could not open file"), status);
264		return;
265	}
266
267	fCommonType = type;
268	fTypeControl->SetText(type);
269	_AdoptType();
270}
271
272
273void
274FileTypeWindow::_AdoptType()
275{
276	for (int32 i = 0; i < fEntries.CountItems(); i++) {
277		BNode node(fEntries.ItemAt(i));
278		BNodeInfo info(&node);
279		if (node.InitCheck() != B_OK
280			|| info.InitCheck() != B_OK)
281			continue;
282
283		info.SetType(fCommonType.String());
284	}
285}
286
287
288void
289FileTypeWindow::_AdoptPreferredApp(BMessage* message, bool sameAs)
290{
291	if (retrieve_preferred_app(message, sameAs, fCommonType.String(),
292			fCommonPreferredApp) == B_OK) {
293		_AdoptPreferredApp();
294		_UpdatePreferredApps();
295	}
296}
297
298
299void
300FileTypeWindow::_AdoptPreferredApp()
301{
302	for (int32 i = 0; i < fEntries.CountItems(); i++) {
303		BNode node(fEntries.ItemAt(i));
304		if (fCommonPreferredApp.Length() == 0) {
305			node.RemoveAttr("BEOS:PREF_APP");
306			continue;
307		}
308
309		BNodeInfo info(&node);
310		if (node.InitCheck() != B_OK
311			|| info.InitCheck() != B_OK)
312			continue;
313
314		info.SetPreferredApp(fCommonPreferredApp.String());
315	}
316}
317
318
319void
320FileTypeWindow::_UpdatePreferredApps()
321{
322	BMimeType type(fCommonType.String());
323	update_preferred_app_menu(fPreferredField->Menu(), &type,
324		kMsgPreferredAppChosen, fCommonPreferredApp.String());
325}
326
327
328void
329FileTypeWindow::MessageReceived(BMessage* message)
330{
331	switch (message->what) {
332		// File Type group
333
334		case kMsgTypeEntered:
335			fCommonType = fTypeControl->Text();
336			_AdoptType();
337			break;
338
339		case kMsgSelectType:
340		{
341			BWindow* window = new TypeListWindow(fCommonType.String(),
342				kMsgTypeSelected, this);
343			window->Show();
344			break;
345		}
346		case kMsgTypeSelected:
347		{
348			const char* type;
349			if (message->FindString("type", &type) == B_OK) {
350				fCommonType = type;
351				fTypeControl->SetText(type);
352				_AdoptType();
353			}
354			break;
355		}
356
357		case kMsgSameTypeAs:
358		{
359			BMessage panel(kMsgOpenFilePanel);
360			panel.AddString("title", B_TRANSLATE("Select same type as"));
361			panel.AddInt32("message", kMsgSameTypeAsOpened);
362			panel.AddMessenger("target", this);
363			panel.AddBool("allowDirs", true);
364
365			be_app_messenger.SendMessage(&panel);
366			break;
367		}
368		case kMsgSameTypeAsOpened:
369			_AdoptType(message);
370			break;
371
372		// Preferred Application group
373
374		case kMsgPreferredAppChosen:
375		{
376			const char* signature;
377			if (message->FindString("signature", &signature) == B_OK)
378				fCommonPreferredApp = signature;
379			else
380				fCommonPreferredApp = "";
381
382			_AdoptPreferredApp();
383			break;
384		}
385
386		case kMsgSelectPreferredApp:
387		{
388			BMessage panel(kMsgOpenFilePanel);
389			panel.AddString("title",
390				B_TRANSLATE("Select preferred application"));
391			panel.AddInt32("message", kMsgPreferredAppOpened);
392			panel.AddMessenger("target", this);
393
394			be_app_messenger.SendMessage(&panel);
395			break;
396		}
397		case kMsgPreferredAppOpened:
398			_AdoptPreferredApp(message, false);
399			break;
400
401		case kMsgSamePreferredAppAs:
402		{
403			BMessage panel(kMsgOpenFilePanel);
404			panel.AddString("title",
405				B_TRANSLATE("Select same preferred application as"));
406			panel.AddInt32("message", kMsgSamePreferredAppAsOpened);
407			panel.AddMessenger("target", this);
408			panel.AddBool("allowDirs", true);
409
410			be_app_messenger.SendMessage(&panel);
411			break;
412		}
413		case kMsgSamePreferredAppAsOpened:
414			_AdoptPreferredApp(message, true);
415			break;
416
417		// Other
418
419		case B_SIMPLE_DATA:
420		{
421			entry_ref ref;
422			if (message->FindRef("refs", &ref) != B_OK)
423				break;
424
425			BFile file(&ref, B_READ_ONLY);
426			if (is_application(file))
427				_AdoptPreferredApp(message, false);
428			else
429				_AdoptType(message);
430			break;
431		}
432
433		case B_META_MIME_CHANGED:
434			const char* type;
435			int32 which;
436			if (message->FindString("be:type", &type) != B_OK
437				|| message->FindInt32("be:which", &which) != B_OK)
438				break;
439
440			if (which == B_MIME_TYPE_DELETED
441				|| which == B_SUPPORTED_TYPES_CHANGED) {
442				_UpdatePreferredApps();
443			}
444			break;
445
446		default:
447			BWindow::MessageReceived(message);
448	}
449}
450
451
452bool
453FileTypeWindow::QuitRequested()
454{
455	be_app->PostMessage(kMsgTypeWindowClosed);
456	return true;
457}
458
459