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