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