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 "ApplicationTypesWindow.h"
8 #include "FileTypes.h"
9 #include "FileTypesWindow.h"
10 #include "MimeTypeListView.h"
11 #include "StringView.h"
12
13 #include <AppFileInfo.h>
14 #include <Application.h>
15 #include <Bitmap.h>
16 #include <Box.h>
17 #include <Button.h>
18 #include <Catalog.h>
19 #include <ControlLook.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 <Path.h>
27 #include <PopUpMenu.h>
28 #include <Query.h>
29 #include <Roster.h>
30 #include <Screen.h>
31 #include <ScrollView.h>
32 #include <StatusBar.h>
33 #include <StringFormat.h>
34 #include <StringView.h>
35 #include <TextView.h>
36 #include <Volume.h>
37 #include <VolumeRoster.h>
38
39 #include <stdio.h>
40 #include <strings.h>
41
42
43 // TODO: think about adopting Tracker's info window style here (pressable path)
44
45
46 #undef B_TRANSLATION_CONTEXT
47 #define B_TRANSLATION_CONTEXT "Application Types Window"
48
49
50 class ProgressWindow : public BWindow {
51 public:
52 ProgressWindow(const char* message, int32 max,
53 volatile bool* signalQuit);
54 virtual ~ProgressWindow();
55
56 virtual void MessageReceived(BMessage* message);
57
58 private:
59 BStatusBar* fStatusBar;
60 BButton* fAbortButton;
61 volatile bool* fQuitListener;
62 };
63
64 const uint32 kMsgTypeSelected = 'typs';
65 const uint32 kMsgTypeInvoked = 'typi';
66 const uint32 kMsgRemoveUninstalled = 'runs';
67 const uint32 kMsgEdit = 'edit';
68
69
70 const char*
variety_to_text(uint32 variety)71 variety_to_text(uint32 variety)
72 {
73 switch (variety) {
74 case B_DEVELOPMENT_VERSION:
75 return B_TRANSLATE("Development");
76 case B_ALPHA_VERSION:
77 return B_TRANSLATE("Alpha");
78 case B_BETA_VERSION:
79 return B_TRANSLATE("Beta");
80 case B_GAMMA_VERSION:
81 return B_TRANSLATE("Gamma");
82 case B_GOLDEN_MASTER_VERSION:
83 return B_TRANSLATE("Golden master");
84 case B_FINAL_VERSION:
85 return B_TRANSLATE("Final");
86 }
87
88 return "-";
89 }
90
91
92 // #pragma mark -
93
94
ProgressWindow(const char * message,int32 max,volatile bool * signalQuit)95 ProgressWindow::ProgressWindow(const char* message,
96 int32 max, volatile bool* signalQuit)
97 :
98 BWindow(BRect(0, 0, 300, 200), B_TRANSLATE("Progress"), B_MODAL_WINDOW_LOOK,
99 B_MODAL_SUBSET_WINDOW_FEEL, B_ASYNCHRONOUS_CONTROLS |
100 B_NOT_V_RESIZABLE | B_AUTO_UPDATE_SIZE_LIMITS),
101 fQuitListener(signalQuit)
102 {
103 char count[100];
104 snprintf(count, sizeof(count), "/%" B_PRId32, max);
105
106 fStatusBar = new BStatusBar("status", message, count);
107 fStatusBar->SetMaxValue(max);
108 fAbortButton = new BButton("abort", B_TRANSLATE("Abort"),
109 new BMessage(B_CANCEL));
110
111 float padding = be_control_look->DefaultItemSpacing();
112 BLayoutBuilder::Group<>(this, B_VERTICAL, padding)
113 .SetInsets(padding, padding, padding, padding)
114 .Add(fStatusBar)
115 .Add(fAbortButton);
116
117 // center on screen
118 BScreen screen(this);
119 MoveTo(screen.Frame().left + (screen.Frame().Width()
120 - Bounds().Width()) / 2.0f,
121 screen.Frame().top + (screen.Frame().Height()
122 - Bounds().Height()) / 2.0f);
123 }
124
125
~ProgressWindow()126 ProgressWindow::~ProgressWindow()
127 {
128 }
129
130
131 void
MessageReceived(BMessage * message)132 ProgressWindow::MessageReceived(BMessage* message)
133 {
134 switch (message->what) {
135 case B_UPDATE_STATUS_BAR:
136 char count[100];
137 snprintf(count, sizeof(count), "%" B_PRId32,
138 (int32)fStatusBar->CurrentValue() + 1);
139
140 fStatusBar->Update(1, NULL, count);
141 break;
142
143 case B_CANCEL:
144 fAbortButton->SetEnabled(false);
145 if (fQuitListener != NULL)
146 *fQuitListener = true;
147 break;
148
149 default:
150 BWindow::MessageReceived(message);
151 break;
152 }
153 }
154
155
156 // #pragma mark -
157
158
ApplicationTypesWindow(const BMessage & settings)159 ApplicationTypesWindow::ApplicationTypesWindow(const BMessage& settings)
160 : BWindow(_Frame(settings), B_TRANSLATE("Application types"),
161 B_TITLED_WINDOW,
162 B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS)
163 {
164 float padding = be_control_look->DefaultItemSpacing();
165 BAlignment labelAlignment = be_control_look->DefaultLabelAlignment();
166 BAlignment fullWidthTopAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_TOP);
167
168 // Application list
169
170 fTypeListView = new MimeTypeListView("listview", "application", true, true);
171 fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected));
172 fTypeListView->SetInvocationMessage(new BMessage(kMsgTypeInvoked));
173
174 BScrollView* scrollView = new BScrollView("scrollview", fTypeListView,
175 B_FRAME_EVENTS | B_WILL_DRAW, false, true);
176
177 BButton* button = new BButton("remove", B_TRANSLATE("Remove uninstalled"),
178 new BMessage(kMsgRemoveUninstalled));
179
180 // "Information" group
181
182 BBox* infoBox = new BBox((char*)NULL);
183 infoBox->SetLabel(B_TRANSLATE("Information"));
184 infoBox->SetExplicitAlignment(fullWidthTopAlignment);
185
186 fNameView = new StringView(B_TRANSLATE("Name:"), NULL);
187 fNameView->TextView()->SetExplicitAlignment(labelAlignment);
188 fNameView->LabelView()->SetExplicitAlignment(labelAlignment);
189 fSignatureView = new StringView(B_TRANSLATE("Signature:"), NULL);
190 fSignatureView->TextView()->SetExplicitAlignment(labelAlignment);
191 fSignatureView->LabelView()->SetExplicitAlignment(labelAlignment);
192 fSignatureView->TextView()->SetExplicitMinSize(BSize(
193 fSignatureView->TextView()->StringWidth("M") * 42, B_SIZE_UNSET));
194 fPathView = new StringView(B_TRANSLATE("Path:"), NULL);
195 fPathView->TextView()->SetExplicitAlignment(labelAlignment);
196 fPathView->LabelView()->SetExplicitAlignment(labelAlignment);
197
198 BLayoutBuilder::Grid<>(infoBox, padding, padding)
199 .SetInsets(padding, padding * 2, padding, padding)
200 .Add(fNameView->LabelView(), 0, 0)
201 .Add(fNameView->TextView(), 1, 0, 2)
202 .Add(fSignatureView->LabelView(), 0, 1)
203 .Add(fSignatureView->TextView(), 1, 1, 2)
204 .Add(fPathView->LabelView(), 0, 2)
205 .Add(fPathView->TextView(), 1, 2, 2);
206
207 // "Version" group
208
209 BBox* versionBox = new BBox("");
210 versionBox->SetLabel(B_TRANSLATE("Version"));
211 versionBox->SetExplicitAlignment(fullWidthTopAlignment);
212
213 fVersionView = new StringView(B_TRANSLATE("Version:"), NULL);
214 fVersionView->TextView()->SetExplicitAlignment(labelAlignment);
215 fVersionView->LabelView()->SetExplicitAlignment(labelAlignment);
216 fDescriptionLabel = new StringView(B_TRANSLATE("Description:"), NULL);
217 fDescriptionLabel->LabelView()->SetExplicitAlignment(labelAlignment);
218 fDescriptionView = new BTextView("description");
219 fDescriptionView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
220 fDescriptionView->SetLowColor(fDescriptionView->ViewColor());
221 fDescriptionView->MakeEditable(false);
222
223 BLayoutBuilder::Grid<>(versionBox, padding, padding)
224 .SetInsets(padding, padding * 2, padding, padding)
225 .Add(fVersionView->LabelView(), 0, 0)
226 .Add(fVersionView->TextView(), 1, 0)
227 .Add(fDescriptionLabel->LabelView(), 0, 1)
228 .Add(fDescriptionView, 1, 1, 2, 2);
229
230 // Launch and Tracker buttons
231
232 fEditButton = new BButton(B_TRANSLATE("Edit" B_UTF8_ELLIPSIS),
233 new BMessage(kMsgEdit));
234 // launch and tracker buttons get messages in _SetType()
235 fLaunchButton = new BButton(B_TRANSLATE("Launch"));
236 fTrackerButton = new BButton(
237 B_TRANSLATE("Show in Tracker" B_UTF8_ELLIPSIS));
238
239
240 BLayoutBuilder::Group<>(this, B_HORIZONTAL, padding)
241 .AddGroup(B_VERTICAL, padding, 3)
242 .Add(scrollView)
243 .AddGroup(B_HORIZONTAL)
244 .Add(button)
245 .AddGlue()
246 .End()
247 .End()
248 .AddGroup(B_VERTICAL, padding)
249 .Add(infoBox)
250 .Add(versionBox)
251 .AddGroup(B_HORIZONTAL, padding)
252 .Add(fEditButton)
253 .Add(fLaunchButton)
254 .Add(fTrackerButton)
255 .AddGlue()
256 .End()
257 .AddGlue(10.0)
258 .End()
259
260 .SetInsets(B_USE_WINDOW_SPACING);
261
262 BMimeType::StartWatching(this);
263 _SetType(NULL);
264 }
265
266
~ApplicationTypesWindow()267 ApplicationTypesWindow::~ApplicationTypesWindow()
268 {
269 BMimeType::StopWatching(this);
270 }
271
272
273 BRect
_Frame(const BMessage & settings) const274 ApplicationTypesWindow::_Frame(const BMessage& settings) const
275 {
276 BRect rect;
277 if (settings.FindRect("app_types_frame", &rect) == B_OK)
278 return rect;
279
280 return BRect(100.0f, 100.0f, 540.0f, 480.0f);
281 }
282
283
284 void
_RemoveUninstalled()285 ApplicationTypesWindow::_RemoveUninstalled()
286 {
287 // Note: this runs in the looper's thread, which isn't that nice
288
289 int32 removed = 0;
290 volatile bool quit = false;
291
292 BWindow* progressWindow =
293 new ProgressWindow(
294 B_TRANSLATE("Removing uninstalled application types"),
295 fTypeListView->FullListCountItems(), &quit);
296 progressWindow->AddToSubset(this);
297 progressWindow->Show();
298
299 for (int32 i = fTypeListView->FullListCountItems(); i-- > 0 && !quit;) {
300 MimeTypeItem* item = dynamic_cast<MimeTypeItem*>
301 (fTypeListView->FullListItemAt(i));
302 progressWindow->PostMessage(B_UPDATE_STATUS_BAR);
303
304 if (item == NULL)
305 continue;
306
307 // search for application on all volumes
308
309 bool found = false;
310
311 BVolumeRoster volumeRoster;
312 BVolume volume;
313 while (volumeRoster.GetNextVolume(&volume) == B_OK) {
314 if (!volume.KnowsQuery())
315 continue;
316
317 BQuery query;
318 query.PushAttr("BEOS:APP_SIG");
319 query.PushString(item->Type());
320 query.PushOp(B_EQ);
321
322 query.SetVolume(&volume);
323 query.Fetch();
324
325 entry_ref ref;
326 if (query.GetNextRef(&ref) == B_OK) {
327 found = true;
328 break;
329 }
330 }
331
332 if (!found) {
333 BMimeType mimeType(item->Type());
334 mimeType.Delete();
335
336 removed++;
337
338 // We're blocking the message loop that received the MIME changes,
339 // so we dequeue all waiting messages from time to time
340 if (removed % 10 == 0)
341 UpdateIfNeeded();
342 }
343 }
344
345 progressWindow->PostMessage(B_QUIT_REQUESTED);
346
347 static BStringFormat format(B_TRANSLATE("{0, plural, "
348 "one{# Application type could be removed} "
349 "other{# Application types could be removed}}"));
350 BString message;
351 format.Format(message, removed);
352
353 error_alert(message, B_OK, B_INFO_ALERT);
354 }
355
356
357 void
_SetType(BMimeType * type,int32 forceUpdate)358 ApplicationTypesWindow::_SetType(BMimeType* type, int32 forceUpdate)
359 {
360 if (type == NULL) {
361 fCurrentType.Unset();
362
363 // Information group
364 fNameView->SetText(NULL);
365 fNameView->SetEnabled(false);
366 fSignatureView->SetText(NULL);
367 fSignatureView->SetEnabled(false);
368 fPathView->SetText(NULL);
369 fPathView->SetEnabled(false);
370
371 // Version group
372 fVersionView->SetText(NULL);
373 fVersionView->SetEnabled(false);
374 fDescriptionView->SetText(NULL);
375 fDescriptionLabel->SetEnabled(true);
376
377 // Buttons
378 fEditButton->SetEnabled(false);
379 fLaunchButton->SetMessage(NULL);
380 fLaunchButton->SetEnabled(false);
381 fTrackerButton->SetMessage(NULL);
382 fTrackerButton->SetEnabled(false);
383
384 return;
385 }
386
387 if (fCurrentType == *type) {
388 if (!forceUpdate)
389 return;
390 } else
391 forceUpdate = B_EVERYTHING_CHANGED;
392
393 if (&fCurrentType != type)
394 fCurrentType.SetTo(type->Type());
395
396 fSignatureView->SetText(type->Type());
397 fSignatureView->SetEnabled(true);
398
399 if ((forceUpdate & B_SHORT_DESCRIPTION_CHANGED) != 0) {
400 char description[B_MIME_TYPE_LENGTH];
401
402 if (type->GetShortDescription(description) != B_OK) {
403 fNameView->SetText("");
404 fNameView->SetEnabled(false);
405 } else {
406 fNameView->SetText(description);
407 fNameView->SetEnabled(true);
408 }
409 }
410
411 if ((forceUpdate & B_APP_HINT_CHANGED) != 0) {
412 bool appInfoFound = false;
413 entry_ref ref;
414
415 if (be_roster->FindApp(fCurrentType.Type(), &ref) == B_OK) {
416 // Set launch message
417 BMessenger tracker("application/x-vnd.Be-TRAK");
418 BMessage* message = new BMessage(B_REFS_RECEIVED);
419 message->AddRef("refs", &ref);
420
421 fLaunchButton->SetMessage(message);
422 fLaunchButton->SetTarget(tracker);
423 fLaunchButton->SetEnabled(true);
424
425 // update version information
426
427 BFile file(&ref, B_READ_ONLY);
428 if (file.InitCheck() == B_OK) {
429 fEditButton->SetEnabled(true);
430
431 BAppFileInfo appInfo(&file);
432 version_info versionInfo;
433 if (appInfo.InitCheck() == B_OK
434 && appInfo.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND)
435 == B_OK) {
436 char version[256];
437 snprintf(version, sizeof(version),
438 "%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32 ", %s/%" B_PRIu32,
439 versionInfo.major, versionInfo.middle,
440 versionInfo.minor,
441 variety_to_text(versionInfo.variety),
442 versionInfo.internal);
443
444 fVersionView->SetText(version);
445 fVersionView->SetEnabled(true);
446 fDescriptionView->SetText(versionInfo.long_info);
447 fDescriptionLabel->SetEnabled(true);
448
449 appInfoFound = true;
450 }
451 } else {
452 fEditButton->SetEnabled(false);
453 }
454 } else {
455 fEditButton->SetEnabled(false);
456 fLaunchButton->SetMessage(NULL);
457 fLaunchButton->SetEnabled(false);
458 }
459
460 if (!appInfoFound) {
461 fVersionView->SetText(NULL);
462 fVersionView->SetEnabled(false);
463 fDescriptionView->SetText(NULL);
464 fDescriptionLabel->SetEnabled(false);
465 }
466
467 BPath path(&ref);
468 if (path.InitCheck() == B_OK) {
469 // Set path
470 path.GetParent(&path);
471 fPathView->SetText(path.Path());
472 fPathView->SetEnabled(true);
473
474 // Set "Show In Tracker" message
475 BEntry entry(path.Path());
476 entry_ref directoryRef;
477 if (entry.GetRef(&directoryRef) == B_OK) {
478 BMessenger tracker("application/x-vnd.Be-TRAK");
479 BMessage* message = new BMessage(B_REFS_RECEIVED);
480 message->AddRef("refs", &directoryRef);
481
482 fTrackerButton->SetMessage(message);
483 fTrackerButton->SetTarget(tracker);
484 fTrackerButton->SetEnabled(true);
485 } else {
486 fTrackerButton->SetMessage(NULL);
487 fTrackerButton->SetEnabled(false);
488 }
489 } else {
490 fPathView->SetText(NULL);
491 fPathView->SetEnabled(false);
492 fTrackerButton->SetMessage(NULL);
493 fTrackerButton->SetEnabled(false);
494 }
495 }
496 }
497
498
499 void
MessageReceived(BMessage * message)500 ApplicationTypesWindow::MessageReceived(BMessage* message)
501 {
502 switch (message->what) {
503 case kMsgTypeSelected:
504 {
505 int32 index;
506 if (message->FindInt32("index", &index) == B_OK) {
507 MimeTypeItem* item = (MimeTypeItem*)fTypeListView->ItemAt(index);
508 if (item != NULL) {
509 BMimeType type(item->Type());
510 _SetType(&type);
511 } else
512 _SetType(NULL);
513 }
514 break;
515 }
516
517 case kMsgTypeInvoked:
518 {
519 int32 index;
520 if (message->FindInt32("index", &index) == B_OK) {
521 MimeTypeItem* item = (MimeTypeItem*)fTypeListView->ItemAt(index);
522 if (item != NULL) {
523 BMimeType type(item->Type());
524 entry_ref ref;
525 if (type.GetAppHint(&ref) == B_OK) {
526 BMessage refs(B_REFS_RECEIVED);
527 refs.AddRef("refs", &ref);
528
529 be_app->PostMessage(&refs);
530 }
531 }
532 }
533 break;
534 }
535
536 case kMsgEdit:
537 fTypeListView->Invoke();
538 break;
539
540 case kMsgRemoveUninstalled:
541 _RemoveUninstalled();
542 break;
543
544 case B_META_MIME_CHANGED:
545 {
546 const char* type;
547 int32 which;
548 if (message->FindString("be:type", &type) != B_OK
549 || message->FindInt32("be:which", &which) != B_OK) {
550 break;
551 }
552
553 if (fCurrentType.Type() == NULL)
554 break;
555
556 if (!strcasecmp(fCurrentType.Type(), type)) {
557 if (which != B_MIME_TYPE_DELETED)
558 _SetType(&fCurrentType, which);
559 else
560 _SetType(NULL);
561 }
562 break;
563 }
564
565 default:
566 BWindow::MessageReceived(message);
567 }
568 }
569
570
571 bool
QuitRequested()572 ApplicationTypesWindow::QuitRequested()
573 {
574 BMessage update(kMsgSettingsChanged);
575 update.AddRect("app_types_frame", Frame());
576 be_app_messenger.SendMessage(&update);
577
578 be_app->PostMessage(kMsgApplicationTypesWindowClosed);
579 return true;
580 }
581
582
583