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