xref: /haiku/src/preferences/filetypes/PreferredAppMenu.cpp (revision 56430ad8002b8fd1ac69b590e9cc130de6d9e852)
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 
28 static int
29 compare_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 
38 static bool
39 is_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 
52 static void
53 add_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 
66 static BMenuItem*
67 create_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 
85 void
86 update_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(item);
205 	}
206 }
207 
208 
209 status_t
210 retrieve_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