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
compare_menu_items(const void * _a,const void * _b)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
is_application_in_message(BMessage & applications,const char * app)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
add_signature(BMenuItem * item,const char * signature)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*
create_application_item(const char * signature,uint32 what)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
update_preferred_app_menu(BMenu * menu,BMimeType * type,uint32 what,const char * preferredFrom)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(true);
205 }
206 }
207
208
209 status_t
retrieve_preferred_app(BMessage * message,bool sameAs,const char * forType,BString & preferredApp)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