xref: /haiku/src/preferences/filetypes/ApplicationTypesWindow.cpp (revision b0623e48efa646b8d1a4c6f51e09d696d9803276)
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(const BMessage &settings)
162 	: BWindow(_Frame(settings), "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 BRect
299 ApplicationTypesWindow::_Frame(const BMessage& settings) const
300 {
301 	BRect rect;
302 	if (settings.FindRect("app_types_frame", &rect) == B_OK)
303 		return rect;
304 
305 	return BRect(100.0f, 100.0f, 540.0f, 480.0f);
306 }
307 
308 
309 void
310 ApplicationTypesWindow::_RemoveUninstalled()
311 {
312 	// Note: this runs in the looper's thread, which isn't that nice
313 
314 	int32 removed = 0;
315 	volatile bool quit = false;
316 
317 	BWindow* progressWindow = new ProgressWindow("Removing uninstalled application types",
318 		fTypeListView->FullListCountItems(), &quit);
319 	progressWindow->AddToSubset(this);
320 	progressWindow->Show();
321 
322 	for (int32 i = fTypeListView->FullListCountItems(); i-- > 0 && !quit;) {
323 		MimeTypeItem* item = dynamic_cast<MimeTypeItem*>(fTypeListView->FullListItemAt(i));
324 		progressWindow->PostMessage(B_UPDATE_STATUS_BAR);
325 
326 		if (item == NULL)
327 			continue;
328 
329 		// search for application on all volumes
330 
331 		bool found = false;
332 
333 		BVolumeRoster volumeRoster;
334 		BVolume volume;
335 		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
336 			if (!volume.KnowsQuery())
337 				continue;
338 
339 			BQuery query;
340 			query.PushAttr("BEOS:APP_SIG");
341 			query.PushString(item->Type());
342 			query.PushOp(B_EQ);
343 
344 			query.SetVolume(&volume);
345 			query.Fetch();
346 
347 			entry_ref ref;
348 			if (query.GetNextRef(&ref) == B_OK) {
349 				found = true;
350 				break;
351 			}
352 		}
353 
354 		if (!found) {
355 			BMimeType mimeType(item->Type());
356 			mimeType.Delete();
357 
358 			removed++;
359 
360 			// We're blocking the message loop that received the MIME changes,
361 			// so we dequeue all waiting messages from time to time
362 			if (removed % 10 == 0)
363 				UpdateIfNeeded();
364 		}
365 	}
366 
367 	progressWindow->PostMessage(B_QUIT_REQUESTED);
368 
369 	char message[512];
370 	snprintf(message, sizeof(message), "%ld Application Type%s could be removed.",
371 		removed, removed == 1 ? "" : "s");
372 	error_alert(message, B_OK, B_INFO_ALERT);
373 }
374 
375 
376 void
377 ApplicationTypesWindow::_SetType(BMimeType* type, int32 forceUpdate)
378 {
379 	bool enabled = type != NULL;
380 	bool appFound = true;
381 
382 	// update controls
383 
384 	if (type != NULL) {
385 		if (fCurrentType == *type) {
386 			if (!forceUpdate)
387 				return;
388 		} else
389 			forceUpdate = B_EVERYTHING_CHANGED;
390 
391 		if (&fCurrentType != type)
392 			fCurrentType.SetTo(type->Type());
393 
394 		fSignatureView->SetText(type->Type());
395 
396 		char description[B_MIME_TYPE_LENGTH];
397 
398 		if ((forceUpdate & B_SHORT_DESCRIPTION_CHANGED) != 0) {
399 			if (type->GetShortDescription(description) != B_OK)
400 				description[0] = '\0';
401 			fNameView->SetText(description);
402 		}
403 
404 		entry_ref ref;
405 		if ((forceUpdate & B_APP_HINT_CHANGED) != 0
406 			&& be_roster->FindApp(fCurrentType.Type(), &ref) == B_OK) {
407 			// Set launch message
408 			BMessenger tracker("application/x-vnd.Be-TRAK");
409 			BMessage* message = new BMessage(B_REFS_RECEIVED);
410 			message->AddRef("refs", &ref);
411 
412 			fLaunchButton->SetMessage(message);
413 			fLaunchButton->SetTarget(tracker);
414 
415 			// Set path
416 			BPath path(&ref);
417 			path.GetParent(&path);
418 			fPathView->SetText(path.Path());
419 
420 			// Set "Open In Tracker" message
421 			BEntry entry(path.Path());
422 			entry_ref directoryRef;
423 			if (entry.GetRef(&directoryRef) == B_OK) {
424 				BMessenger tracker("application/x-vnd.Be-TRAK");
425 				message = new BMessage(B_REFS_RECEIVED);
426 				message->AddRef("refs", &directoryRef);
427 
428 				fTrackerButton->SetMessage(message);
429 				fTrackerButton->SetTarget(tracker);
430 			} else {
431 				fTrackerButton->SetMessage(NULL);
432 				appFound = false;
433 			}
434 		}
435 
436 		if (forceUpdate == B_EVERYTHING_CHANGED) {
437 			// update version information
438 
439 			BFile file(&ref, B_READ_ONLY);
440 			if (file.InitCheck() == B_OK) {
441 				BAppFileInfo appInfo(&file);
442 				version_info versionInfo;
443 				if (appInfo.InitCheck() == B_OK
444 					&& appInfo.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND) == B_OK) {
445 					char version[256];
446 					snprintf(version, sizeof(version), "%lu.%lu.%lu, %s/%lu",
447 						versionInfo.major, versionInfo.middle, versionInfo.minor,
448 						variety_to_text(versionInfo.variety), versionInfo.internal);
449 					fVersionView->SetText(version);
450 					fDescriptionView->SetText(versionInfo.long_info);
451 				} else {
452 					fVersionView->SetText(NULL);
453 					fDescriptionView->SetText(NULL);
454 				}
455 			}
456 		}
457 	} else {
458 		fNameView->SetText(NULL);
459 		fNameView->SetText(NULL);
460 		fPathView->SetText(NULL);
461 
462 		fVersionView->SetText(NULL);
463 		fDescriptionView->SetText(NULL);
464 	}
465 
466 	fNameView->SetEnabled(enabled);
467 	fSignatureView->SetEnabled(enabled);
468 	fPathView->SetEnabled(enabled);
469 
470 	fVersionView->SetEnabled(enabled);
471 	fDescriptionLabel->SetEnabled(enabled);
472 
473 	fTrackerButton->SetEnabled(enabled && appFound);
474 	fLaunchButton->SetEnabled(enabled && appFound);
475 }
476 
477 
478 void
479 ApplicationTypesWindow::FrameResized(float width, float height)
480 {
481 	// This works around a flaw of BTextView
482 	fDescriptionView->SetTextRect(fDescriptionView->Bounds());
483 }
484 
485 
486 void
487 ApplicationTypesWindow::MessageReceived(BMessage* message)
488 {
489 	switch (message->what) {
490 		case kMsgTypeSelected:
491 		{
492 			int32 index;
493 			if (message->FindInt32("index", &index) == B_OK) {
494 				MimeTypeItem* item = (MimeTypeItem*)fTypeListView->ItemAt(index);
495 				if (item != NULL) {
496 					BMimeType type(item->Type());
497 					_SetType(&type);
498 				} else
499 					_SetType(NULL);
500 			}
501 			break;
502 		}
503 
504 		case kMsgTypeInvoked:
505 		{
506 			int32 index;
507 			if (message->FindInt32("index", &index) == B_OK) {
508 				MimeTypeItem* item = (MimeTypeItem*)fTypeListView->ItemAt(index);
509 				if (item != NULL) {
510 					BMimeType type(item->Type());
511 					entry_ref ref;
512 					if (type.GetAppHint(&ref) == B_OK) {
513 						BMessage refs(B_REFS_RECEIVED);
514 						refs.AddRef("refs", &ref);
515 
516 						be_app->PostMessage(&refs);
517 					}
518 				}
519 			}
520 			break;
521 		}
522 
523 		case kMsgRemoveUninstalled:
524 			_RemoveUninstalled();
525 			break;
526 
527 		case B_META_MIME_CHANGED:
528 		{
529 			const char* type;
530 			int32 which;
531 			if (message->FindString("be:type", &type) != B_OK
532 				|| message->FindInt32("be:which", &which) != B_OK)
533 				break;
534 
535 			if (fCurrentType.Type() == NULL)
536 				break;
537 
538 			if (!strcasecmp(fCurrentType.Type(), type)) {
539 				if (which != B_MIME_TYPE_DELETED)
540 					_SetType(&fCurrentType, which);
541 				else
542 					_SetType(NULL);
543 			}
544 			break;
545 		}
546 
547 		default:
548 			BWindow::MessageReceived(message);
549 	}
550 }
551 
552 
553 bool
554 ApplicationTypesWindow::QuitRequested()
555 {
556 	BMessage update(kMsgSettingsChanged);
557 	update.AddRect("app_types_frame", Frame());
558 	be_app_messenger.SendMessage(&update);
559 
560 	be_app->PostMessage(kMsgApplicationTypesWindowClosed);
561 	return true;
562 }
563 
564 
565