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
48class 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
62const uint32 kMsgTypeSelected = 'typs';
63const uint32 kMsgTypeInvoked = 'typi';
64const uint32 kMsgRemoveUninstalled = 'runs';
65const uint32 kMsgEdit = 'edit';
66
67
68const char*
69variety_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
102ProgressWindow::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
133ProgressWindow::~ProgressWindow()
134{
135}
136
137
138void
139ProgressWindow::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
166ApplicationTypesWindow::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
275ApplicationTypesWindow::~ApplicationTypesWindow()
276{
277	BMimeType::StopWatching(this);
278}
279
280
281BRect
282ApplicationTypesWindow::_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
292void
293ApplicationTypesWindow::_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
364void
365ApplicationTypesWindow::_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
471void
472ApplicationTypesWindow::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
543bool
544ApplicationTypesWindow::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