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