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