xref: /haiku/src/preferences/filetypes/FileTypesWindow.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
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 "AttributeListView.h"
8 #include "AttributeWindow.h"
9 #include "DropTargetListView.h"
10 #include "ExtensionWindow.h"
11 #include "FileTypes.h"
12 #include "FileTypesWindow.h"
13 #include "IconView.h"
14 #include "MimeTypeListView.h"
15 #include "NewFileTypeWindow.h"
16 #include "PreferredAppMenu.h"
17 #include "StringView.h"
18 
19 #include <Alignment.h>
20 #include <AppFileInfo.h>
21 #include <Application.h>
22 #include <Bitmap.h>
23 #include <Box.h>
24 #include <Button.h>
25 #include <Catalog.h>
26 #include <ControlLook.h>
27 #include <LayoutBuilder.h>
28 #include <ListView.h>
29 #include <Locale.h>
30 #include <MenuBar.h>
31 #include <MenuField.h>
32 #include <MenuItem.h>
33 #include <Mime.h>
34 #include <NodeInfo.h>
35 #include <OutlineListView.h>
36 #include <PopUpMenu.h>
37 #include <ScrollView.h>
38 #include <SpaceLayoutItem.h>
39 #include <SplitView.h>
40 #include <TextControl.h>
41 
42 #include <OverrideAlert.h>
43 #include <be_apps/Tracker/RecentItems.h>
44 
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <strings.h>
48 
49 
50 #undef B_TRANSLATION_CONTEXT
51 #define B_TRANSLATION_CONTEXT "FileTypes Window"
52 
53 
54 const uint32 kMsgTypeSelected = 'typs';
55 const uint32 kMsgAddType = 'atyp';
56 const uint32 kMsgRemoveType = 'rtyp';
57 
58 const uint32 kMsgExtensionSelected = 'exts';
59 const uint32 kMsgExtensionInvoked = 'exti';
60 const uint32 kMsgAddExtension = 'aext';
61 const uint32 kMsgRemoveExtension = 'rext';
62 const uint32 kMsgRuleEntered = 'rule';
63 
64 const uint32 kMsgAttributeSelected = 'atrs';
65 const uint32 kMsgAttributeInvoked = 'atri';
66 const uint32 kMsgAddAttribute = 'aatr';
67 const uint32 kMsgRemoveAttribute = 'ratr';
68 const uint32 kMsgMoveUpAttribute = 'muat';
69 const uint32 kMsgMoveDownAttribute = 'mdat';
70 
71 const uint32 kMsgPreferredAppChosen = 'papc';
72 const uint32 kMsgSelectPreferredApp = 'slpa';
73 const uint32 kMsgSamePreferredAppAs = 'spaa';
74 
75 const uint32 kMsgPreferredAppOpened = 'paOp';
76 const uint32 kMsgSamePreferredAppAsOpened = 'spaO';
77 
78 const uint32 kMsgTypeEntered = 'type';
79 const uint32 kMsgDescriptionEntered = 'dsce';
80 
81 const uint32 kMsgToggleIcons = 'tgic';
82 const uint32 kMsgToggleRule = 'tgrl';
83 
84 
85 static const char* kAttributeNames[] = {
86 	"attr:public_name",
87 	"attr:name",
88 	"attr:type",
89 	"attr:editable",
90 	"attr:viewable",
91 	"attr:extra",
92 	"attr:alignment",
93 	"attr:width",
94 	"attr:display_as"
95 };
96 
97 
98 class TypeIconView : public IconView {
99 	typedef IconView _inherited;
100 
101 	public:
102 		TypeIconView(const char* name);
103 		virtual ~TypeIconView();
104 
105 		virtual void Draw(BRect updateRect);
106 		virtual void GetPreferredSize(float* _width, float* _height);
107 
108 	protected:
109 		virtual BRect BitmapRect() const;
110 };
111 
112 
113 class ExtensionListView : public DropTargetListView {
114 	public:
115 		ExtensionListView(const char* name,
116 			list_view_type type = B_SINGLE_SELECTION_LIST,
117 			uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE);
118 		virtual ~ExtensionListView();
119 
120 		virtual	BSize MinSize();
121 
122 		virtual void MessageReceived(BMessage* message);
123 		virtual bool AcceptsDrag(const BMessage* message);
124 
125 		void SetType(BMimeType* type);
126 
127 	private:
128 		BMimeType	fType;
129 		BSize		fMinSize;
130 };
131 
132 
133 //	#pragma mark -
134 
135 
136 TypeIconView::TypeIconView(const char* name)
137 	: IconView(name)
138 {
139 	ShowEmptyFrame(false);
140 	SetIconSize((icon_size)48);
141 }
142 
143 
144 TypeIconView::~TypeIconView()
145 {
146 }
147 
148 
149 void
150 TypeIconView::Draw(BRect updateRect)
151 {
152 	if (!IsEnabled())
153 		return;
154 
155 	IconView::Draw(updateRect);
156 
157 	const char* text = NULL;
158 
159 	switch (IconSource()) {
160 		case kNoIcon:
161 			text = B_TRANSLATE("no icon");
162 			break;
163 		case kApplicationIcon:
164 			text = B_TRANSLATE("(from application)");
165 			break;
166 		case kSupertypeIcon:
167 			text = B_TRANSLATE("(from super type)");
168 			break;
169 
170 		default:
171 			return;
172 	}
173 
174 	SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
175 		B_DISABLED_LABEL_TINT));
176 	SetLowColor(ViewColor());
177 
178 	font_height fontHeight;
179 	GetFontHeight(&fontHeight);
180 
181 	const BRect bitmapRect = _inherited::BitmapRect();
182 	float y = fontHeight.ascent;
183 	if (IconSource() == kNoIcon) {
184 		// center text in the middle of the icon
185 		y += (bitmapRect.Height() - fontHeight.ascent - fontHeight.descent) / 2.0f;
186 	} else
187 		y += bitmapRect.Height() + 3.0f;
188 
189 	DrawString(text, BPoint(ceilf((Bounds().Width() - StringWidth(text)) / 2.0f),
190 		ceilf(y)));
191 }
192 
193 
194 void
195 TypeIconView::GetPreferredSize(float* _width, float* _height)
196 {
197 	const BRect bitmapRect = _inherited::BitmapRect();
198 
199 	if (_width) {
200 		float a = StringWidth(B_TRANSLATE("(from application)"));
201 		float b = StringWidth(B_TRANSLATE("(from super type)"));
202 		float width = max_c(a, b);
203 		if (width < bitmapRect.Width())
204 			width = bitmapRect.Width();
205 
206 		*_width = ceilf(width);
207 	}
208 
209 	if (_height) {
210 		font_height fontHeight;
211 		GetFontHeight(&fontHeight);
212 
213 		*_height = bitmapRect.Height() + 3.0f + ceilf(fontHeight.ascent
214 			+ fontHeight.descent);
215 	}
216 }
217 
218 
219 BRect
220 TypeIconView::BitmapRect() const
221 {
222 	const BRect bitmapRect = _inherited::BitmapRect();
223 
224 	if (IconSource() == kNoIcon) {
225 		// this also defines the drop target area
226 		font_height fontHeight;
227 		GetFontHeight(&fontHeight);
228 
229 		float width = StringWidth(B_TRANSLATE("no icon")) + 8.0f;
230 		float height = ceilf(fontHeight.ascent + fontHeight.descent) + 6.0f;
231 		float x = (Bounds().Width() - width) / 2.0f;
232 		float y = ceilf((bitmapRect.Height() - fontHeight.ascent - fontHeight.descent)
233 			/ 2.0f) - 3.0f;
234 
235 		return BRect(x, y, x + width, y + height);
236 	}
237 
238 	float x = (Bounds().Width() - bitmapRect.Width()) / 2.0f;
239 	return BRect(x, 0.0f, x + bitmapRect.Width(), bitmapRect.Height());
240 }
241 
242 
243 //	#pragma mark -
244 
245 
246 ExtensionListView::ExtensionListView(const char* name,
247 		list_view_type type, uint32 flags)
248 	:
249 	DropTargetListView(name, type, flags)
250 {
251 }
252 
253 
254 ExtensionListView::~ExtensionListView()
255 {
256 }
257 
258 
259 BSize
260 ExtensionListView::MinSize()
261 {
262 	if (!fMinSize.IsWidthSet()) {
263 		BFont font;
264 		GetFont(&font);
265 		fMinSize.width = font.StringWidth(".mmmmm");
266 
267 		font_height height;
268 		font.GetHeight(&height);
269 		fMinSize.height = (height.ascent + height.descent + height.leading) * 3;
270 	}
271 
272 	return fMinSize;
273 }
274 
275 
276 void
277 ExtensionListView::MessageReceived(BMessage* message)
278 {
279 	if (message->WasDropped() && AcceptsDrag(message)) {
280 		// create extension list
281 		BList list;
282 		entry_ref ref;
283 		for (int32 index = 0; message->FindRef("refs", index, &ref) == B_OK;
284 				index++) {
285 			const char* point = strchr(ref.name, '.');
286 			if (point != NULL && point[1])
287 				list.AddItem(strdup(++point));
288 		}
289 
290 		merge_extensions(fType, list);
291 
292 		// delete extension list
293 		for (int32 index = list.CountItems(); index-- > 0;) {
294 			free(list.ItemAt(index));
295 		}
296 	} else
297 		DropTargetListView::MessageReceived(message);
298 }
299 
300 
301 bool
302 ExtensionListView::AcceptsDrag(const BMessage* message)
303 {
304 	if (fType.Type() == NULL)
305 		return false;
306 
307 	int32 count = 0;
308 	entry_ref ref;
309 
310 	for (int32 index = 0; message->FindRef("refs", index, &ref) == B_OK;
311 			index++) {
312 		const char* point = strchr(ref.name, '.');
313 		if (point != NULL && point[1])
314 			count++;
315 	}
316 
317 	return count > 0;
318 }
319 
320 
321 void
322 ExtensionListView::SetType(BMimeType* type)
323 {
324 	if (type != NULL)
325 		fType.SetTo(type->Type());
326 	else
327 		fType.Unset();
328 }
329 
330 
331 //	#pragma mark -
332 
333 
334 FileTypesWindow::FileTypesWindow(const BMessage& settings)
335 	:
336 	BWindow(_Frame(settings), B_TRANSLATE_SYSTEM_NAME("FileTypes"),
337 		B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS
338 		| B_AUTO_UPDATE_SIZE_LIMITS),
339 	fNewTypeWindow(NULL)
340 {
341 	bool showIcons;
342 	bool showRule;
343 	if (settings.FindBool("show_icons", &showIcons) != B_OK)
344 		showIcons = true;
345 	if (settings.FindBool("show_rule", &showRule) != B_OK)
346 		showRule = false;
347 
348 	float padding = be_control_look->DefaultItemSpacing();
349 	BAlignment labelAlignment = be_control_look->DefaultLabelAlignment();
350 	BAlignment fullAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
351 
352 	// add the menu
353 	BMenuBar* menuBar = new BMenuBar("");
354 
355 	BMenu* menu = new BMenu(B_TRANSLATE("File"));
356 	BMenuItem* item = new BMenuItem(
357 		B_TRANSLATE("New resource file" B_UTF8_ELLIPSIS), NULL, 'N',
358 		B_COMMAND_KEY);
359 	item->SetEnabled(false);
360 	menu->AddItem(item);
361 
362 	BMenu* recentsMenu = BRecentFilesList::NewFileListMenu(
363 		B_TRANSLATE("Open" B_UTF8_ELLIPSIS), NULL, NULL,
364 		be_app, 10, false, NULL, kSignature);
365 	item = new BMenuItem(recentsMenu, new BMessage(kMsgOpenFilePanel));
366 	item->SetShortcut('O', B_COMMAND_KEY);
367 	menu->AddItem(item);
368 
369 	menu->AddItem(new BMenuItem(
370 		B_TRANSLATE("Application types" B_UTF8_ELLIPSIS),
371 		new BMessage(kMsgOpenApplicationTypesWindow)));
372 	menu->AddSeparatorItem();
373 
374 	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
375 		new BMessage(B_QUIT_REQUESTED), 'Q', B_COMMAND_KEY));
376 	menu->SetTargetForItems(be_app);
377 	menuBar->AddItem(menu);
378 
379 	menu = new BMenu(B_TRANSLATE("Settings"));
380 	item = new BMenuItem(B_TRANSLATE("Show icons in list"),
381 		new BMessage(kMsgToggleIcons));
382 	item->SetMarked(showIcons);
383 	item->SetTarget(this);
384 	menu->AddItem(item);
385 
386 	item = new BMenuItem(B_TRANSLATE("Show recognition rule"),
387 		new BMessage(kMsgToggleRule));
388 	item->SetMarked(showRule);
389 	item->SetTarget(this);
390 	menu->AddItem(item);
391 	menuBar->AddItem(menu);
392 	menuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
393 
394 	// MIME Types list
395 	BButton* addTypeButton = new BButton("add",
396 		B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddType));
397 
398 	fRemoveTypeButton = new BButton("remove", B_TRANSLATE("Remove"),
399 		new BMessage(kMsgRemoveType) );
400 
401 	fTypeListView = new MimeTypeListView("typeview", NULL, showIcons, false);
402 	fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected));
403 	fTypeListView->SetExplicitMinSize(BSize(200, B_SIZE_UNSET));
404 
405 	BScrollView* typeListScrollView = new BScrollView("scrollview",
406 		fTypeListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
407 
408 	// "Icon" group
409 
410 	fIconView = new TypeIconView("icon");
411 	fIconBox = new BBox("Icon BBox");
412 	fIconBox->SetLabel(B_TRANSLATE("Icon"));
413 	BLayoutBuilder::Group<>(fIconBox, B_VERTICAL, padding)
414 		.SetInsets(padding)
415 		.AddGlue(1)
416 		.Add(fIconView, 3)
417 		.AddGlue(1);
418 
419 	// "File Recognition" group
420 
421 	fRecognitionBox = new BBox("Recognition Box");
422 	fRecognitionBox->SetLabel(B_TRANSLATE("File recognition"));
423 	fRecognitionBox->SetExplicitAlignment(fullAlignment);
424 
425 	fExtensionLabel = new StringView(B_TRANSLATE("Extensions:"), NULL);
426 	fExtensionLabel->LabelView()->SetExplicitAlignment(labelAlignment);
427 
428 	fAddExtensionButton = new BButton("add ext",
429 		B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddExtension));
430 	fAddExtensionButton->SetExplicitMaxSize(
431 		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
432 
433 	fRemoveExtensionButton = new BButton("remove ext", B_TRANSLATE("Remove"),
434 		new BMessage(kMsgRemoveExtension));
435 
436 	fExtensionListView = new ExtensionListView("listview ext",
437 		B_SINGLE_SELECTION_LIST);
438 	fExtensionListView->SetSelectionMessage(
439 		new BMessage(kMsgExtensionSelected));
440 	fExtensionListView->SetInvocationMessage(
441 		new BMessage(kMsgExtensionInvoked));
442 
443 	BScrollView* scrollView = new BScrollView("scrollview ext",
444 		fExtensionListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
445 
446 	fRuleControl = new BTextControl("rule", B_TRANSLATE("Rule:"), "",
447 		new BMessage(kMsgRuleEntered));
448 	fRuleControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
449 	fRuleControl->Hide();
450 
451 	BLayoutBuilder::Grid<>(fRecognitionBox, padding, padding / 2)
452 		.SetInsets(padding, padding * 2, padding, padding)
453 		.Add(fExtensionLabel->LabelView(), 0, 0)
454 		.Add(scrollView, 0, 1, 2, 2)
455 		.Add(fAddExtensionButton, 2, 1)
456 		.Add(fRemoveExtensionButton, 2, 2)
457 		.Add(fRuleControl, 0, 3, 3, 1);
458 
459 	// "Description" group
460 
461 	fDescriptionBox = new BBox("description BBox");
462 	fDescriptionBox->SetLabel(B_TRANSLATE("Description"));
463 	fDescriptionBox->SetExplicitAlignment(fullAlignment);
464 
465 	fInternalNameView = new StringView(B_TRANSLATE("Internal name:"), NULL);
466 	fInternalNameView->SetEnabled(false);
467 	fTypeNameControl = new BTextControl("type", B_TRANSLATE("Type name:"), "",
468 		new BMessage(kMsgTypeEntered));
469 	fDescriptionControl = new BTextControl("description",
470 		B_TRANSLATE("Description:"), "", new BMessage(kMsgDescriptionEntered));
471 
472 	BLayoutBuilder::Grid<>(fDescriptionBox, padding / 2, padding / 2)
473 		.SetInsets(padding, padding * 2, padding, padding)
474 		.Add(fInternalNameView->LabelView(), 0, 0)
475 		.Add(fInternalNameView->TextView(), 1, 0)
476 		.Add(fTypeNameControl->CreateLabelLayoutItem(), 0, 1)
477 		.Add(fTypeNameControl->CreateTextViewLayoutItem(), 1, 1, 2)
478 		.Add(fDescriptionControl->CreateLabelLayoutItem(), 0, 2)
479 		.Add(fDescriptionControl->CreateTextViewLayoutItem(), 1, 2, 2);
480 
481 	// "Preferred Application" group
482 
483 	fPreferredBox = new BBox("preferred BBox");
484 	fPreferredBox->SetLabel(B_TRANSLATE("Preferred application"));
485 
486 	menu = new BPopUpMenu("preferred");
487 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("None"),
488 		new BMessage(kMsgPreferredAppChosen)));
489 	item->SetMarked(true);
490 	fPreferredField = new BMenuField("preferred", (char*)NULL, menu);
491 
492 	fSelectButton = new BButton("select",
493 		B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
494 		new BMessage(kMsgSelectPreferredApp));
495 
496 	fSameAsButton = new BButton("same as",
497 		B_TRANSLATE("Same as" B_UTF8_ELLIPSIS),
498 		new BMessage(kMsgSamePreferredAppAs));
499 
500 	BLayoutBuilder::Group<>(fPreferredBox, B_HORIZONTAL, padding)
501 		.SetInsets(padding, padding * 2, padding, padding)
502 		.Add(fPreferredField)
503 		.Add(fSelectButton)
504 		.Add(fSameAsButton);
505 
506 	// "Extra Attributes" group
507 
508 	fAttributeBox = new BBox("Attribute Box");
509 	fAttributeBox->SetLabel(B_TRANSLATE("Extra attributes"));
510 
511 	fAddAttributeButton = new BButton("add attr",
512 		B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddAttribute));
513 	fAddAttributeButton->SetExplicitMaxSize(
514 		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
515 
516 	fRemoveAttributeButton = new BButton("remove attr", B_TRANSLATE("Remove"),
517 		new BMessage(kMsgRemoveAttribute));
518 	fRemoveAttributeButton->SetExplicitMaxSize(
519 		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
520 
521 	fMoveUpAttributeButton = new BButton("move up attr", B_TRANSLATE("Move up"),
522 		new BMessage(kMsgMoveUpAttribute));
523 	fMoveUpAttributeButton->SetExplicitMaxSize(
524 		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
525 	fMoveDownAttributeButton = new BButton("move down attr",
526 		B_TRANSLATE("Move down"), new BMessage(kMsgMoveDownAttribute));
527 	fMoveDownAttributeButton->SetExplicitMaxSize(
528 		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
529 
530 	fAttributeListView = new AttributeListView("listview attr");
531 	fAttributeListView->SetSelectionMessage(
532 		new BMessage(kMsgAttributeSelected));
533 	fAttributeListView->SetInvocationMessage(
534 		new BMessage(kMsgAttributeInvoked));
535 
536 	BScrollView* attributesScroller = new BScrollView("scrollview attr",
537 		fAttributeListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
538 
539 	BLayoutBuilder::Group<>(fAttributeBox, B_HORIZONTAL, padding)
540 		.SetInsets(padding, padding * 2, padding, padding)
541 		.Add(attributesScroller, 1.0f)
542 		.AddGroup(B_VERTICAL, padding / 2, 0.0f)
543 			.SetInsets(0)
544 			.Add(fAddAttributeButton)
545 			.Add(fRemoveAttributeButton)
546 			.AddStrut(padding)
547 			.Add(fMoveUpAttributeButton)
548 			.Add(fMoveDownAttributeButton)
549 			.AddGlue();
550 
551 	fMainSplitView = new BSplitView(B_HORIZONTAL, floorf(padding / 2));
552 
553 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
554 		.SetInsets(0)
555 		.Add(menuBar)
556 		.AddGroup(B_HORIZONTAL, 0)
557 			.SetInsets(B_USE_WINDOW_SPACING)
558 			.AddSplit(fMainSplitView)
559 				.AddGroup(B_VERTICAL, padding)
560 					.Add(typeListScrollView)
561 					.AddGroup(B_HORIZONTAL, padding)
562 						.Add(addTypeButton)
563 						.Add(fRemoveTypeButton)
564 						.AddGlue()
565 						.End()
566 					.End()
567 				// Right side
568 				.AddGroup(B_VERTICAL, padding)
569 					.AddGroup(B_HORIZONTAL, padding)
570 						.Add(fIconBox, 1)
571 						.Add(fRecognitionBox, 3)
572 						.End()
573 					.Add(fDescriptionBox)
574 					.Add(fPreferredBox)
575 					.Add(fAttributeBox, 5);
576 
577 	_SetType(NULL);
578 	_ShowSnifferRule(showRule);
579 
580 	float leftWeight;
581 	float rightWeight;
582 	if (settings.FindFloat("left_split_weight", &leftWeight) != B_OK
583 		|| settings.FindFloat("right_split_weight", &rightWeight) != B_OK) {
584 		leftWeight = 0.2;
585 		rightWeight = 1.0 - leftWeight;
586 	}
587 	fMainSplitView->SetItemWeight(0, leftWeight, false);
588 	fMainSplitView->SetItemWeight(1, rightWeight, true);
589 
590 	BMimeType::StartWatching(this);
591 }
592 
593 
594 FileTypesWindow::~FileTypesWindow()
595 {
596 	BMimeType::StopWatching(this);
597 }
598 
599 
600 void
601 FileTypesWindow::MessageReceived(BMessage* message)
602 {
603 	switch (message->what) {
604 		case B_SIMPLE_DATA:
605 		{
606 			type_code type;
607 			if (message->GetInfo("refs", &type) == B_OK
608 				&& type == B_REF_TYPE) {
609 				be_app->PostMessage(message);
610 			}
611 			break;
612 		}
613 
614 		case kMsgToggleIcons:
615 		{
616 			BMenuItem* item;
617 			if (message->FindPointer("source", (void **)&item) != B_OK)
618 				break;
619 
620 			item->SetMarked(!fTypeListView->IsShowingIcons());
621 			fTypeListView->ShowIcons(item->IsMarked());
622 
623 			// update settings
624 			BMessage update(kMsgSettingsChanged);
625 			update.AddBool("show_icons", item->IsMarked());
626 			be_app_messenger.SendMessage(&update);
627 			break;
628 		}
629 
630 		case kMsgToggleRule:
631 		{
632 			BMenuItem* item;
633 			if (message->FindPointer("source", (void **)&item) != B_OK)
634 				break;
635 
636 			item->SetMarked(fRuleControl->IsHidden());
637 			_ShowSnifferRule(item->IsMarked());
638 
639 			// update settings
640 			BMessage update(kMsgSettingsChanged);
641 			update.AddBool("show_rule", item->IsMarked());
642 			be_app_messenger.SendMessage(&update);
643 			break;
644 		}
645 
646 		case kMsgTypeSelected:
647 		{
648 			int32 index;
649 			if (message->FindInt32("index", &index) == B_OK) {
650 				MimeTypeItem* item
651 					= (MimeTypeItem*)fTypeListView->ItemAt(index);
652 				if (item != NULL) {
653 					BMimeType type(item->Type());
654 					_SetType(&type);
655 				} else
656 					_SetType(NULL);
657 			}
658 			break;
659 		}
660 
661 		case kMsgAddType:
662 			if (fNewTypeWindow == NULL) {
663 				fNewTypeWindow
664 					= new NewFileTypeWindow(this, fCurrentType.Type());
665 				fNewTypeWindow->Show();
666 			} else
667 				fNewTypeWindow->Activate();
668 			break;
669 
670 		case kMsgNewTypeWindowClosed:
671 			fNewTypeWindow = NULL;
672 			break;
673 
674 		case kMsgRemoveType:
675 		{
676 			if (fCurrentType.Type() == NULL)
677 				break;
678 
679 			BAlert* alert;
680 			if (fCurrentType.IsSupertypeOnly()) {
681 				alert = new BPrivate::OverrideAlert(
682 					B_TRANSLATE("FileTypes request"),
683 					B_TRANSLATE("Removing a super type cannot be reverted.\n"
684 					"All file types that belong to this super type "
685 					"will be lost!\n\n"
686 					"Are you sure you want to do this? To remove the whole "
687 					"group, hold down the Shift key and press \"Remove\"."),
688 					B_TRANSLATE("Remove"), B_SHIFT_KEY, B_TRANSLATE("Cancel"),
689 					0, NULL, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
690 				alert->SetShortcut(1, B_ESCAPE);
691 			} else {
692 				alert = new BAlert(B_TRANSLATE("FileTypes request"),
693 					B_TRANSLATE("Removing a file type cannot be reverted.\n"
694 					"Are you sure you want to remove it?"),
695 					B_TRANSLATE("Remove"), B_TRANSLATE("Cancel"),
696 					NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
697 				alert->SetShortcut(1, B_ESCAPE);
698 			}
699 			if (alert->Go())
700 				break;
701 
702 			status_t status = fCurrentType.Delete();
703 			if (status != B_OK) {
704 				fprintf(stderr, B_TRANSLATE(
705 					"Could not remove file type: %s\n"), strerror(status));
706 			}
707 			break;
708 		}
709 
710 		case kMsgSelectNewType:
711 		{
712 			const char* type;
713 			if (message->FindString("type", &type) == B_OK)
714 				fTypeListView->SelectNewType(type);
715 			break;
716 		}
717 
718 		// File Recognition group
719 
720 		case kMsgExtensionSelected:
721 		{
722 			int32 index;
723 			if (message->FindInt32("index", &index) == B_OK) {
724 				BStringItem* item
725 					= (BStringItem*)fExtensionListView->ItemAt(index);
726 				fRemoveExtensionButton->SetEnabled(item != NULL);
727 			}
728 			break;
729 		}
730 
731 		case kMsgExtensionInvoked:
732 		{
733 			if (fCurrentType.Type() == NULL)
734 				break;
735 
736 			int32 index;
737 			if (message->FindInt32("index", &index) == B_OK) {
738 				BStringItem* item
739 					= (BStringItem*)fExtensionListView->ItemAt(index);
740 				if (item == NULL)
741 					break;
742 
743 				BWindow* window
744 					= new ExtensionWindow(this, fCurrentType, item->Text());
745 				window->Show();
746 			}
747 			break;
748 		}
749 
750 		case kMsgAddExtension:
751 		{
752 			if (fCurrentType.Type() == NULL)
753 				break;
754 
755 			BWindow* window = new ExtensionWindow(this, fCurrentType, NULL);
756 			window->Show();
757 			break;
758 		}
759 
760 		case kMsgRemoveExtension:
761 		{
762 			int32 index = fExtensionListView->CurrentSelection();
763 			if (index < 0 || fCurrentType.Type() == NULL)
764 				break;
765 
766 			BMessage extensions;
767 			if (fCurrentType.GetFileExtensions(&extensions) == B_OK) {
768 				extensions.RemoveData("extensions", index);
769 				fCurrentType.SetFileExtensions(&extensions);
770 			}
771 			break;
772 		}
773 
774 		case kMsgRuleEntered:
775 		{
776 			// check rule
777 			BString parseError;
778 			if (BMimeType::CheckSnifferRule(fRuleControl->Text(),
779 					&parseError) != B_OK) {
780 				parseError.Prepend(
781 					B_TRANSLATE("Recognition rule is not valid:\n\n"));
782 				error_alert(parseError.String());
783 			} else
784 				fCurrentType.SetSnifferRule(fRuleControl->Text());
785 			break;
786 		}
787 
788 		// Description group
789 
790 		case kMsgTypeEntered:
791 		{
792 			fCurrentType.SetShortDescription(fTypeNameControl->Text());
793 			break;
794 		}
795 
796 		case kMsgDescriptionEntered:
797 		{
798 			fCurrentType.SetLongDescription(fDescriptionControl->Text());
799 			break;
800 		}
801 
802 		// Preferred Application group
803 
804 		case kMsgPreferredAppChosen:
805 		{
806 			const char* signature;
807 			if (message->FindString("signature", &signature) != B_OK)
808 				signature = NULL;
809 
810 			fCurrentType.SetPreferredApp(signature);
811 			break;
812 		}
813 
814 		case kMsgSelectPreferredApp:
815 		{
816 			BMessage panel(kMsgOpenFilePanel);
817 			panel.AddString("title",
818 				B_TRANSLATE("Select preferred application"));
819 			panel.AddInt32("message", kMsgPreferredAppOpened);
820 			panel.AddMessenger("target", this);
821 
822 			be_app_messenger.SendMessage(&panel);
823 			break;
824 		}
825 		case kMsgPreferredAppOpened:
826 			_AdoptPreferredApplication(message, false);
827 			break;
828 
829 		case kMsgSamePreferredAppAs:
830 		{
831 			BMessage panel(kMsgOpenFilePanel);
832 			panel.AddString("title",
833 				B_TRANSLATE("Select same preferred application as"));
834 			panel.AddInt32("message", kMsgSamePreferredAppAsOpened);
835 			panel.AddMessenger("target", this);
836 			panel.AddBool("allowDirs", true);
837 
838 			be_app_messenger.SendMessage(&panel);
839 			break;
840 		}
841 		case kMsgSamePreferredAppAsOpened:
842 			_AdoptPreferredApplication(message, true);
843 			break;
844 
845 		// Extra Attributes group
846 
847 		case kMsgAttributeSelected:
848 		{
849 			int32 index;
850 			if (message->FindInt32("index", &index) == B_OK) {
851 				AttributeItem* item
852 					= (AttributeItem*)fAttributeListView->ItemAt(index);
853 				fRemoveAttributeButton->SetEnabled(item != NULL);
854 				fMoveUpAttributeButton->SetEnabled(index > 0);
855 				fMoveDownAttributeButton->SetEnabled(index >= 0
856 					&& index < fAttributeListView->CountItems() - 1);
857 			}
858 			break;
859 		}
860 
861 		case kMsgAttributeInvoked:
862 		{
863 			if (fCurrentType.Type() == NULL)
864 				break;
865 
866 			int32 index;
867 			if (message->FindInt32("index", &index) == B_OK) {
868 				AttributeItem* item
869 					= (AttributeItem*)fAttributeListView->ItemAt(index);
870 				if (item == NULL)
871 					break;
872 
873 				BWindow* window = new AttributeWindow(this, fCurrentType,
874 					item);
875 				window->Show();
876 			}
877 			break;
878 		}
879 
880 		case kMsgAddAttribute:
881 		{
882 			if (fCurrentType.Type() == NULL)
883 				break;
884 
885 			BWindow* window = new AttributeWindow(this, fCurrentType, NULL);
886 			window->Show();
887 			break;
888 		}
889 
890 		case kMsgRemoveAttribute:
891 		{
892 			int32 index = fAttributeListView->CurrentSelection();
893 			if (index < 0 || fCurrentType.Type() == NULL)
894 				break;
895 
896 			BMessage attributes;
897 			if (fCurrentType.GetAttrInfo(&attributes) == B_OK) {
898 				for (uint32 i = 0; i <
899 						sizeof(kAttributeNames) / sizeof(kAttributeNames[0]);
900 						i++) {
901 					attributes.RemoveData(kAttributeNames[i], index);
902 				}
903 
904 				fCurrentType.SetAttrInfo(&attributes);
905 			}
906 			break;
907 		}
908 
909 		case kMsgMoveUpAttribute:
910 		{
911 			int32 index = fAttributeListView->CurrentSelection();
912 			if (index < 1 || fCurrentType.Type() == NULL)
913 				break;
914 
915 			_MoveUpAttributeIndex(index);
916 			break;
917 		}
918 
919 		case kMsgMoveDownAttribute:
920 		{
921 			int32 index = fAttributeListView->CurrentSelection();
922 			if (index < 0 || index == fAttributeListView->CountItems() - 1
923 				|| fCurrentType.Type() == NULL) {
924 				break;
925 			}
926 
927 			_MoveUpAttributeIndex(index + 1);
928 			break;
929 		}
930 
931 		case B_META_MIME_CHANGED:
932 		{
933 			const char* type;
934 			int32 which;
935 			if (message->FindString("be:type", &type) != B_OK
936 				|| message->FindInt32("be:which", &which) != B_OK)
937 				break;
938 
939 			if (fCurrentType.Type() == NULL)
940 				break;
941 
942 			if (!strcasecmp(fCurrentType.Type(), type)) {
943 				if (which != B_MIME_TYPE_DELETED)
944 					_SetType(&fCurrentType, which);
945 				else
946 					_SetType(NULL);
947 			} else {
948 				// this change could still affect our current type
949 
950 				if (which == B_MIME_TYPE_DELETED
951 					|| which == B_SUPPORTED_TYPES_CHANGED
952 					|| which == B_PREFERRED_APP_CHANGED) {
953 					_UpdatePreferredApps(&fCurrentType);
954 				}
955 			}
956 			break;
957 		}
958 
959 		default:
960 			BWindow::MessageReceived(message);
961 	}
962 }
963 
964 
965 void
966 FileTypesWindow::SelectType(const char* type)
967 {
968 	fTypeListView->SelectType(type);
969 }
970 
971 
972 bool
973 FileTypesWindow::QuitRequested()
974 {
975 	BMessage update(kMsgSettingsChanged);
976 	update.AddRect("file_types_frame", Frame());
977 	update.AddFloat("left_split_weight", fMainSplitView->ItemWeight((int32)0));
978 	update.AddFloat("right_split_weight", fMainSplitView->ItemWeight(1));
979 	be_app_messenger.SendMessage(&update);
980 
981 	be_app->PostMessage(kMsgTypesWindowClosed);
982 	return true;
983 }
984 
985 
986 void
987 FileTypesWindow::PlaceSubWindow(BWindow* window)
988 {
989 	window->MoveTo(Frame().left + (Frame().Width() - window->Frame().Width())
990 		/ 2.0f, Frame().top + (Frame().Height() - window->Frame().Height())
991 		/ 2.0f);
992 }
993 
994 
995 // #pragma mark - private
996 
997 
998 BRect
999 FileTypesWindow::_Frame(const BMessage& settings) const
1000 {
1001 	BRect rect;
1002 	if (settings.FindRect("file_types_frame", &rect) == B_OK)
1003 		return rect;
1004 
1005 	return BRect(80.0f, 80.0f, 0.0f, 0.0f);
1006 }
1007 
1008 
1009 void
1010 FileTypesWindow::_ShowSnifferRule(bool show)
1011 {
1012 	if (fRuleControl->IsHidden() == !show)
1013 		return;
1014 
1015 	if (!show)
1016 		fRuleControl->Hide();
1017 	else
1018 		fRuleControl->Show();
1019 }
1020 
1021 
1022 void
1023 FileTypesWindow::_UpdateExtensions(BMimeType* type)
1024 {
1025 	// clear list
1026 
1027 	for (int32 i = fExtensionListView->CountItems(); i-- > 0;) {
1028 		delete fExtensionListView->ItemAt(i);
1029 	}
1030 	fExtensionListView->MakeEmpty();
1031 
1032 	// fill it again
1033 
1034 	if (type == NULL)
1035 		return;
1036 
1037 	BMessage extensions;
1038 	if (type->GetFileExtensions(&extensions) != B_OK)
1039 		return;
1040 
1041 	const char* extension;
1042 	int32 i = 0;
1043 	while (extensions.FindString("extensions", i++, &extension) == B_OK) {
1044 		char dotExtension[B_FILE_NAME_LENGTH];
1045 		snprintf(dotExtension, B_FILE_NAME_LENGTH, ".%s", extension);
1046 
1047 		fExtensionListView->AddItem(new BStringItem(dotExtension));
1048 	}
1049 }
1050 
1051 
1052 void
1053 FileTypesWindow::_AdoptPreferredApplication(BMessage* message, bool sameAs)
1054 {
1055 	if (fCurrentType.Type() == NULL)
1056 		return;
1057 
1058 	BString preferred;
1059 	if (retrieve_preferred_app(message, sameAs, fCurrentType.Type(), preferred)
1060 		!= B_OK) {
1061 		return;
1062 	}
1063 
1064 	status_t status = fCurrentType.SetPreferredApp(preferred.String());
1065 	if (status != B_OK)
1066 		error_alert(B_TRANSLATE("Could not set preferred application"),
1067 			status);
1068 }
1069 
1070 
1071 void
1072 FileTypesWindow::_UpdatePreferredApps(BMimeType* type)
1073 {
1074 	update_preferred_app_menu(fPreferredField->Menu(), type,
1075 		kMsgPreferredAppChosen);
1076 }
1077 
1078 
1079 void
1080 FileTypesWindow::_UpdateIcon(BMimeType* type)
1081 {
1082 	if (type != NULL)
1083 		fIconView->SetTo(*type);
1084 	else
1085 		fIconView->Unset();
1086 }
1087 
1088 
1089 void
1090 FileTypesWindow::_SetType(BMimeType* type, int32 forceUpdate)
1091 {
1092 	bool enabled = type != NULL;
1093 
1094 	// update controls
1095 
1096 	if (type != NULL) {
1097 		if (fCurrentType == *type) {
1098 			if (!forceUpdate)
1099 				return;
1100 		} else
1101 			forceUpdate = B_EVERYTHING_CHANGED;
1102 
1103 		if (&fCurrentType != type)
1104 			fCurrentType.SetTo(type->Type());
1105 
1106 		fInternalNameView->SetText(type->Type());
1107 
1108 		char description[B_MIME_TYPE_LENGTH];
1109 
1110 		if ((forceUpdate & B_SHORT_DESCRIPTION_CHANGED) != 0) {
1111 			if (type->GetShortDescription(description) != B_OK)
1112 				description[0] = '\0';
1113 			fTypeNameControl->SetText(description);
1114 		}
1115 
1116 		if ((forceUpdate & B_LONG_DESCRIPTION_CHANGED) != 0) {
1117 			if (type->GetLongDescription(description) != B_OK)
1118 				description[0] = '\0';
1119 			fDescriptionControl->SetText(description);
1120 		}
1121 
1122 		if ((forceUpdate & B_SNIFFER_RULE_CHANGED) != 0) {
1123 			BString rule;
1124 			if (type->GetSnifferRule(&rule) != B_OK)
1125 				rule = "";
1126 			fRuleControl->SetText(rule.String());
1127 		}
1128 
1129 		fExtensionListView->SetType(&fCurrentType);
1130 	} else {
1131 		fCurrentType.Unset();
1132 		fInternalNameView->SetText(NULL);
1133 		fTypeNameControl->SetText(NULL);
1134 		fDescriptionControl->SetText(NULL);
1135 		fRuleControl->SetText(NULL);
1136 		fPreferredField->Menu()->ItemAt(0)->SetMarked(true);
1137 		fExtensionListView->SetType(NULL);
1138 		fAttributeListView->SetTo(NULL);
1139 	}
1140 
1141 	if ((forceUpdate & B_FILE_EXTENSIONS_CHANGED) != 0)
1142 		_UpdateExtensions(type);
1143 
1144 	if ((forceUpdate & B_PREFERRED_APP_CHANGED) != 0)
1145 		_UpdatePreferredApps(type);
1146 
1147 	if ((forceUpdate & (B_ICON_CHANGED | B_PREFERRED_APP_CHANGED)) != 0)
1148 		_UpdateIcon(type);
1149 
1150 	if ((forceUpdate & B_ATTR_INFO_CHANGED) != 0)
1151 		fAttributeListView->SetTo(type);
1152 
1153 	// enable/disable controls
1154 
1155 	fIconView->SetEnabled(enabled);
1156 
1157 	fInternalNameView->SetEnabled(enabled);
1158 	fTypeNameControl->SetEnabled(enabled);
1159 	fDescriptionControl->SetEnabled(enabled);
1160 	fPreferredField->SetEnabled(enabled);
1161 
1162 	fRemoveTypeButton->SetEnabled(enabled);
1163 
1164 	fSelectButton->SetEnabled(enabled);
1165 	fSameAsButton->SetEnabled(enabled);
1166 
1167 	fExtensionLabel->SetEnabled(enabled);
1168 	fAddExtensionButton->SetEnabled(enabled);
1169 	fRemoveExtensionButton->SetEnabled(false);
1170 	fRuleControl->SetEnabled(enabled);
1171 
1172 	fAddAttributeButton->SetEnabled(enabled);
1173 	fRemoveAttributeButton->SetEnabled(false);
1174 	fMoveUpAttributeButton->SetEnabled(false);
1175 	fMoveDownAttributeButton->SetEnabled(false);
1176 }
1177 
1178 
1179 void
1180 FileTypesWindow::_MoveUpAttributeIndex(int32 index)
1181 {
1182 	BMessage attributes;
1183 	if (fCurrentType.GetAttrInfo(&attributes) != B_OK)
1184 		return;
1185 
1186 	// Iterate over all known attribute fields, and for each field,
1187 	// iterate over all fields of the same name and build a copy
1188 	// of the attributes message with the field at the given index swapped
1189 	// with the previous field.
1190 	BMessage resortedAttributes;
1191 	for (uint32 i = 0; i <
1192 			sizeof(kAttributeNames) / sizeof(kAttributeNames[0]);
1193 			i++) {
1194 
1195 		type_code type;
1196 		int32 count;
1197 		bool isFixedSize;
1198 		if (attributes.GetInfo(kAttributeNames[i], &type, &count,
1199 				&isFixedSize) != B_OK) {
1200 			// Apparently the message does not contain this name,
1201 			// so just ignore this attribute name.
1202 			// NOTE: This shows that the attribute description is
1203 			// too fragile. It would have been better to pack each
1204 			// attribute description into a separate BMessage.
1205 			continue;
1206 		}
1207 
1208 		for (int32 j = 0; j < count; j++) {
1209 			const void* data;
1210 			ssize_t size;
1211 			int32 originalIndex;
1212 			if (j == index - 1)
1213 				originalIndex = j + 1;
1214 			else if (j == index)
1215 				originalIndex = j - 1;
1216 			else
1217 				originalIndex = j;
1218 			attributes.FindData(kAttributeNames[i], type,
1219 				originalIndex, &data, &size);
1220 			if (j == 0) {
1221 				resortedAttributes.AddData(kAttributeNames[i], type,
1222 					data, size, isFixedSize);
1223 			} else {
1224 				resortedAttributes.AddData(kAttributeNames[i], type,
1225 					data, size);
1226 			}
1227 		}
1228 	}
1229 
1230 	// Setting it directly on the type will trigger an update of the GUI as
1231 	// well. TODO: FileTypes is heavily descructive, it should use an
1232 	// Undo/Redo stack.
1233 	fCurrentType.SetAttrInfo(&resortedAttributes);
1234 }
1235 
1236 
1237