1/*
2 * Copyright 2006, Axel D��rfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "FileTypes.h"
8#include "PreferredAppMenu.h"
9
10#include <Alert.h>
11#include <AppFileInfo.h>
12#include <Catalog.h>
13#include <Locale.h>
14#include <Menu.h>
15#include <MenuItem.h>
16#include <Mime.h>
17#include <NodeInfo.h>
18#include <String.h>
19
20#include <stdio.h>
21#include <strings.h>
22
23
24#undef B_TRANSLATION_CONTEXT
25#define B_TRANSLATION_CONTEXT "Preferred App Menu"
26
27
28static int
29compare_menu_items(const void* _a, const void* _b)
30{
31	BMenuItem* a = *(BMenuItem**)_a;
32	BMenuItem* b = *(BMenuItem**)_b;
33
34	return strcasecmp(a->Label(), b->Label());
35}
36
37
38static bool
39is_application_in_message(BMessage& applications, const char* app)
40{
41	const char* signature;
42	int32 i = 0;
43	while (applications.FindString("applications", i++, &signature) == B_OK) {
44		if (!strcasecmp(signature, app))
45			return true;
46	}
47
48	return false;
49}
50
51
52static void
53add_signature(BMenuItem* item, const char* signature)
54{
55	const char* subType = strchr(signature, '/');
56	if (subType == NULL)
57		return;
58
59	char label[B_MIME_TYPE_LENGTH];
60	snprintf(label, sizeof(label), "%s (%s)", item->Label(), subType + 1);
61
62	item->SetLabel(label);
63}
64
65
66static BMenuItem*
67create_application_item(const char* signature, uint32 what)
68{
69	char name[B_FILE_NAME_LENGTH];
70
71	BMessage* message = new BMessage(what);
72	message->AddString("signature", signature);
73
74	BMimeType applicationType(signature);
75	if (applicationType.GetShortDescription(name) == B_OK)
76		return new BMenuItem(name, message);
77
78	return new BMenuItem(signature, message);
79}
80
81
82//	#pragma mark - Public functions
83
84
85void
86update_preferred_app_menu(BMenu* menu, BMimeType* type, uint32 what,
87	const char* preferredFrom)
88{
89	// clear menu (but leave the first entry, ie. "None")
90
91	for (int32 i = menu->CountItems(); i-- > 1;) {
92		delete menu->RemoveItem(i);
93	}
94
95	// fill it again
96
97	menu->ItemAt(0)->SetMarked(true);
98
99	BMessage applications;
100	if (type == NULL || type->GetSupportingApps(&applications) != B_OK)
101		return;
102
103	char preferred[B_MIME_TYPE_LENGTH];
104	if (type->GetPreferredApp(preferred) != B_OK)
105		preferred[0] = '\0';
106
107	int32 lastFullSupport;
108	if (applications.FindInt32("be:sub", &lastFullSupport) != B_OK)
109		lastFullSupport = -1;
110
111	BList subList;
112	BList superList;
113
114	const char* signature;
115	int32 i = 0;
116	while (applications.FindString("applications", i, &signature) == B_OK) {
117		BMenuItem* item = create_application_item(signature, what);
118
119		if (i < lastFullSupport)
120			subList.AddItem(item);
121		else
122			superList.AddItem(item);
123
124		i++;
125	}
126
127	// sort lists
128
129	subList.SortItems(compare_menu_items);
130	superList.SortItems(compare_menu_items);
131
132	// add lists to the menu
133
134	if (subList.CountItems() != 0 || superList.CountItems() != 0)
135		menu->AddSeparatorItem();
136
137	for (int32 i = 0; i < subList.CountItems(); i++) {
138		menu->AddItem((BMenuItem*)subList.ItemAt(i));
139	}
140
141	// Add type separator
142	if (superList.CountItems() != 0 && subList.CountItems() != 0)
143		menu->AddSeparatorItem();
144
145	for (int32 i = 0; i < superList.CountItems(); i++) {
146		menu->AddItem((BMenuItem*)superList.ItemAt(i));
147	}
148
149	// make items unique and select current choice
150
151	bool lastItemSame = false;
152	const char* lastSignature = NULL;
153	BMenuItem* last = NULL;
154	BMenuItem* select = NULL;
155
156	for (int32 index = 0; index < menu->CountItems(); index++) {
157		BMenuItem* item = menu->ItemAt(index);
158		if (item == NULL)
159			continue;
160
161		if (item->Message() == NULL
162			|| item->Message()->FindString("signature", &signature) != B_OK)
163			continue;
164
165		if ((preferredFrom == NULL && !strcasecmp(signature, preferred))
166			|| (preferredFrom != NULL
167				&& !strcasecmp(signature, preferredFrom))) {
168			select = item;
169		}
170
171		if (last == NULL || strcmp(last->Label(), item->Label())) {
172			if (lastItemSame)
173				add_signature(last, lastSignature);
174
175			lastItemSame = false;
176			last = item;
177			lastSignature = signature;
178			continue;
179		}
180
181		lastItemSame = true;
182		add_signature(last, lastSignature);
183
184		last = item;
185		lastSignature = signature;
186	}
187
188	if (lastItemSame)
189		add_signature(last, lastSignature);
190
191	if (select != NULL) {
192		// We don't select the item earlier, so that the menu field can
193		// pick up the signature as well as label.
194		select->SetMarked(true);
195	} else if ((preferredFrom == NULL && preferred[0])
196		|| (preferredFrom != NULL && preferredFrom[0])) {
197		// The preferred application is not an application that support
198		// this file type!
199		BMenuItem* item = create_application_item(preferredFrom
200			? preferredFrom : preferred, what);
201
202		menu->AddSeparatorItem();
203		menu->AddItem(item);
204		item->SetMarked(true);
205	}
206}
207
208
209status_t
210retrieve_preferred_app(BMessage* message, bool sameAs, const char* forType,
211	BString& preferredApp)
212{
213	entry_ref ref;
214	if (message == NULL || message->FindRef("refs", &ref) != B_OK)
215		return B_BAD_VALUE;
216
217	BFile file(&ref, B_READ_ONLY);
218	status_t status = file.InitCheck();
219
220	char preferred[B_MIME_TYPE_LENGTH];
221
222	if (status == B_OK) {
223		if (sameAs) {
224			// get preferred app from file
225			BNodeInfo nodeInfo(&file);
226			status = nodeInfo.InitCheck();
227			if (status == B_OK) {
228				if (nodeInfo.GetPreferredApp(preferred) != B_OK)
229					preferred[0] = '\0';
230
231				if (!preferred[0]) {
232					// get MIME type from file
233					char type[B_MIME_TYPE_LENGTH];
234					if (nodeInfo.GetType(type) == B_OK) {
235						BMimeType mimeType(type);
236						mimeType.GetPreferredApp(preferred);
237					}
238				}
239			}
240		} else {
241			// get application signature
242			BAppFileInfo appInfo(&file);
243			status = appInfo.InitCheck();
244
245			if (status == B_OK && appInfo.GetSignature(preferred) != B_OK)
246				preferred[0] = '\0';
247		}
248	}
249
250	if (status != B_OK) {
251		error_alert(B_TRANSLATE("File could not be opened"),
252			status, B_STOP_ALERT);
253		return status;
254	}
255
256	if (!preferred[0]) {
257		error_alert(sameAs
258			? B_TRANSLATE("Could not retrieve preferred application of this "
259				"file")
260			: B_TRANSLATE("Could not retrieve application signature"));
261		return B_ERROR;
262	}
263
264	// Check if the application chosen supports this type
265
266	BMimeType mimeType(forType);
267	bool found = false;
268
269	BMessage applications;
270	if (mimeType.GetSupportingApps(&applications) == B_OK
271		&& is_application_in_message(applications, preferred))
272		found = true;
273
274	applications.MakeEmpty();
275
276	if (!found && mimeType.GetWildcardApps(&applications) == B_OK
277		&& is_application_in_message(applications, preferred))
278		found = true;
279
280	if (!found) {
281		// warn user
282		BMimeType appType(preferred);
283		char description[B_MIME_TYPE_LENGTH];
284		if (appType.GetShortDescription(description) != B_OK)
285			description[0] = '\0';
286
287		char warning[512];
288		snprintf(warning, sizeof(warning), B_TRANSLATE("The application "
289			"\"%s\" does not support this file type.\n"
290			"Are you sure you want to set it anyway?"),
291			description[0] ? description : preferred);
292
293		BAlert* alert = new BAlert(B_TRANSLATE("FileTypes request"), warning,
294			B_TRANSLATE("Set preferred application"), B_TRANSLATE("Cancel"),
295			NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
296		alert->SetShortcut(1, B_ESCAPE);
297		if (alert->Go() == 1)
298			return B_ERROR;
299	}
300
301	preferredApp = preferred;
302	return B_OK;
303}
304
305