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