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