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