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
50class 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
64const uint32 kMsgTypeSelected = 'typs';
65const uint32 kMsgTypeInvoked = 'typi';
66const uint32 kMsgRemoveUninstalled = 'runs';
67const uint32 kMsgEdit = 'edit';
68
69
70const char*
71variety_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
95ProgressWindow::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
126ProgressWindow::~ProgressWindow()
127{
128}
129
130
131void
132ProgressWindow::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
159ApplicationTypesWindow::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
267ApplicationTypesWindow::~ApplicationTypesWindow()
268{
269	BMimeType::StopWatching(this);
270}
271
272
273BRect
274ApplicationTypesWindow::_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
284void
285ApplicationTypesWindow::_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
357void
358ApplicationTypesWindow::_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
499void
500ApplicationTypesWindow::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
571bool
572ApplicationTypesWindow::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