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