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