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