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