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
37 const uint32 kMsgTypeEntered = 'type';
38 const uint32 kMsgSelectType = 'sltp';
39 const uint32 kMsgTypeSelected = 'tpsd';
40 const uint32 kMsgSameTypeAs = 'stpa';
41 const uint32 kMsgSameTypeAsOpened = 'stpO';
42
43 const uint32 kMsgPreferredAppChosen = 'papc';
44 const uint32 kMsgSelectPreferredApp = 'slpa';
45 const uint32 kMsgSamePreferredAppAs = 'spaa';
46 const uint32 kMsgPreferredAppOpened = 'paOp';
47 const uint32 kMsgSamePreferredAppAsOpened = 'spaO';
48
49
FileTypeWindow(BPoint position,const BMessage & refs)50 FileTypeWindow::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
~FileTypeWindow()135 FileTypeWindow::~FileTypeWindow()
136 {
137 BMimeType::StopWatching(this);
138 }
139
140
141 BString
_Title(const BMessage & refs)142 FileTypeWindow::_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
185 void
_SetTo(const BMessage & refs)186 FileTypeWindow::_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
240 void
_AdoptType(BMessage * message)241 FileTypeWindow::_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
273 void
_AdoptType()274 FileTypeWindow::_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
288 void
_AdoptPreferredApp(BMessage * message,bool sameAs)289 FileTypeWindow::_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
299 void
_AdoptPreferredApp()300 FileTypeWindow::_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
319 void
_UpdatePreferredApps()320 FileTypeWindow::_UpdatePreferredApps()
321 {
322 BMimeType type(fCommonType.String());
323 update_preferred_app_menu(fPreferredField->Menu(), &type,
324 kMsgPreferredAppChosen, fCommonPreferredApp.String());
325 }
326
327
328 void
MessageReceived(BMessage * message)329 FileTypeWindow::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
452 bool
QuitRequested()453 FileTypeWindow::QuitRequested()
454 {
455 be_app->PostMessage(kMsgTypeWindowClosed);
456 return true;
457 }
458
459