xref: /haiku/src/preferences/filetypes/ApplicationTypesWindow.cpp (revision 2a2e7ad562841be14b2d1f8ad870780f32be2b1f)
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*
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 
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 
126 ProgressWindow::~ProgressWindow()
127 {
128 }
129 
130 
131 void
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 
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 
267 ApplicationTypesWindow::~ApplicationTypesWindow()
268 {
269 	BMimeType::StopWatching(this);
270 }
271 
272 
273 BRect
274 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
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
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
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
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