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