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