xref: /haiku/src/preferences/filetypes/FileTypeWindow.cpp (revision 1e36cfc2721ef13a187c6f7354dc9cbc485e89d3)
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 
7 #include "FileTypes.h"
8 #include "FileTypeWindow.h"
9 #include "IconView.h"
10 #include "PreferredAppMenu.h"
11 #include "TypeListWindow.h"
12 
13 #include <Application.h>
14 #include <Bitmap.h>
15 #include <Box.h>
16 #include <Button.h>
17 #include <File.h>
18 #include <MenuField.h>
19 #include <MenuItem.h>
20 #include <Mime.h>
21 #include <NodeInfo.h>
22 #include <PopUpMenu.h>
23 #include <TextControl.h>
24 
25 #include <stdio.h>
26 
27 
28 const uint32 kMsgTypeEntered = 'type';
29 const uint32 kMsgSelectType = 'sltp';
30 const uint32 kMsgTypeSelected = 'tpsd';
31 const uint32 kMsgSameTypeAs = 'stpa';
32 const uint32 kMsgSameTypeAsOpened = 'stpO';
33 
34 const uint32 kMsgPreferredAppChosen = 'papc';
35 const uint32 kMsgSelectPreferredApp = 'slpa';
36 const uint32 kMsgSamePreferredAppAs = 'spaa';
37 const uint32 kMsgPreferredAppOpened = 'paOp';
38 const uint32 kMsgSamePreferredAppAsOpened = 'spaO';
39 
40 
41 FileTypeWindow::FileTypeWindow(BPoint position, const BMessage& refs)
42 	: BWindow(BRect(0.0f, 0.0f, 200.0f, 200.0f).OffsetBySelf(position),
43 		"File Type", B_TITLED_WINDOW,
44 		B_NOT_V_RESIZABLE | B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS)
45 {
46 	BRect rect = Bounds();
47 	BView* topView = new BView(rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW);
48 	topView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
49 	AddChild(topView);
50 
51 	// "File Type" group
52 
53 	BFont font(be_bold_font);
54 	font_height fontHeight;
55 	font.GetHeight(&fontHeight);
56 
57 	rect.InsetBy(8.0f, 8.0f);
58 	BBox* box = new BBox(rect, NULL, B_FOLLOW_LEFT_RIGHT);
59 	box->SetLabel("File Type");
60 	topView->AddChild(box);
61 
62 	rect = box->Bounds();
63 	rect.InsetBy(8.0f, 4.0f + fontHeight.ascent + fontHeight.descent);
64 	fTypeControl = new BTextControl(rect, "type", NULL, NULL,
65 		new BMessage(kMsgTypeEntered), B_FOLLOW_LEFT_RIGHT);
66 	fTypeControl->SetDivider(0.0f);
67 	float width, height;
68 	fTypeControl->GetPreferredSize(&width, &height);
69 	fTypeControl->ResizeTo(rect.Width(), height);
70 	box->AddChild(fTypeControl);
71 
72 	// filter out invalid characters that can't be part of a MIME type name
73 	BTextView* textView = fTypeControl->TextView();
74 	const char* disallowedCharacters = "<>@,;:\"()[]?=";
75 	for (int32 i = 0; disallowedCharacters[i]; i++) {
76 		textView->DisallowChar(disallowedCharacters[i]);
77 	}
78 
79 	rect.OffsetBy(0.0f, fTypeControl->Bounds().Height() + 5.0f);
80 	fSelectTypeButton = new BButton(rect, "select type", "Select" B_UTF8_ELLIPSIS,
81 		new BMessage(kMsgSelectType), B_FOLLOW_LEFT | B_FOLLOW_TOP);
82 	fSelectTypeButton->ResizeToPreferred();
83 	box->AddChild(fSelectTypeButton);
84 
85 	rect.OffsetBy(fSelectTypeButton->Bounds().Width() + 8.0f, 0.0f);
86 	fSameTypeAsButton = new BButton(rect, "same type as", "Same As" B_UTF8_ELLIPSIS,
87 		new BMessage(kMsgSameTypeAs), B_FOLLOW_LEFT | B_FOLLOW_TOP);
88 	fSameTypeAsButton->ResizeToPreferred();
89 	box->AddChild(fSameTypeAsButton);
90 
91 	width = font.StringWidth("Icon") + 16.0f;
92 	if (width < B_LARGE_ICON + 16.0f)
93 		width = B_LARGE_ICON + 16.0f;
94 
95 	height = fSelectTypeButton->Frame().bottom + 8.0f;
96 	if (height < 8.0f + B_LARGE_ICON + fontHeight.ascent + fontHeight.descent)
97 		height = 8.0f + B_LARGE_ICON + fontHeight.ascent + fontHeight.descent;
98 	box->ResizeTo(box->Bounds().Width() - width - 8.0f, height);
99 
100 	// "Icon" group
101 
102 	rect = box->Frame();
103 	rect.left = rect.right + 8.0f;
104 	rect.right += width + 8.0f;
105 	float iconBoxWidth = rect.Width();
106 	box = new BBox(rect, NULL, B_FOLLOW_RIGHT | B_FOLLOW_TOP);
107 	box->SetLabel("Icon");
108 	topView->AddChild(box);
109 
110 	rect = BRect(8.0f, 0.0f, 7.0f + B_LARGE_ICON, B_LARGE_ICON - 1.0f);
111 	rect.OffsetBy(0.0f, (box->Bounds().Height() - rect.Height()) / 2.0f);
112 	if (rect.top < fontHeight.ascent + fontHeight.descent + 4.0f)
113 		rect.top = fontHeight.ascent + fontHeight.descent + 4.0f;
114 	fIconView = new IconView(rect, "icon");
115 	box->AddChild(fIconView);
116 
117 	// "Preferred Application" group
118 
119 	rect.top = box->Frame().bottom + 8.0f;
120 	rect.bottom = rect.top + box->Bounds().Height();
121 	rect.left = 8.0f;
122 	rect.right = Bounds().Width() - 8.0f;
123 	box = new BBox(rect, NULL, B_FOLLOW_LEFT_RIGHT);
124 	box->SetLabel("Preferred Application");
125 	topView->AddChild(box);
126 
127 	BMenu* menu = new BPopUpMenu("preferred");
128 	BMenuItem* item;
129 	menu->AddItem(item = new BMenuItem("Default Application",
130 		new BMessage(kMsgPreferredAppChosen)));
131 	item->SetMarked(true);
132 
133 	rect = fTypeControl->Frame();
134 	BView* constrainingView = new BView(rect, NULL, B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW);
135 	constrainingView->SetViewColor(topView->ViewColor());
136 
137 	fPreferredField = new BMenuField(rect.OffsetToCopy(B_ORIGIN), "preferred",
138 		NULL, menu);
139 	fPreferredField->GetPreferredSize(&width, &height);
140 	fPreferredField->ResizeTo(rect.Width(), height);
141 	constrainingView->ResizeTo(rect.Width(), height);
142 	constrainingView->AddChild(fPreferredField);
143 		// we embed the menu field in another view to make it behave like
144 		// we want so that it can't obscure other elements with larger
145 		// labels
146 
147 	box->AddChild(constrainingView);
148 
149 	rect.OffsetBy(0.0f, height + 5.0f);
150 	fSelectAppButton = new BButton(rect, "select app", "Select" B_UTF8_ELLIPSIS,
151 		new BMessage(kMsgSelectPreferredApp), B_FOLLOW_LEFT | B_FOLLOW_TOP);
152 	fSelectAppButton->ResizeToPreferred();
153 	box->AddChild(fSelectAppButton);
154 
155 	rect.OffsetBy(fSelectAppButton->Bounds().Width() + 8.0f, 0.0f);
156 	fSameAppAsButton = new BButton(rect, "same app as", "Same As" B_UTF8_ELLIPSIS,
157 		new BMessage(kMsgSamePreferredAppAs), B_FOLLOW_LEFT | B_FOLLOW_TOP);
158 	fSameAppAsButton->ResizeToPreferred();
159 	box->AddChild(fSameAppAsButton);
160 	box->ResizeBy(0.0f, height - fTypeControl->Bounds().Height());
161 
162 	ResizeTo(fSameAppAsButton->Frame().right + 100.0f, box->Frame().bottom + 8.0f);
163 	SetSizeLimits(fSameAppAsButton->Frame().right + iconBoxWidth + 32.0f, 32767.0f,
164 		Bounds().Height(), Bounds().Height());
165 
166 	fTypeControl->MakeFocus(true);
167 
168 	BMimeType::StartWatching(this);
169 	_SetTo(refs);
170 }
171 
172 
173 FileTypeWindow::~FileTypeWindow()
174 {
175 	BMimeType::StopWatching(this);
176 }
177 
178 
179 BString
180 FileTypeWindow::_Title(const BMessage& refs)
181 {
182 	BString title;
183 	entry_ref ref;
184 	if (refs.FindRef("refs", 1, &ref) == B_OK) {
185 		bool same = false;
186 		BEntry entry, parent;
187 		if (entry.SetTo(&ref) == B_OK
188 			&& entry.GetParent(&parent) == B_OK) {
189 			same = true;
190 
191 			// Check if all entries have the same parent directory
192 			for (int32 i = 0; refs.FindRef("refs", i, &ref) == B_OK; i++) {
193 				BEntry directory;
194 				if (entry.SetTo(&ref) == B_OK
195 					&& entry.GetParent(&directory) == B_OK) {
196 					if (directory != parent) {
197 						same = false;
198 						break;
199 					}
200 				}
201 			}
202 		}
203 
204 		char name[B_FILE_NAME_LENGTH];
205 		if (same && parent.GetName(name) == B_OK) {
206 			title = "Multiple Files from \"";
207 			title.Append(name);
208 			title.Append("\"");
209 		} else
210 			title = "[Multiple Files]";
211 	} else if (refs.FindRef("refs", 0, &ref) == B_OK)
212 		title = ref.name;
213 
214 	title.Append(" File Type");
215 	return title;
216 }
217 
218 
219 void
220 FileTypeWindow::_SetTo(const BMessage& refs)
221 {
222 	SetTitle(_Title(refs).String());
223 
224 	// get common info and icons
225 
226 	fCommonPreferredApp = "";
227 	fCommonType = "";
228 	entry_ref ref;
229 	for (int32 i = 0; refs.FindRef("refs", i, &ref) == B_OK; i++) {
230 		BNode node(&ref);
231 		if (node.InitCheck() != B_OK)
232 			continue;
233 
234 		BNodeInfo info(&node);
235 		if (info.InitCheck() != B_OK)
236 			continue;
237 
238 		// TODO: watch entries?
239 
240 		entry_ref* copiedRef = new entry_ref(ref);
241 		fEntries.AddItem(copiedRef);
242 
243 		char type[B_MIME_TYPE_LENGTH];
244 		if (info.GetType(type) != B_OK)
245 			type[0] = '\0';
246 
247 		if (i > 0) {
248 			if (fCommonType != type)
249 				fCommonType = "";
250 		} else
251 			fCommonType = type;
252 
253 		char preferredApp[B_MIME_TYPE_LENGTH];
254 		if (info.GetPreferredApp(preferredApp) != B_OK)
255 			preferredApp[0] = '\0';
256 
257 		if (i > 0) {
258 			if (fCommonPreferredApp != preferredApp)
259 				fCommonPreferredApp = "";
260 		} else
261 			fCommonPreferredApp = preferredApp;
262 
263 		if (i == 0)
264 			fIconView->SetTo(ref);
265 	}
266 
267 	fTypeControl->SetText(fCommonType.String());
268 	_UpdatePreferredApps();
269 
270 	fIconView->ShowIconHeap(fEntries.CountItems() != 1);
271 }
272 
273 
274 void
275 FileTypeWindow::_AdoptType(BMessage* message)
276 {
277 	entry_ref ref;
278 	if (message == NULL || message->FindRef("refs", &ref) != B_OK)
279 		return;
280 
281 	BNode node(&ref);
282 	status_t status = node.InitCheck();
283 
284 	char type[B_MIME_TYPE_LENGTH];
285 
286 	if (status == B_OK) {
287 			// get type from file
288 		BNodeInfo nodeInfo(&node);
289 		status = nodeInfo.InitCheck();
290 		if (status == B_OK) {
291 			if (nodeInfo.GetType(type) != B_OK)
292 				type[0] = '\0';
293 		}
294 	}
295 
296 	if (status != B_OK) {
297 		error_alert("Could not open file", status);
298 		return;
299 	}
300 
301 	fCommonType = type;
302 	fTypeControl->SetText(type);
303 	_AdoptType();
304 }
305 
306 
307 void
308 FileTypeWindow::_AdoptType()
309 {
310 	for (int32 i = 0; i < fEntries.CountItems(); i++) {
311 		BNode node(fEntries.ItemAt(i));
312 		BNodeInfo info(&node);
313 		if (node.InitCheck() != B_OK
314 			|| info.InitCheck() != B_OK)
315 			continue;
316 
317 		info.SetType(fCommonType.String());
318 	}
319 }
320 
321 
322 void
323 FileTypeWindow::_AdoptPreferredApp(BMessage* message, bool sameAs)
324 {
325 	if (retrieve_preferred_app(message, sameAs, fCommonType.String(),
326 			fCommonPreferredApp) == B_OK) {
327 		_AdoptPreferredApp();
328 		_UpdatePreferredApps();
329 	}
330 }
331 
332 
333 void
334 FileTypeWindow::_AdoptPreferredApp()
335 {
336 	for (int32 i = 0; i < fEntries.CountItems(); i++) {
337 		BNode node(fEntries.ItemAt(i));
338 		if (fCommonPreferredApp.Length() == 0) {
339 			node.RemoveAttr("BEOS:PREF_APP");
340 			continue;
341 		}
342 
343 		BNodeInfo info(&node);
344 		if (node.InitCheck() != B_OK
345 			|| info.InitCheck() != B_OK)
346 			continue;
347 
348 		info.SetPreferredApp(fCommonPreferredApp.String());
349 	}
350 }
351 
352 
353 void
354 FileTypeWindow::_UpdatePreferredApps()
355 {
356 	BMimeType type(fCommonType.String());
357 	update_preferred_app_menu(fPreferredField->Menu(), &type,
358 		kMsgPreferredAppChosen, fCommonPreferredApp.String());
359 }
360 
361 
362 void
363 FileTypeWindow::MessageReceived(BMessage* message)
364 {
365 	switch (message->what) {
366 		// File Type group
367 
368 		case kMsgTypeEntered:
369 			fCommonType = fTypeControl->Text();
370 			_AdoptType();
371 			break;
372 
373 		case kMsgSelectType:
374 		{
375 			BWindow* window = new TypeListWindow(fCommonType.String(),
376 				kMsgTypeSelected, this);
377 			window->Show();
378 			break;
379 		}
380 		case kMsgTypeSelected:
381 		{
382 			const char* type;
383 			if (message->FindString("type", &type) == B_OK) {
384 				fCommonType = type;
385 				fTypeControl->SetText(type);
386 				_AdoptType();
387 			}
388 			break;
389 		}
390 
391 		case kMsgSameTypeAs:
392 		{
393 			BMessage panel(kMsgOpenFilePanel);
394 			panel.AddString("title", "Select Same Type As");
395 			panel.AddInt32("message", kMsgSameTypeAsOpened);
396 			panel.AddMessenger("target", this);
397 
398 			be_app_messenger.SendMessage(&panel);
399 			break;
400 		}
401 		case kMsgSameTypeAsOpened:
402 			_AdoptType(message);
403 			break;
404 
405 		// Preferred Application group
406 
407 		case kMsgPreferredAppChosen:
408 		{
409 			const char* signature;
410 			if (message->FindString("signature", &signature) == B_OK)
411 				fCommonPreferredApp = signature;
412 			else
413 				fCommonPreferredApp = "";
414 
415 			_AdoptPreferredApp();
416 			break;
417 		}
418 
419 		case kMsgSelectPreferredApp:
420 		{
421 			BMessage panel(kMsgOpenFilePanel);
422 			panel.AddString("title", "Select Preferred Application");
423 			panel.AddInt32("message", kMsgPreferredAppOpened);
424 			panel.AddMessenger("target", this);
425 
426 			be_app_messenger.SendMessage(&panel);
427 			break;
428 		}
429 		case kMsgPreferredAppOpened:
430 			_AdoptPreferredApp(message, false);
431 			break;
432 
433 		case kMsgSamePreferredAppAs:
434 		{
435 			BMessage panel(kMsgOpenFilePanel);
436 			panel.AddString("title", "Select Same Preferred Application As");
437 			panel.AddInt32("message", kMsgSamePreferredAppAsOpened);
438 			panel.AddMessenger("target", this);
439 
440 			be_app_messenger.SendMessage(&panel);
441 			break;
442 		}
443 		case kMsgSamePreferredAppAsOpened:
444 			_AdoptPreferredApp(message, true);
445 			break;
446 
447 		// Other
448 
449 		case B_SIMPLE_DATA:
450 		{
451 			entry_ref ref;
452 			if (message->FindRef("refs", &ref) != B_OK)
453 				break;
454 
455 			BFile file(&ref, B_READ_ONLY);
456 			if (is_application(file))
457 				_AdoptPreferredApp(message, false);
458 			else
459 				_AdoptType(message);
460 			break;
461 		}
462 
463 		case B_META_MIME_CHANGED:
464 			const char* type;
465 			int32 which;
466 			if (message->FindString("be:type", &type) != B_OK
467 				|| message->FindInt32("be:which", &which) != B_OK)
468 				break;
469 
470 			if (which == B_MIME_TYPE_DELETED
471 #ifdef __HAIKU__
472 				|| which == B_SUPPORTED_TYPES_CHANGED
473 #endif
474 				)
475 				_UpdatePreferredApps();
476 			break;
477 
478 		default:
479 			BWindow::MessageReceived(message);
480 	}
481 }
482 
483 
484 bool
485 FileTypeWindow::QuitRequested()
486 {
487 	be_app->PostMessage(kMsgTypeWindowClosed);
488 	return true;
489 }
490 
491