1209139Srpaulo/*
2209139Srpaulo * Copyright 2006-2010, Axel D��rfler, axeld@pinc-software.de.
3209139Srpaulo * Distributed under the terms of the MIT License.
4209139Srpaulo */
5252726Srpaulo
6252726Srpaulo
7209139Srpaulo#include "AttributeListView.h"
8209139Srpaulo#include "AttributeWindow.h"
9209139Srpaulo#include "DropTargetListView.h"
10209139Srpaulo#include "ExtensionWindow.h"
11209139Srpaulo#include "FileTypes.h"
12209139Srpaulo#include "FileTypesWindow.h"
13209139Srpaulo#include "IconView.h"
14209139Srpaulo#include "MimeTypeListView.h"
15209139Srpaulo#include "NewFileTypeWindow.h"
16209139Srpaulo#include "PreferredAppMenu.h"
17209139Srpaulo#include "StringView.h"
18209139Srpaulo
19209139Srpaulo#include <Alignment.h>
20209139Srpaulo#include <AppFileInfo.h>
21209139Srpaulo#include <Application.h>
22209139Srpaulo#include <Bitmap.h>
23209139Srpaulo#include <Box.h>
24209139Srpaulo#include <Button.h>
25209139Srpaulo#include <Catalog.h>
26209139Srpaulo#include <ControlLook.h>
27209139Srpaulo#include <LayoutBuilder.h>
28209139Srpaulo#include <ListView.h>
29209139Srpaulo#include <Locale.h>
30209139Srpaulo#include <MenuBar.h>
31209139Srpaulo#include <MenuField.h>
32209139Srpaulo#include <MenuItem.h>
33209139Srpaulo#include <Mime.h>
34209139Srpaulo#include <NodeInfo.h>
35252726Srpaulo#include <OutlineListView.h>
36252726Srpaulo#include <PopUpMenu.h>
37252726Srpaulo#include <ScrollView.h>
38252726Srpaulo#include <SpaceLayoutItem.h>
39209139Srpaulo#include <SplitView.h>
40252726Srpaulo#include <TextControl.h>
41209139Srpaulo
42209139Srpaulo#include <OverrideAlert.h>
43209139Srpaulo#include <be_apps/Tracker/RecentItems.h>
44209139Srpaulo
45209139Srpaulo#include <stdio.h>
46209139Srpaulo#include <stdlib.h>
47209139Srpaulo
48209139Srpaulo
49209139Srpaulo#undef B_TRANSLATION_CONTEXT
50209139Srpaulo#define B_TRANSLATION_CONTEXT "FileTypes Window"
51209139Srpaulo
52209139Srpaulo
53209139Srpauloconst uint32 kMsgTypeSelected = 'typs';
54209139Srpauloconst uint32 kMsgAddType = 'atyp';
55209139Srpauloconst uint32 kMsgRemoveType = 'rtyp';
56209139Srpaulo
57209139Srpauloconst uint32 kMsgExtensionSelected = 'exts';
58209139Srpauloconst uint32 kMsgExtensionInvoked = 'exti';
59209139Srpauloconst uint32 kMsgAddExtension = 'aext';
60209139Srpauloconst uint32 kMsgRemoveExtension = 'rext';
61209139Srpauloconst uint32 kMsgRuleEntered = 'rule';
62209139Srpaulo
63209139Srpauloconst uint32 kMsgAttributeSelected = 'atrs';
64209139Srpauloconst uint32 kMsgAttributeInvoked = 'atri';
65209139Srpauloconst uint32 kMsgAddAttribute = 'aatr';
66209139Srpauloconst uint32 kMsgRemoveAttribute = 'ratr';
67209139Srpauloconst uint32 kMsgMoveUpAttribute = 'muat';
68209139Srpauloconst uint32 kMsgMoveDownAttribute = 'mdat';
69209139Srpaulo
70209139Srpauloconst uint32 kMsgPreferredAppChosen = 'papc';
71209139Srpauloconst uint32 kMsgSelectPreferredApp = 'slpa';
72209139Srpauloconst uint32 kMsgSamePreferredAppAs = 'spaa';
73209139Srpaulo
74209139Srpauloconst uint32 kMsgPreferredAppOpened = 'paOp';
75209139Srpauloconst uint32 kMsgSamePreferredAppAsOpened = 'spaO';
76209139Srpaulo
77209139Srpauloconst uint32 kMsgTypeEntered = 'type';
78209139Srpauloconst uint32 kMsgDescriptionEntered = 'dsce';
79209139Srpaulo
80209139Srpauloconst uint32 kMsgToggleIcons = 'tgic';
81209139Srpauloconst uint32 kMsgToggleRule = 'tgrl';
82209139Srpaulo
83209139Srpaulo
84209139Srpaulostatic const char* kAttributeNames[] = {
85209139Srpaulo	"attr:public_name",
86209139Srpaulo	"attr:name",
87209139Srpaulo	"attr:type",
88209139Srpaulo	"attr:editable",
89209139Srpaulo	"attr:viewable",
90209139Srpaulo	"attr:extra",
91209139Srpaulo	"attr:alignment",
92209139Srpaulo	"attr:width",
93209139Srpaulo	"attr:display_as"
94209139Srpaulo};
95209139Srpaulo
96209139Srpaulo
97209139Srpauloclass TypeIconView : public IconView {
98209139Srpaulo	public:
99209139Srpaulo		TypeIconView(const char* name);
100209139Srpaulo		virtual ~TypeIconView();
101209139Srpaulo
102209139Srpaulo		virtual void Draw(BRect updateRect);
103209139Srpaulo		virtual void GetPreferredSize(float* _width, float* _height);
104209139Srpaulo
105209139Srpaulo	protected:
106209139Srpaulo		virtual BRect BitmapRect() const;
107209139Srpaulo};
108209139Srpaulo
109209139Srpaulo
110209139Srpauloclass ExtensionListView : public DropTargetListView {
111209139Srpaulo	public:
112209139Srpaulo		ExtensionListView(const char* name,
113209139Srpaulo			list_view_type type = B_SINGLE_SELECTION_LIST,
114209139Srpaulo			uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE);
115209139Srpaulo		virtual ~ExtensionListView();
116209139Srpaulo
117209139Srpaulo		virtual	BSize MinSize();
118209139Srpaulo
119209139Srpaulo		virtual void MessageReceived(BMessage* message);
120209139Srpaulo		virtual bool AcceptsDrag(const BMessage* message);
121209139Srpaulo
122209139Srpaulo		void SetType(BMimeType* type);
123209139Srpaulo
124209139Srpaulo	private:
125209139Srpaulo		BMimeType	fType;
126209139Srpaulo		BSize		fMinSize;
127209139Srpaulo};
128209139Srpaulo
129209139Srpaulo
130209139Srpaulo//	#pragma mark -
131209139Srpaulo
132209139Srpaulo
133209139SrpauloTypeIconView::TypeIconView(const char* name)
134209139Srpaulo	: IconView(name)
135209139Srpaulo{
136209139Srpaulo	ShowEmptyFrame(false);
137209139Srpaulo	SetIconSize(48);
138209139Srpaulo}
139209139Srpaulo
140209139Srpaulo
141209139SrpauloTypeIconView::~TypeIconView()
142209139Srpaulo{
143209139Srpaulo}
144209139Srpaulo
145209139Srpaulo
146209139Srpaulovoid
147209139SrpauloTypeIconView::Draw(BRect updateRect)
148209139Srpaulo{
149209139Srpaulo	if (!IsEnabled())
150209139Srpaulo		return;
151209139Srpaulo
152209139Srpaulo	IconView::Draw(updateRect);
153209139Srpaulo
154209139Srpaulo	const char* text = NULL;
155209139Srpaulo
156209139Srpaulo	switch (IconSource()) {
157209139Srpaulo		case kNoIcon:
158209139Srpaulo			text = B_TRANSLATE("no icon");
159209139Srpaulo			break;
160209139Srpaulo		case kApplicationIcon:
161209139Srpaulo			text = B_TRANSLATE("(from application)");
162209139Srpaulo			break;
163209139Srpaulo		case kSupertypeIcon:
164209139Srpaulo			text = B_TRANSLATE("(from super type)");
165209139Srpaulo			break;
166209139Srpaulo
167209139Srpaulo		default:
168209139Srpaulo			return;
169209139Srpaulo	}
170209139Srpaulo
171209139Srpaulo	SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
172209139Srpaulo		B_DISABLED_LABEL_TINT));
173209139Srpaulo	SetLowColor(ViewColor());
174209139Srpaulo
175209139Srpaulo	font_height fontHeight;
176209139Srpaulo	GetFontHeight(&fontHeight);
177209139Srpaulo
178209139Srpaulo	float y = fontHeight.ascent;
179209139Srpaulo	if (IconSource() == kNoIcon) {
180209139Srpaulo		// center text in the middle of the icon
181209139Srpaulo		y += (IconSize() - fontHeight.ascent - fontHeight.descent) / 2.0f;
182209139Srpaulo	} else
183209139Srpaulo		y += IconSize() + 3.0f;
184209139Srpaulo
185209139Srpaulo	DrawString(text, BPoint(ceilf((Bounds().Width() - StringWidth(text)) / 2.0f),
186209139Srpaulo		ceilf(y)));
187209139Srpaulo}
188209139Srpaulo
189209139Srpaulo
190209139Srpaulovoid
191209139SrpauloTypeIconView::GetPreferredSize(float* _width, float* _height)
192209139Srpaulo{
193209139Srpaulo	if (_width) {
194209139Srpaulo		float a = StringWidth(B_TRANSLATE("(from application)"));
195209139Srpaulo		float b = StringWidth(B_TRANSLATE("(from super type)"));
196209139Srpaulo		float width = max_c(a, b);
197209139Srpaulo		if (width < IconSize())
198209139Srpaulo			width = IconSize();
199209139Srpaulo
200209139Srpaulo		*_width = ceilf(width);
201209139Srpaulo	}
202209139Srpaulo
203209139Srpaulo	if (_height) {
204209139Srpaulo		font_height fontHeight;
205209139Srpaulo		GetFontHeight(&fontHeight);
206209139Srpaulo
207209139Srpaulo		*_height = IconSize() + 3.0f + ceilf(fontHeight.ascent
208209139Srpaulo			+ fontHeight.descent);
209209139Srpaulo	}
210209139Srpaulo}
211209139Srpaulo
212209139Srpaulo
213209139SrpauloBRect
214209139SrpauloTypeIconView::BitmapRect() const
215209139Srpaulo{
216209139Srpaulo	if (IconSource() == kNoIcon) {
217209139Srpaulo		// this also defines the drop target area
218209139Srpaulo		font_height fontHeight;
219209139Srpaulo		GetFontHeight(&fontHeight);
220209139Srpaulo
221209139Srpaulo		float width = StringWidth(B_TRANSLATE("no icon")) + 8.0f;
222209139Srpaulo		float height = ceilf(fontHeight.ascent + fontHeight.descent) + 6.0f;
223209139Srpaulo		float x = (Bounds().Width() - width) / 2.0f;
224209139Srpaulo		float y = ceilf((IconSize() - fontHeight.ascent - fontHeight.descent)
225209139Srpaulo			/ 2.0f) - 3.0f;
226
227		return BRect(x, y, x + width, y + height);
228	}
229
230	float x = (Bounds().Width() - IconSize()) / 2.0f;
231	return BRect(x, 0.0f, x + IconSize() - 1, IconSize() - 1);
232}
233
234
235//	#pragma mark -
236
237
238ExtensionListView::ExtensionListView(const char* name,
239		list_view_type type, uint32 flags)
240	:
241	DropTargetListView(name, type, flags)
242{
243}
244
245
246ExtensionListView::~ExtensionListView()
247{
248}
249
250
251BSize
252ExtensionListView::MinSize()
253{
254	if (!fMinSize.IsWidthSet()) {
255		BFont font;
256		GetFont(&font);
257		fMinSize.width = font.StringWidth(".mmmmm");
258
259		font_height height;
260		font.GetHeight(&height);
261		fMinSize.height = (height.ascent + height.descent + height.leading) * 3;
262	}
263
264	return fMinSize;
265}
266
267
268void
269ExtensionListView::MessageReceived(BMessage* message)
270{
271	if (message->WasDropped() && AcceptsDrag(message)) {
272		// create extension list
273		BList list;
274		entry_ref ref;
275		for (int32 index = 0; message->FindRef("refs", index, &ref) == B_OK;
276				index++) {
277			const char* point = strchr(ref.name, '.');
278			if (point != NULL && point[1])
279				list.AddItem(strdup(++point));
280		}
281
282		merge_extensions(fType, list);
283
284		// delete extension list
285		for (int32 index = list.CountItems(); index-- > 0;) {
286			free(list.ItemAt(index));
287		}
288	} else
289		DropTargetListView::MessageReceived(message);
290}
291
292
293bool
294ExtensionListView::AcceptsDrag(const BMessage* message)
295{
296	if (fType.Type() == NULL)
297		return false;
298
299	int32 count = 0;
300	entry_ref ref;
301
302	for (int32 index = 0; message->FindRef("refs", index, &ref) == B_OK;
303			index++) {
304		const char* point = strchr(ref.name, '.');
305		if (point != NULL && point[1])
306			count++;
307	}
308
309	return count > 0;
310}
311
312
313void
314ExtensionListView::SetType(BMimeType* type)
315{
316	if (type != NULL)
317		fType.SetTo(type->Type());
318	else
319		fType.Unset();
320}
321
322
323//	#pragma mark -
324
325
326FileTypesWindow::FileTypesWindow(const BMessage& settings)
327	:
328	BWindow(_Frame(settings), B_TRANSLATE_SYSTEM_NAME("FileTypes"),
329		B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS
330		| B_AUTO_UPDATE_SIZE_LIMITS),
331	fNewTypeWindow(NULL)
332{
333	bool showIcons;
334	bool showRule;
335	if (settings.FindBool("show_icons", &showIcons) != B_OK)
336		showIcons = true;
337	if (settings.FindBool("show_rule", &showRule) != B_OK)
338		showRule = false;
339
340	float padding = be_control_look->DefaultItemSpacing();
341	BAlignment labelAlignment = be_control_look->DefaultLabelAlignment();
342	BAlignment fullAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
343
344	// add the menu
345	BMenuBar* menuBar = new BMenuBar("");
346
347	BMenu* menu = new BMenu(B_TRANSLATE("File"));
348	BMenuItem* item = new BMenuItem(
349		B_TRANSLATE("New resource file" B_UTF8_ELLIPSIS), NULL, 'N',
350		B_COMMAND_KEY);
351	item->SetEnabled(false);
352	menu->AddItem(item);
353
354	BMenu* recentsMenu = BRecentFilesList::NewFileListMenu(
355		B_TRANSLATE("Open" B_UTF8_ELLIPSIS), NULL, NULL,
356		be_app, 10, false, NULL, kSignature);
357	item = new BMenuItem(recentsMenu, new BMessage(kMsgOpenFilePanel));
358	item->SetShortcut('O', B_COMMAND_KEY);
359	menu->AddItem(item);
360
361	menu->AddItem(new BMenuItem(
362		B_TRANSLATE("Application types" B_UTF8_ELLIPSIS),
363		new BMessage(kMsgOpenApplicationTypesWindow)));
364	menu->AddSeparatorItem();
365
366	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
367		new BMessage(B_QUIT_REQUESTED), 'Q', B_COMMAND_KEY));
368	menu->SetTargetForItems(be_app);
369	menuBar->AddItem(menu);
370
371	menu = new BMenu(B_TRANSLATE("Settings"));
372	item = new BMenuItem(B_TRANSLATE("Show icons in list"),
373		new BMessage(kMsgToggleIcons));
374	item->SetMarked(showIcons);
375	item->SetTarget(this);
376	menu->AddItem(item);
377
378	item = new BMenuItem(B_TRANSLATE("Show recognition rule"),
379		new BMessage(kMsgToggleRule));
380	item->SetMarked(showRule);
381	item->SetTarget(this);
382	menu->AddItem(item);
383	menuBar->AddItem(menu);
384	menuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
385
386	// MIME Types list
387	BButton* addTypeButton = new BButton("add",
388		B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddType));
389
390	fRemoveTypeButton = new BButton("remove", B_TRANSLATE("Remove"),
391		new BMessage(kMsgRemoveType) );
392
393	fTypeListView = new MimeTypeListView("typeview", NULL, showIcons, false);
394	fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected));
395	fTypeListView->SetExplicitMinSize(BSize(200, B_SIZE_UNSET));
396
397	BScrollView* typeListScrollView = new BScrollView("scrollview",
398		fTypeListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
399
400	// "Icon" group
401
402	fIconView = new TypeIconView("icon");
403	fIconBox = new BBox("Icon BBox");
404	fIconBox->SetLabel(B_TRANSLATE("Icon"));
405	BLayoutBuilder::Group<>(fIconBox, B_VERTICAL, padding)
406		.SetInsets(padding)
407		.AddGlue(1)
408		.Add(fIconView, 3)
409		.AddGlue(1);
410
411	// "File Recognition" group
412
413	fRecognitionBox = new BBox("Recognition Box");
414	fRecognitionBox->SetLabel(B_TRANSLATE("File recognition"));
415	fRecognitionBox->SetExplicitAlignment(fullAlignment);
416
417	fExtensionLabel = new StringView(B_TRANSLATE("Extensions:"), NULL);
418	fExtensionLabel->LabelView()->SetExplicitAlignment(labelAlignment);
419
420	fAddExtensionButton = new BButton("add ext",
421		B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddExtension));
422	fAddExtensionButton->SetExplicitMaxSize(
423		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
424
425	fRemoveExtensionButton = new BButton("remove ext", B_TRANSLATE("Remove"),
426		new BMessage(kMsgRemoveExtension));
427
428	fExtensionListView = new ExtensionListView("listview ext",
429		B_SINGLE_SELECTION_LIST);
430	fExtensionListView->SetSelectionMessage(
431		new BMessage(kMsgExtensionSelected));
432	fExtensionListView->SetInvocationMessage(
433		new BMessage(kMsgExtensionInvoked));
434
435	BScrollView* scrollView = new BScrollView("scrollview ext",
436		fExtensionListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
437
438	fRuleControl = new BTextControl("rule", B_TRANSLATE("Rule:"), "",
439		new BMessage(kMsgRuleEntered));
440	fRuleControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
441	fRuleControl->Hide();
442
443	BLayoutBuilder::Grid<>(fRecognitionBox, padding, padding / 2)
444		.SetInsets(padding, padding * 2, padding, padding)
445		.Add(fExtensionLabel->LabelView(), 0, 0)
446		.Add(scrollView, 0, 1, 2, 2)
447		.Add(fAddExtensionButton, 2, 1)
448		.Add(fRemoveExtensionButton, 2, 2)
449		.Add(fRuleControl, 0, 3, 3, 1);
450
451	// "Description" group
452
453	fDescriptionBox = new BBox("description BBox");
454	fDescriptionBox->SetLabel(B_TRANSLATE("Description"));
455	fDescriptionBox->SetExplicitAlignment(fullAlignment);
456
457	fInternalNameView = new StringView(B_TRANSLATE("Internal name:"), NULL);
458	fInternalNameView->SetEnabled(false);
459	fTypeNameControl = new BTextControl("type", B_TRANSLATE("Type name:"), "",
460		new BMessage(kMsgTypeEntered));
461	fDescriptionControl = new BTextControl("description",
462		B_TRANSLATE("Description:"), "", new BMessage(kMsgDescriptionEntered));
463
464	BLayoutBuilder::Grid<>(fDescriptionBox, padding / 2, padding / 2)
465		.SetInsets(padding, padding * 2, padding, padding)
466		.Add(fInternalNameView->LabelView(), 0, 0)
467		.Add(fInternalNameView->TextView(), 1, 0)
468		.Add(fTypeNameControl->CreateLabelLayoutItem(), 0, 1)
469		.Add(fTypeNameControl->CreateTextViewLayoutItem(), 1, 1, 2)
470		.Add(fDescriptionControl->CreateLabelLayoutItem(), 0, 2)
471		.Add(fDescriptionControl->CreateTextViewLayoutItem(), 1, 2, 2);
472
473	// "Preferred Application" group
474
475	fPreferredBox = new BBox("preferred BBox");
476	fPreferredBox->SetLabel(B_TRANSLATE("Preferred application"));
477
478	menu = new BPopUpMenu("preferred");
479	menu->AddItem(item = new BMenuItem(B_TRANSLATE("None"),
480		new BMessage(kMsgPreferredAppChosen)));
481	item->SetMarked(true);
482	fPreferredField = new BMenuField("preferred", (char*)NULL, menu);
483
484	fSelectButton = new BButton("select",
485		B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
486		new BMessage(kMsgSelectPreferredApp));
487
488	fSameAsButton = new BButton("same as",
489		B_TRANSLATE("Same as" B_UTF8_ELLIPSIS),
490		new BMessage(kMsgSamePreferredAppAs));
491
492	BLayoutBuilder::Group<>(fPreferredBox, B_HORIZONTAL, padding)
493		.SetInsets(padding, padding * 2, padding, padding)
494		.Add(fPreferredField)
495		.Add(fSelectButton)
496		.Add(fSameAsButton);
497
498	// "Extra Attributes" group
499
500	fAttributeBox = new BBox("Attribute Box");
501	fAttributeBox->SetLabel(B_TRANSLATE("Extra attributes"));
502
503	fAddAttributeButton = new BButton("add attr",
504		B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddAttribute));
505	fAddAttributeButton->SetExplicitMaxSize(
506		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
507
508	fRemoveAttributeButton = new BButton("remove attr", B_TRANSLATE("Remove"),
509		new BMessage(kMsgRemoveAttribute));
510	fRemoveAttributeButton->SetExplicitMaxSize(
511		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
512
513	fMoveUpAttributeButton = new BButton("move up attr", B_TRANSLATE("Move up"),
514		new BMessage(kMsgMoveUpAttribute));
515	fMoveUpAttributeButton->SetExplicitMaxSize(
516		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
517	fMoveDownAttributeButton = new BButton("move down attr",
518		B_TRANSLATE("Move down"), new BMessage(kMsgMoveDownAttribute));
519	fMoveDownAttributeButton->SetExplicitMaxSize(
520		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
521
522	fAttributeListView = new AttributeListView("listview attr");
523	fAttributeListView->SetSelectionMessage(
524		new BMessage(kMsgAttributeSelected));
525	fAttributeListView->SetInvocationMessage(
526		new BMessage(kMsgAttributeInvoked));
527
528	BScrollView* attributesScroller = new BScrollView("scrollview attr",
529		fAttributeListView, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
530
531	BLayoutBuilder::Group<>(fAttributeBox, B_HORIZONTAL, padding)
532		.SetInsets(padding, padding * 2, padding, padding)
533		.Add(attributesScroller, 1.0f)
534		.AddGroup(B_VERTICAL, padding / 2, 0.0f)
535			.SetInsets(0)
536			.Add(fAddAttributeButton)
537			.Add(fRemoveAttributeButton)
538			.AddStrut(padding)
539			.Add(fMoveUpAttributeButton)
540			.Add(fMoveDownAttributeButton)
541			.AddGlue();
542
543	fMainSplitView = new BSplitView(B_HORIZONTAL, floorf(padding / 2));
544
545	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
546		.SetInsets(0)
547		.Add(menuBar)
548		.AddGroup(B_HORIZONTAL, 0)
549			.SetInsets(padding, padding, padding, padding)
550			.AddSplit(fMainSplitView)
551				.AddGroup(B_VERTICAL, padding)
552					.Add(typeListScrollView)
553					.AddGroup(B_HORIZONTAL, padding)
554						.Add(addTypeButton)
555						.Add(fRemoveTypeButton)
556						.AddGlue()
557						.End()
558					.End()
559				// Right side
560				.AddGroup(B_VERTICAL, padding)
561					.AddGroup(B_HORIZONTAL, padding)
562						.Add(fIconBox, 1)
563						.Add(fRecognitionBox, 3)
564						.End()
565					.Add(fDescriptionBox)
566					.Add(fPreferredBox)
567					.Add(fAttributeBox, 5);
568
569	_SetType(NULL);
570	_ShowSnifferRule(showRule);
571
572	float leftWeight;
573	float rightWeight;
574	if (settings.FindFloat("left_split_weight", &leftWeight) != B_OK
575		|| settings.FindFloat("right_split_weight", &rightWeight) != B_OK) {
576		leftWeight = 0.2;
577		rightWeight = 1.0 - leftWeight;
578	}
579	fMainSplitView->SetItemWeight(0, leftWeight, false);
580	fMainSplitView->SetItemWeight(1, rightWeight, true);
581
582	BMimeType::StartWatching(this);
583}
584
585
586FileTypesWindow::~FileTypesWindow()
587{
588	BMimeType::StopWatching(this);
589}
590
591
592void
593FileTypesWindow::MessageReceived(BMessage* message)
594{
595	switch (message->what) {
596		case B_SIMPLE_DATA:
597		{
598			type_code type;
599			if (message->GetInfo("refs", &type) == B_OK
600				&& type == B_REF_TYPE) {
601				be_app->PostMessage(message);
602			}
603			break;
604		}
605
606		case kMsgToggleIcons:
607		{
608			BMenuItem* item;
609			if (message->FindPointer("source", (void **)&item) != B_OK)
610				break;
611
612			item->SetMarked(!fTypeListView->IsShowingIcons());
613			fTypeListView->ShowIcons(item->IsMarked());
614
615			// update settings
616			BMessage update(kMsgSettingsChanged);
617			update.AddBool("show_icons", item->IsMarked());
618			be_app_messenger.SendMessage(&update);
619			break;
620		}
621
622		case kMsgToggleRule:
623		{
624			BMenuItem* item;
625			if (message->FindPointer("source", (void **)&item) != B_OK)
626				break;
627
628			item->SetMarked(fRuleControl->IsHidden());
629			_ShowSnifferRule(item->IsMarked());
630
631			// update settings
632			BMessage update(kMsgSettingsChanged);
633			update.AddBool("show_rule", item->IsMarked());
634			be_app_messenger.SendMessage(&update);
635			break;
636		}
637
638		case kMsgTypeSelected:
639		{
640			int32 index;
641			if (message->FindInt32("index", &index) == B_OK) {
642				MimeTypeItem* item
643					= (MimeTypeItem*)fTypeListView->ItemAt(index);
644				if (item != NULL) {
645					BMimeType type(item->Type());
646					_SetType(&type);
647				} else
648					_SetType(NULL);
649			}
650			break;
651		}
652
653		case kMsgAddType:
654			if (fNewTypeWindow == NULL) {
655				fNewTypeWindow
656					= new NewFileTypeWindow(this, fCurrentType.Type());
657				fNewTypeWindow->Show();
658			} else
659				fNewTypeWindow->Activate();
660			break;
661
662		case kMsgNewTypeWindowClosed:
663			fNewTypeWindow = NULL;
664			break;
665
666		case kMsgRemoveType:
667		{
668			if (fCurrentType.Type() == NULL)
669				break;
670
671			BAlert* alert;
672			if (fCurrentType.IsSupertypeOnly()) {
673				alert = new BPrivate::OverrideAlert(
674					B_TRANSLATE("FileTypes request"),
675					B_TRANSLATE("Removing a super type cannot be reverted.\n"
676					"All file types that belong to this super type "
677					"will be lost!\n\n"
678					"Are you sure you want to do this? To remove the whole "
679					"group, hold down the Shift key and press \"Remove\"."),
680					B_TRANSLATE("Remove"), B_SHIFT_KEY, B_TRANSLATE("Cancel"),
681					0, NULL, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
682				alert->SetShortcut(1, B_ESCAPE);
683			} else {
684				alert = new BAlert(B_TRANSLATE("FileTypes request"),
685					B_TRANSLATE("Removing a file type cannot be reverted.\n"
686					"Are you sure you want to remove it?"),
687					B_TRANSLATE("Remove"), B_TRANSLATE("Cancel"),
688					NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
689				alert->SetShortcut(1, B_ESCAPE);
690			}
691			if (alert->Go())
692				break;
693
694			status_t status = fCurrentType.Delete();
695			if (status != B_OK) {
696				fprintf(stderr, B_TRANSLATE(
697					"Could not remove file type: %s\n"), strerror(status));
698			}
699			break;
700		}
701
702		case kMsgSelectNewType:
703		{
704			const char* type;
705			if (message->FindString("type", &type) == B_OK)
706				fTypeListView->SelectNewType(type);
707			break;
708		}
709
710		// File Recognition group
711
712		case kMsgExtensionSelected:
713		{
714			int32 index;
715			if (message->FindInt32("index", &index) == B_OK) {
716				BStringItem* item
717					= (BStringItem*)fExtensionListView->ItemAt(index);
718				fRemoveExtensionButton->SetEnabled(item != NULL);
719			}
720			break;
721		}
722
723		case kMsgExtensionInvoked:
724		{
725			if (fCurrentType.Type() == NULL)
726				break;
727
728			int32 index;
729			if (message->FindInt32("index", &index) == B_OK) {
730				BStringItem* item
731					= (BStringItem*)fExtensionListView->ItemAt(index);
732				if (item == NULL)
733					break;
734
735				BWindow* window
736					= new ExtensionWindow(this, fCurrentType, item->Text());
737				window->Show();
738			}
739			break;
740		}
741
742		case kMsgAddExtension:
743		{
744			if (fCurrentType.Type() == NULL)
745				break;
746
747			BWindow* window = new ExtensionWindow(this, fCurrentType, NULL);
748			window->Show();
749			break;
750		}
751
752		case kMsgRemoveExtension:
753		{
754			int32 index = fExtensionListView->CurrentSelection();
755			if (index < 0 || fCurrentType.Type() == NULL)
756				break;
757
758			BMessage extensions;
759			if (fCurrentType.GetFileExtensions(&extensions) == B_OK) {
760				extensions.RemoveData("extensions", index);
761				fCurrentType.SetFileExtensions(&extensions);
762			}
763			break;
764		}
765
766		case kMsgRuleEntered:
767		{
768			// check rule
769			BString parseError;
770			if (BMimeType::CheckSnifferRule(fRuleControl->Text(),
771					&parseError) != B_OK) {
772				parseError.Prepend(
773					B_TRANSLATE("Recognition rule is not valid:\n\n"));
774				error_alert(parseError.String());
775			} else
776				fCurrentType.SetSnifferRule(fRuleControl->Text());
777			break;
778		}
779
780		// Description group
781
782		case kMsgTypeEntered:
783		{
784			fCurrentType.SetShortDescription(fTypeNameControl->Text());
785			break;
786		}
787
788		case kMsgDescriptionEntered:
789		{
790			fCurrentType.SetLongDescription(fDescriptionControl->Text());
791			break;
792		}
793
794		// Preferred Application group
795
796		case kMsgPreferredAppChosen:
797		{
798			const char* signature;
799			if (message->FindString("signature", &signature) != B_OK)
800				signature = NULL;
801
802			fCurrentType.SetPreferredApp(signature);
803			break;
804		}
805
806		case kMsgSelectPreferredApp:
807		{
808			BMessage panel(kMsgOpenFilePanel);
809			panel.AddString("title",
810				B_TRANSLATE("Select preferred application"));
811			panel.AddInt32("message", kMsgPreferredAppOpened);
812			panel.AddMessenger("target", this);
813
814			be_app_messenger.SendMessage(&panel);
815			break;
816		}
817		case kMsgPreferredAppOpened:
818			_AdoptPreferredApplication(message, false);
819			break;
820
821		case kMsgSamePreferredAppAs:
822		{
823			BMessage panel(kMsgOpenFilePanel);
824			panel.AddString("title",
825				B_TRANSLATE("Select same preferred application as"));
826			panel.AddInt32("message", kMsgSamePreferredAppAsOpened);
827			panel.AddMessenger("target", this);
828
829			be_app_messenger.SendMessage(&panel);
830			break;
831		}
832		case kMsgSamePreferredAppAsOpened:
833			_AdoptPreferredApplication(message, true);
834			break;
835
836		// Extra Attributes group
837
838		case kMsgAttributeSelected:
839		{
840			int32 index;
841			if (message->FindInt32("index", &index) == B_OK) {
842				AttributeItem* item
843					= (AttributeItem*)fAttributeListView->ItemAt(index);
844				fRemoveAttributeButton->SetEnabled(item != NULL);
845				fMoveUpAttributeButton->SetEnabled(index > 0);
846				fMoveDownAttributeButton->SetEnabled(index >= 0
847					&& index < fAttributeListView->CountItems() - 1);
848			}
849			break;
850		}
851
852		case kMsgAttributeInvoked:
853		{
854			if (fCurrentType.Type() == NULL)
855				break;
856
857			int32 index;
858			if (message->FindInt32("index", &index) == B_OK) {
859				AttributeItem* item
860					= (AttributeItem*)fAttributeListView->ItemAt(index);
861				if (item == NULL)
862					break;
863
864				BWindow* window = new AttributeWindow(this, fCurrentType,
865					item);
866				window->Show();
867			}
868			break;
869		}
870
871		case kMsgAddAttribute:
872		{
873			if (fCurrentType.Type() == NULL)
874				break;
875
876			BWindow* window = new AttributeWindow(this, fCurrentType, NULL);
877			window->Show();
878			break;
879		}
880
881		case kMsgRemoveAttribute:
882		{
883			int32 index = fAttributeListView->CurrentSelection();
884			if (index < 0 || fCurrentType.Type() == NULL)
885				break;
886
887			BMessage attributes;
888			if (fCurrentType.GetAttrInfo(&attributes) == B_OK) {
889				for (uint32 i = 0; i <
890						sizeof(kAttributeNames) / sizeof(kAttributeNames[0]);
891						i++) {
892					attributes.RemoveData(kAttributeNames[i], index);
893				}
894
895				fCurrentType.SetAttrInfo(&attributes);
896			}
897			break;
898		}
899
900		case kMsgMoveUpAttribute:
901		{
902			int32 index = fAttributeListView->CurrentSelection();
903			if (index < 1 || fCurrentType.Type() == NULL)
904				break;
905
906			_MoveUpAttributeIndex(index);
907			break;
908		}
909
910		case kMsgMoveDownAttribute:
911		{
912			int32 index = fAttributeListView->CurrentSelection();
913			if (index < 0 || index == fAttributeListView->CountItems() - 1
914				|| fCurrentType.Type() == NULL) {
915				break;
916			}
917
918			_MoveUpAttributeIndex(index + 1);
919			break;
920		}
921
922		case B_META_MIME_CHANGED:
923		{
924			const char* type;
925			int32 which;
926			if (message->FindString("be:type", &type) != B_OK
927				|| message->FindInt32("be:which", &which) != B_OK)
928				break;
929
930			if (fCurrentType.Type() == NULL)
931				break;
932
933			if (!strcasecmp(fCurrentType.Type(), type)) {
934				if (which != B_MIME_TYPE_DELETED)
935					_SetType(&fCurrentType, which);
936				else
937					_SetType(NULL);
938			} else {
939				// this change could still affect our current type
940
941				if (which == B_MIME_TYPE_DELETED
942					|| which == B_SUPPORTED_TYPES_CHANGED
943					|| which == B_PREFERRED_APP_CHANGED) {
944					_UpdatePreferredApps(&fCurrentType);
945				}
946			}
947			break;
948		}
949
950		default:
951			BWindow::MessageReceived(message);
952	}
953}
954
955
956void
957FileTypesWindow::SelectType(const char* type)
958{
959	fTypeListView->SelectType(type);
960}
961
962
963bool
964FileTypesWindow::QuitRequested()
965{
966	BMessage update(kMsgSettingsChanged);
967	update.AddRect("file_types_frame", Frame());
968	update.AddFloat("left_split_weight", fMainSplitView->ItemWeight((int32)0));
969	update.AddFloat("right_split_weight", fMainSplitView->ItemWeight(1));
970	be_app_messenger.SendMessage(&update);
971
972	be_app->PostMessage(kMsgTypesWindowClosed);
973	return true;
974}
975
976
977void
978FileTypesWindow::PlaceSubWindow(BWindow* window)
979{
980	window->MoveTo(Frame().left + (Frame().Width() - window->Frame().Width())
981		/ 2.0f, Frame().top + (Frame().Height() - window->Frame().Height())
982		/ 2.0f);
983}
984
985
986// #pragma mark - private
987
988
989BRect
990FileTypesWindow::_Frame(const BMessage& settings) const
991{
992	BRect rect;
993	if (settings.FindRect("file_types_frame", &rect) == B_OK)
994		return rect;
995
996	return BRect(80.0f, 80.0f, 0.0f, 0.0f);
997}
998
999
1000void
1001FileTypesWindow::_ShowSnifferRule(bool show)
1002{
1003	if (fRuleControl->IsHidden() == !show)
1004		return;
1005
1006	if (!show)
1007		fRuleControl->Hide();
1008	else
1009		fRuleControl->Show();
1010}
1011
1012
1013void
1014FileTypesWindow::_UpdateExtensions(BMimeType* type)
1015{
1016	// clear list
1017
1018	for (int32 i = fExtensionListView->CountItems(); i-- > 0;) {
1019		delete fExtensionListView->ItemAt(i);
1020	}
1021	fExtensionListView->MakeEmpty();
1022
1023	// fill it again
1024
1025	if (type == NULL)
1026		return;
1027
1028	BMessage extensions;
1029	if (type->GetFileExtensions(&extensions) != B_OK)
1030		return;
1031
1032	const char* extension;
1033	int32 i = 0;
1034	while (extensions.FindString("extensions", i++, &extension) == B_OK) {
1035		char dotExtension[B_FILE_NAME_LENGTH];
1036		snprintf(dotExtension, B_FILE_NAME_LENGTH, ".%s", extension);
1037
1038		fExtensionListView->AddItem(new BStringItem(dotExtension));
1039	}
1040}
1041
1042
1043void
1044FileTypesWindow::_AdoptPreferredApplication(BMessage* message, bool sameAs)
1045{
1046	if (fCurrentType.Type() == NULL)
1047		return;
1048
1049	BString preferred;
1050	if (retrieve_preferred_app(message, sameAs, fCurrentType.Type(), preferred)
1051		!= B_OK) {
1052		return;
1053	}
1054
1055	status_t status = fCurrentType.SetPreferredApp(preferred.String());
1056	if (status != B_OK)
1057		error_alert(B_TRANSLATE("Could not set preferred application"),
1058			status);
1059}
1060
1061
1062void
1063FileTypesWindow::_UpdatePreferredApps(BMimeType* type)
1064{
1065	update_preferred_app_menu(fPreferredField->Menu(), type,
1066		kMsgPreferredAppChosen);
1067}
1068
1069
1070void
1071FileTypesWindow::_UpdateIcon(BMimeType* type)
1072{
1073	if (type != NULL)
1074		fIconView->SetTo(*type);
1075	else
1076		fIconView->Unset();
1077}
1078
1079
1080void
1081FileTypesWindow::_SetType(BMimeType* type, int32 forceUpdate)
1082{
1083	bool enabled = type != NULL;
1084
1085	// update controls
1086
1087	if (type != NULL) {
1088		if (fCurrentType == *type) {
1089			if (!forceUpdate)
1090				return;
1091		} else
1092			forceUpdate = B_EVERYTHING_CHANGED;
1093
1094		if (&fCurrentType != type)
1095			fCurrentType.SetTo(type->Type());
1096
1097		fInternalNameView->SetText(type->Type());
1098
1099		char description[B_MIME_TYPE_LENGTH];
1100
1101		if ((forceUpdate & B_SHORT_DESCRIPTION_CHANGED) != 0) {
1102			if (type->GetShortDescription(description) != B_OK)
1103				description[0] = '\0';
1104			fTypeNameControl->SetText(description);
1105		}
1106
1107		if ((forceUpdate & B_LONG_DESCRIPTION_CHANGED) != 0) {
1108			if (type->GetLongDescription(description) != B_OK)
1109				description[0] = '\0';
1110			fDescriptionControl->SetText(description);
1111		}
1112
1113		if ((forceUpdate & B_SNIFFER_RULE_CHANGED) != 0) {
1114			BString rule;
1115			if (type->GetSnifferRule(&rule) != B_OK)
1116				rule = "";
1117			fRuleControl->SetText(rule.String());
1118		}
1119
1120		fExtensionListView->SetType(&fCurrentType);
1121	} else {
1122		fCurrentType.Unset();
1123		fInternalNameView->SetText(NULL);
1124		fTypeNameControl->SetText(NULL);
1125		fDescriptionControl->SetText(NULL);
1126		fRuleControl->SetText(NULL);
1127		fPreferredField->Menu()->ItemAt(0)->SetMarked(true);
1128		fExtensionListView->SetType(NULL);
1129		fAttributeListView->SetTo(NULL);
1130	}
1131
1132	if ((forceUpdate & B_FILE_EXTENSIONS_CHANGED) != 0)
1133		_UpdateExtensions(type);
1134
1135	if ((forceUpdate & B_PREFERRED_APP_CHANGED) != 0)
1136		_UpdatePreferredApps(type);
1137
1138	if ((forceUpdate & (B_ICON_CHANGED | B_PREFERRED_APP_CHANGED)) != 0)
1139		_UpdateIcon(type);
1140
1141	if ((forceUpdate & B_ATTR_INFO_CHANGED) != 0)
1142		fAttributeListView->SetTo(type);
1143
1144	// enable/disable controls
1145
1146	fIconView->SetEnabled(enabled);
1147
1148	fInternalNameView->SetEnabled(enabled);
1149	fTypeNameControl->SetEnabled(enabled);
1150	fDescriptionControl->SetEnabled(enabled);
1151	fPreferredField->SetEnabled(enabled);
1152
1153	fRemoveTypeButton->SetEnabled(enabled);
1154
1155	fSelectButton->SetEnabled(enabled);
1156	fSameAsButton->SetEnabled(enabled);
1157
1158	fExtensionLabel->SetEnabled(enabled);
1159	fAddExtensionButton->SetEnabled(enabled);
1160	fRemoveExtensionButton->SetEnabled(false);
1161	fRuleControl->SetEnabled(enabled);
1162
1163	fAddAttributeButton->SetEnabled(enabled);
1164	fRemoveAttributeButton->SetEnabled(false);
1165	fMoveUpAttributeButton->SetEnabled(false);
1166	fMoveDownAttributeButton->SetEnabled(false);
1167}
1168
1169
1170void
1171FileTypesWindow::_MoveUpAttributeIndex(int32 index)
1172{
1173	BMessage attributes;
1174	if (fCurrentType.GetAttrInfo(&attributes) != B_OK)
1175		return;
1176
1177	// Iterate over all known attribute fields, and for each field,
1178	// iterate over all fields of the same name and build a copy
1179	// of the attributes message with the field at the given index swapped
1180	// with the previous field.
1181	BMessage resortedAttributes;
1182	for (uint32 i = 0; i <
1183			sizeof(kAttributeNames) / sizeof(kAttributeNames[0]);
1184			i++) {
1185
1186		type_code type;
1187		int32 count;
1188		bool isFixedSize;
1189		if (attributes.GetInfo(kAttributeNames[i], &type, &count,
1190				&isFixedSize) != B_OK) {
1191			// Apparently the message does not contain this name,
1192			// so just ignore this attribute name.
1193			// NOTE: This shows that the attribute description is
1194			// too fragile. It would have been better to pack each
1195			// attribute description into a separate BMessage.
1196			continue;
1197		}
1198
1199		for (int32 j = 0; j < count; j++) {
1200			const void* data;
1201			ssize_t size;
1202			int32 originalIndex;
1203			if (j == index - 1)
1204				originalIndex = j + 1;
1205			else if (j == index)
1206				originalIndex = j - 1;
1207			else
1208				originalIndex = j;
1209			attributes.FindData(kAttributeNames[i], type,
1210				originalIndex, &data, &size);
1211			if (j == 0) {
1212				resortedAttributes.AddData(kAttributeNames[i], type,
1213					data, size, isFixedSize);
1214			} else {
1215				resortedAttributes.AddData(kAttributeNames[i], type,
1216					data, size);
1217			}
1218		}
1219	}
1220
1221	// Setting it directly on the type will trigger an update of the GUI as
1222	// well. TODO: FileTypes is heavily descructive, it should use an
1223	// Undo/Redo stack.
1224	fCurrentType.SetAttrInfo(&resortedAttributes);
1225}
1226
1227
1228