xref: /haiku/src/preferences/filetypes/FileTypesWindow.cpp (revision f73f5d4c42a01ece688cbb57b5d332cc0f68b2c6)
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 				alert->SetShortcut(1, B_ESCAPE);
683 			} else {
684 				alert = new BAlert(B_TRANSLATE("FileTypes request"),
685 					B_TRANSLATE("Removing a file type cannot be reverted.\n"
686 					"Are you sure you want to remove it?"),
687 					B_TRANSLATE("Remove"), B_TRANSLATE("Cancel"),
688 					NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
689 				alert->SetShortcut(1, B_ESCAPE);
690 			}
691 			if (alert->Go())
692 				break;
693 
694 			status_t status = fCurrentType.Delete();
695 			if (status != B_OK) {
696 				fprintf(stderr, B_TRANSLATE(
697 					"Could not remove file type: %s\n"), strerror(status));
698 			}
699 			break;
700 		}
701 
702 		case kMsgSelectNewType:
703 		{
704 			const char* type;
705 			if (message->FindString("type", &type) == B_OK)
706 				fTypeListView->SelectNewType(type);
707 			break;
708 		}
709 
710 		// File Recognition group
711 
712 		case kMsgExtensionSelected:
713 		{
714 			int32 index;
715 			if (message->FindInt32("index", &index) == B_OK) {
716 				BStringItem* item
717 					= (BStringItem*)fExtensionListView->ItemAt(index);
718 				fRemoveExtensionButton->SetEnabled(item != NULL);
719 			}
720 			break;
721 		}
722 
723 		case kMsgExtensionInvoked:
724 		{
725 			if (fCurrentType.Type() == NULL)
726 				break;
727 
728 			int32 index;
729 			if (message->FindInt32("index", &index) == B_OK) {
730 				BStringItem* item
731 					= (BStringItem*)fExtensionListView->ItemAt(index);
732 				if (item == NULL)
733 					break;
734 
735 				BWindow* window
736 					= new ExtensionWindow(this, fCurrentType, item->Text());
737 				window->Show();
738 			}
739 			break;
740 		}
741 
742 		case kMsgAddExtension:
743 		{
744 			if (fCurrentType.Type() == NULL)
745 				break;
746 
747 			BWindow* window = new ExtensionWindow(this, fCurrentType, NULL);
748 			window->Show();
749 			break;
750 		}
751 
752 		case kMsgRemoveExtension:
753 		{
754 			int32 index = fExtensionListView->CurrentSelection();
755 			if (index < 0 || fCurrentType.Type() == NULL)
756 				break;
757 
758 			BMessage extensions;
759 			if (fCurrentType.GetFileExtensions(&extensions) == B_OK) {
760 				extensions.RemoveData("extensions", index);
761 				fCurrentType.SetFileExtensions(&extensions);
762 			}
763 			break;
764 		}
765 
766 		case kMsgRuleEntered:
767 		{
768 			// check rule
769 			BString parseError;
770 			if (BMimeType::CheckSnifferRule(fRuleControl->Text(),
771 					&parseError) != B_OK) {
772 				parseError.Prepend(
773 					B_TRANSLATE("Recognition rule is not valid:\n\n"));
774 				error_alert(parseError.String());
775 			} else
776 				fCurrentType.SetSnifferRule(fRuleControl->Text());
777 			break;
778 		}
779 
780 		// Description group
781 
782 		case kMsgTypeEntered:
783 		{
784 			fCurrentType.SetShortDescription(fTypeNameControl->Text());
785 			break;
786 		}
787 
788 		case kMsgDescriptionEntered:
789 		{
790 			fCurrentType.SetLongDescription(fDescriptionControl->Text());
791 			break;
792 		}
793 
794 		// Preferred Application group
795 
796 		case kMsgPreferredAppChosen:
797 		{
798 			const char* signature;
799 			if (message->FindString("signature", &signature) != B_OK)
800 				signature = NULL;
801 
802 			fCurrentType.SetPreferredApp(signature);
803 			break;
804 		}
805 
806 		case kMsgSelectPreferredApp:
807 		{
808 			BMessage panel(kMsgOpenFilePanel);
809 			panel.AddString("title",
810 				B_TRANSLATE("Select preferred application"));
811 			panel.AddInt32("message", kMsgPreferredAppOpened);
812 			panel.AddMessenger("target", this);
813 
814 			be_app_messenger.SendMessage(&panel);
815 			break;
816 		}
817 		case kMsgPreferredAppOpened:
818 			_AdoptPreferredApplication(message, false);
819 			break;
820 
821 		case kMsgSamePreferredAppAs:
822 		{
823 			BMessage panel(kMsgOpenFilePanel);
824 			panel.AddString("title",
825 				B_TRANSLATE("Select same preferred application as"));
826 			panel.AddInt32("message", kMsgSamePreferredAppAsOpened);
827 			panel.AddMessenger("target", this);
828 
829 			be_app_messenger.SendMessage(&panel);
830 			break;
831 		}
832 		case kMsgSamePreferredAppAsOpened:
833 			_AdoptPreferredApplication(message, true);
834 			break;
835 
836 		// Extra Attributes group
837 
838 		case kMsgAttributeSelected:
839 		{
840 			int32 index;
841 			if (message->FindInt32("index", &index) == B_OK) {
842 				AttributeItem* item
843 					= (AttributeItem*)fAttributeListView->ItemAt(index);
844 				fRemoveAttributeButton->SetEnabled(item != NULL);
845 				fMoveUpAttributeButton->SetEnabled(index > 0);
846 				fMoveDownAttributeButton->SetEnabled(index >= 0
847 					&& index < fAttributeListView->CountItems() - 1);
848 			}
849 			break;
850 		}
851 
852 		case kMsgAttributeInvoked:
853 		{
854 			if (fCurrentType.Type() == NULL)
855 				break;
856 
857 			int32 index;
858 			if (message->FindInt32("index", &index) == B_OK) {
859 				AttributeItem* item
860 					= (AttributeItem*)fAttributeListView->ItemAt(index);
861 				if (item == NULL)
862 					break;
863 
864 				BWindow* window = new AttributeWindow(this, fCurrentType,
865 					item);
866 				window->Show();
867 			}
868 			break;
869 		}
870 
871 		case kMsgAddAttribute:
872 		{
873 			if (fCurrentType.Type() == NULL)
874 				break;
875 
876 			BWindow* window = new AttributeWindow(this, fCurrentType, NULL);
877 			window->Show();
878 			break;
879 		}
880 
881 		case kMsgRemoveAttribute:
882 		{
883 			int32 index = fAttributeListView->CurrentSelection();
884 			if (index < 0 || fCurrentType.Type() == NULL)
885 				break;
886 
887 			BMessage attributes;
888 			if (fCurrentType.GetAttrInfo(&attributes) == B_OK) {
889 				for (uint32 i = 0; i <
890 						sizeof(kAttributeNames) / sizeof(kAttributeNames[0]);
891 						i++) {
892 					attributes.RemoveData(kAttributeNames[i], index);
893 				}
894 
895 				fCurrentType.SetAttrInfo(&attributes);
896 			}
897 			break;
898 		}
899 
900 		case kMsgMoveUpAttribute:
901 		{
902 			int32 index = fAttributeListView->CurrentSelection();
903 			if (index < 1 || fCurrentType.Type() == NULL)
904 				break;
905 
906 			_MoveUpAttributeIndex(index);
907 			break;
908 		}
909 
910 		case kMsgMoveDownAttribute:
911 		{
912 			int32 index = fAttributeListView->CurrentSelection();
913 			if (index < 0 || index == fAttributeListView->CountItems() - 1
914 				|| fCurrentType.Type() == NULL) {
915 				break;
916 			}
917 
918 			_MoveUpAttributeIndex(index + 1);
919 			break;
920 		}
921 
922 		case B_META_MIME_CHANGED:
923 		{
924 			const char* type;
925 			int32 which;
926 			if (message->FindString("be:type", &type) != B_OK
927 				|| message->FindInt32("be:which", &which) != B_OK)
928 				break;
929 
930 			if (fCurrentType.Type() == NULL)
931 				break;
932 
933 			if (!strcasecmp(fCurrentType.Type(), type)) {
934 				if (which != B_MIME_TYPE_DELETED)
935 					_SetType(&fCurrentType, which);
936 				else
937 					_SetType(NULL);
938 			} else {
939 				// this change could still affect our current type
940 
941 				if (which == B_MIME_TYPE_DELETED
942 					|| which == B_SUPPORTED_TYPES_CHANGED
943 					|| which == B_PREFERRED_APP_CHANGED) {
944 					_UpdatePreferredApps(&fCurrentType);
945 				}
946 			}
947 			break;
948 		}
949 
950 		default:
951 			BWindow::MessageReceived(message);
952 	}
953 }
954 
955 
956 void
957 FileTypesWindow::SelectType(const char* type)
958 {
959 	fTypeListView->SelectType(type);
960 }
961 
962 
963 bool
964 FileTypesWindow::QuitRequested()
965 {
966 	BMessage update(kMsgSettingsChanged);
967 	update.AddRect("file_types_frame", Frame());
968 	update.AddFloat("left_split_weight", fMainSplitView->ItemWeight((int32)0));
969 	update.AddFloat("right_split_weight", fMainSplitView->ItemWeight(1));
970 	be_app_messenger.SendMessage(&update);
971 
972 	be_app->PostMessage(kMsgTypesWindowClosed);
973 	return true;
974 }
975 
976 
977 void
978 FileTypesWindow::PlaceSubWindow(BWindow* window)
979 {
980 	window->MoveTo(Frame().left + (Frame().Width() - window->Frame().Width())
981 		/ 2.0f, Frame().top + (Frame().Height() - window->Frame().Height())
982 		/ 2.0f);
983 }
984 
985 
986 // #pragma mark - private
987 
988 
989 BRect
990 FileTypesWindow::_Frame(const BMessage& settings) const
991 {
992 	BRect rect;
993 	if (settings.FindRect("file_types_frame", &rect) == B_OK)
994 		return rect;
995 
996 	return BRect(80.0f, 80.0f, 0.0f, 0.0f);
997 }
998 
999 
1000 void
1001 FileTypesWindow::_ShowSnifferRule(bool show)
1002 {
1003 	if (fRuleControl->IsHidden() == !show)
1004 		return;
1005 
1006 	if (!show)
1007 		fRuleControl->Hide();
1008 	else
1009 		fRuleControl->Show();
1010 }
1011 
1012 
1013 void
1014 FileTypesWindow::_UpdateExtensions(BMimeType* type)
1015 {
1016 	// clear list
1017 
1018 	for (int32 i = fExtensionListView->CountItems(); i-- > 0;) {
1019 		delete fExtensionListView->ItemAt(i);
1020 	}
1021 	fExtensionListView->MakeEmpty();
1022 
1023 	// fill it again
1024 
1025 	if (type == NULL)
1026 		return;
1027 
1028 	BMessage extensions;
1029 	if (type->GetFileExtensions(&extensions) != B_OK)
1030 		return;
1031 
1032 	const char* extension;
1033 	int32 i = 0;
1034 	while (extensions.FindString("extensions", i++, &extension) == B_OK) {
1035 		char dotExtension[B_FILE_NAME_LENGTH];
1036 		snprintf(dotExtension, B_FILE_NAME_LENGTH, ".%s", extension);
1037 
1038 		fExtensionListView->AddItem(new BStringItem(dotExtension));
1039 	}
1040 }
1041 
1042 
1043 void
1044 FileTypesWindow::_AdoptPreferredApplication(BMessage* message, bool sameAs)
1045 {
1046 	if (fCurrentType.Type() == NULL)
1047 		return;
1048 
1049 	BString preferred;
1050 	if (retrieve_preferred_app(message, sameAs, fCurrentType.Type(), preferred)
1051 		!= B_OK) {
1052 		return;
1053 	}
1054 
1055 	status_t status = fCurrentType.SetPreferredApp(preferred.String());
1056 	if (status != B_OK)
1057 		error_alert(B_TRANSLATE("Could not set preferred application"),
1058 			status);
1059 }
1060 
1061 
1062 void
1063 FileTypesWindow::_UpdatePreferredApps(BMimeType* type)
1064 {
1065 	update_preferred_app_menu(fPreferredField->Menu(), type,
1066 		kMsgPreferredAppChosen);
1067 }
1068 
1069 
1070 void
1071 FileTypesWindow::_UpdateIcon(BMimeType* type)
1072 {
1073 	if (type != NULL)
1074 		fIconView->SetTo(*type);
1075 	else
1076 		fIconView->Unset();
1077 }
1078 
1079 
1080 void
1081 FileTypesWindow::_SetType(BMimeType* type, int32 forceUpdate)
1082 {
1083 	bool enabled = type != NULL;
1084 
1085 	// update controls
1086 
1087 	if (type != NULL) {
1088 		if (fCurrentType == *type) {
1089 			if (!forceUpdate)
1090 				return;
1091 		} else
1092 			forceUpdate = B_EVERYTHING_CHANGED;
1093 
1094 		if (&fCurrentType != type)
1095 			fCurrentType.SetTo(type->Type());
1096 
1097 		fInternalNameView->SetText(type->Type());
1098 
1099 		char description[B_MIME_TYPE_LENGTH];
1100 
1101 		if ((forceUpdate & B_SHORT_DESCRIPTION_CHANGED) != 0) {
1102 			if (type->GetShortDescription(description) != B_OK)
1103 				description[0] = '\0';
1104 			fTypeNameControl->SetText(description);
1105 		}
1106 
1107 		if ((forceUpdate & B_LONG_DESCRIPTION_CHANGED) != 0) {
1108 			if (type->GetLongDescription(description) != B_OK)
1109 				description[0] = '\0';
1110 			fDescriptionControl->SetText(description);
1111 		}
1112 
1113 		if ((forceUpdate & B_SNIFFER_RULE_CHANGED) != 0) {
1114 			BString rule;
1115 			if (type->GetSnifferRule(&rule) != B_OK)
1116 				rule = "";
1117 			fRuleControl->SetText(rule.String());
1118 		}
1119 
1120 		fExtensionListView->SetType(&fCurrentType);
1121 	} else {
1122 		fCurrentType.Unset();
1123 		fInternalNameView->SetText(NULL);
1124 		fTypeNameControl->SetText(NULL);
1125 		fDescriptionControl->SetText(NULL);
1126 		fRuleControl->SetText(NULL);
1127 		fPreferredField->Menu()->ItemAt(0)->SetMarked(true);
1128 		fExtensionListView->SetType(NULL);
1129 		fAttributeListView->SetTo(NULL);
1130 	}
1131 
1132 	if ((forceUpdate & B_FILE_EXTENSIONS_CHANGED) != 0)
1133 		_UpdateExtensions(type);
1134 
1135 	if ((forceUpdate & B_PREFERRED_APP_CHANGED) != 0)
1136 		_UpdatePreferredApps(type);
1137 
1138 	if ((forceUpdate & (B_ICON_CHANGED | B_PREFERRED_APP_CHANGED)) != 0)
1139 		_UpdateIcon(type);
1140 
1141 	if ((forceUpdate & B_ATTR_INFO_CHANGED) != 0)
1142 		fAttributeListView->SetTo(type);
1143 
1144 	// enable/disable controls
1145 
1146 	fIconView->SetEnabled(enabled);
1147 
1148 	fInternalNameView->SetEnabled(enabled);
1149 	fTypeNameControl->SetEnabled(enabled);
1150 	fDescriptionControl->SetEnabled(enabled);
1151 	fPreferredField->SetEnabled(enabled);
1152 
1153 	fRemoveTypeButton->SetEnabled(enabled);
1154 
1155 	fSelectButton->SetEnabled(enabled);
1156 	fSameAsButton->SetEnabled(enabled);
1157 
1158 	fExtensionLabel->SetEnabled(enabled);
1159 	fAddExtensionButton->SetEnabled(enabled);
1160 	fRemoveExtensionButton->SetEnabled(false);
1161 	fRuleControl->SetEnabled(enabled);
1162 
1163 	fAddAttributeButton->SetEnabled(enabled);
1164 	fRemoveAttributeButton->SetEnabled(false);
1165 	fMoveUpAttributeButton->SetEnabled(false);
1166 	fMoveDownAttributeButton->SetEnabled(false);
1167 }
1168 
1169 
1170 void
1171 FileTypesWindow::_MoveUpAttributeIndex(int32 index)
1172 {
1173 	BMessage attributes;
1174 	if (fCurrentType.GetAttrInfo(&attributes) != B_OK)
1175 		return;
1176 
1177 	// Iterate over all known attribute fields, and for each field,
1178 	// iterate over all fields of the same name and build a copy
1179 	// of the attributes message with the field at the given index swapped
1180 	// with the previous field.
1181 	BMessage resortedAttributes;
1182 	for (uint32 i = 0; i <
1183 			sizeof(kAttributeNames) / sizeof(kAttributeNames[0]);
1184 			i++) {
1185 
1186 		type_code type;
1187 		int32 count;
1188 		bool isFixedSize;
1189 		if (attributes.GetInfo(kAttributeNames[i], &type, &count,
1190 				&isFixedSize) != B_OK) {
1191 			// Apparently the message does not contain this name,
1192 			// so just ignore this attribute name.
1193 			// NOTE: This shows that the attribute description is
1194 			// too fragile. It would have been better to pack each
1195 			// attribute description into a separate BMessage.
1196 			continue;
1197 		}
1198 
1199 		for (int32 j = 0; j < count; j++) {
1200 			const void* data;
1201 			ssize_t size;
1202 			int32 originalIndex;
1203 			if (j == index - 1)
1204 				originalIndex = j + 1;
1205 			else if (j == index)
1206 				originalIndex = j - 1;
1207 			else
1208 				originalIndex = j;
1209 			attributes.FindData(kAttributeNames[i], type,
1210 				originalIndex, &data, &size);
1211 			if (j == 0) {
1212 				resortedAttributes.AddData(kAttributeNames[i], type,
1213 					data, size, isFixedSize);
1214 			} else {
1215 				resortedAttributes.AddData(kAttributeNames[i], type,
1216 					data, size);
1217 			}
1218 		}
1219 	}
1220 
1221 	// Setting it directly on the type will trigger an update of the GUI as
1222 	// well. TODO: FileTypes is heavily descructive, it should use an
1223 	// Undo/Redo stack.
1224 	fCurrentType.SetAttrInfo(&resortedAttributes);
1225 }
1226 
1227 
1228