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