xref: /haiku/src/preferences/filetypes/FileTypesWindow.cpp (revision 35ead8815b679605a9b4db8130613ea100f4b14c)
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(
499 		"Same as" B_UTF8_ELLIPSIS) - 24.0f;
500 	innerRect.bottom = innerRect.top + button->Bounds().Height();
501 	fSameAsButton = new BButton(innerRect, "same as",
502 		"Same as" B_UTF8_ELLIPSIS,
503 		new BMessage(kMsgSamePreferredAppAs), B_FOLLOW_RIGHT);
504 	fPreferredBox->AddChild(fSameAsButton);
505 
506 	innerRect.OffsetBy(-innerRect.Width() - 6.0f, 0.0f);
507 	fSelectButton = new BButton(innerRect, "select", "Select" B_UTF8_ELLIPSIS,
508 		new BMessage(kMsgSelectPreferredApp), B_FOLLOW_RIGHT);
509 	fPreferredBox->AddChild(fSelectButton);
510 
511 	menu = new BPopUpMenu("preferred");
512 	menu->AddItem(item = new BMenuItem("None",
513 		new BMessage(kMsgPreferredAppChosen)));
514 	item->SetMarked(true);
515 
516 	innerRect.right = innerRect.left - 6.0f;
517 	innerRect.left = 8.0f;
518 
519 	fPreferredField = new BMenuField(innerRect, "preferred", NULL, menu, true,
520 		B_FOLLOW_LEFT_RIGHT);
521 	float width;
522 	fPreferredField->GetPreferredSize(&width, &height);
523 	fPreferredField->ResizeTo(innerRect.Width(), height);
524 	fPreferredField->MoveBy(0.0f, (innerRect.Height() - height) / 2.0f);
525 
526 	fPreferredBox->AddChild(fPreferredField);
527 
528 	// "Extra Attributes" group
529 
530 	rect.top = rect.bottom + 8.0f;
531 	rect.bottom = topView->Bounds().Height() - 8.0f;
532 	fAttributeBox = new BBox(rect, NULL, B_FOLLOW_LEFT_RIGHT
533 		| B_FOLLOW_TOP_BOTTOM);
534 	fAttributeBox->SetLabel("Extra attributes");
535 	topView->AddChild(fAttributeBox);
536 
537 	innerRect = fAttributeBox->Bounds().InsetByCopy(8.0f, 6.0f);
538 	innerRect.top += ceilf(boldHeight.ascent);
539 	innerRect.left = innerRect.right - button->StringWidth("Remove") - 16.0f;
540 	innerRect.bottom = innerRect.top + button->Bounds().Height();
541 	fAddAttributeButton = new BButton(innerRect, "add attr",
542 		"Add" B_UTF8_ELLIPSIS, new BMessage(kMsgAddAttribute), B_FOLLOW_RIGHT);
543 	fAttributeBox->AddChild(fAddAttributeButton);
544 
545 	innerRect.OffsetBy(0, innerRect.Height() + 4.0f);
546 	fRemoveAttributeButton = new BButton(innerRect, "remove attr", "Remove",
547 		new BMessage(kMsgRemoveAttribute), B_FOLLOW_RIGHT);
548 	fAttributeBox->AddChild(fRemoveAttributeButton);
549 /*
550 	innerRect.OffsetBy(0, innerRect.Height() + 4.0f);
551 	button = new BButton(innerRect, "push attr", "Push Up",
552 		new BMessage(kMsgRemoveAttribute), B_FOLLOW_RIGHT);
553 	fAttributeBox->AddChild(button);
554 */
555 	innerRect.right = innerRect.left - 10.0f - B_V_SCROLL_BAR_WIDTH;
556 	innerRect.left = 10.0f;
557 	innerRect.top = 8.0f + ceilf(boldHeight.ascent);
558 	innerRect.bottom = fAttributeBox->Bounds().bottom - 10.0f;
559 		// take scrollview border into account
560 	fAttributeListView = new AttributeListView(innerRect, "listview attr",
561 		B_FOLLOW_ALL);
562 	fAttributeListView->SetSelectionMessage(new BMessage(kMsgAttributeSelected));
563 	fAttributeListView->SetInvocationMessage(new BMessage(kMsgAttributeInvoked));
564 
565 	scrollView = new BScrollView("scrollview attr", fAttributeListView,
566 		B_FOLLOW_ALL, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
567 	fAttributeBox->AddChild(scrollView);
568 
569 	SetSizeLimits(rightRect.left + 72.0f + font.StringWidth("jpg")
570 		+ font.StringWidth(fRecognitionBox->Label()), 32767.0f,
571 		rect.top + 2.0f * button->Bounds().Height() + boldHeight.ascent
572 		+ 32.0f + menuBar->Bounds().Height(), 32767.0f);
573 
574 	_SetType(NULL);
575 	_ShowSnifferRule(showRule);
576 
577 	BMimeType::StartWatching(this);
578 }
579 
580 
581 FileTypesWindow::~FileTypesWindow()
582 {
583 	BMimeType::StopWatching(this);
584 }
585 
586 
587 BRect
588 FileTypesWindow::_Frame(const BMessage& settings) const
589 {
590 	BRect rect;
591 	if (settings.FindRect("file_types_frame", &rect) == B_OK)
592 		return rect;
593 
594 	return BRect(80.0f, 80.0f, 600.0f, 480.0f);
595 }
596 
597 
598 void
599 FileTypesWindow::_ShowSnifferRule(bool show)
600 {
601 	if (fRuleControl->IsHidden() == !show)
602 		return;
603 
604 	float minWidth, maxWidth, minHeight, maxHeight;
605 	GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
606 
607 	float diff = fRuleControl->Bounds().Height() + 8.0f;
608 
609 	if (!show) {
610 		fRuleControl->Hide();
611 		diff = -diff;
612 	}
613 
614 	// adjust other controls to make space or take it again
615 
616 	fIconBox->ResizeBy(0.0f, diff);
617 	fRecognitionBox->ResizeBy(0.0f, diff);
618 	fDescriptionBox->MoveBy(0.0f, diff);
619 	fPreferredBox->MoveBy(0.0f, diff);
620 	fAttributeBox->MoveBy(0.0f, diff);
621 	fAttributeBox->ResizeBy(0.0f, -diff);
622 
623 	if (show)
624 		fRuleControl->Show();
625 
626 	SetSizeLimits(minWidth, maxWidth, minHeight + diff, maxHeight);
627 }
628 
629 
630 void
631 FileTypesWindow::_UpdateExtensions(BMimeType* type)
632 {
633 	// clear list
634 
635 	for (int32 i = fExtensionListView->CountItems(); i-- > 0;) {
636 		delete fExtensionListView->ItemAt(i);
637 	}
638 	fExtensionListView->MakeEmpty();
639 
640 	// fill it again
641 
642 	if (type == NULL)
643 		return;
644 
645 	BMessage extensions;
646 	if (type->GetFileExtensions(&extensions) != B_OK)
647 		return;
648 
649 	const char* extension;
650 	int32 i = 0;
651 	while (extensions.FindString("extensions", i++, &extension) == B_OK) {
652 		char dotExtension[B_FILE_NAME_LENGTH];
653 		snprintf(dotExtension, B_FILE_NAME_LENGTH, ".%s", extension);
654 
655 		fExtensionListView->AddItem(new BStringItem(dotExtension));
656 	}
657 }
658 
659 
660 void
661 FileTypesWindow::_AdoptPreferredApplication(BMessage* message, bool sameAs)
662 {
663 	if (fCurrentType.Type() == NULL)
664 		return;
665 
666 	BString preferred;
667 	if (retrieve_preferred_app(message, sameAs, fCurrentType.Type(), preferred) != B_OK)
668 		return;
669 
670 	status_t status = fCurrentType.SetPreferredApp(preferred.String());
671 	if (status != B_OK)
672 		error_alert("Could not set preferred application", status);
673 }
674 
675 
676 void
677 FileTypesWindow::_UpdatePreferredApps(BMimeType* type)
678 {
679 	update_preferred_app_menu(fPreferredField->Menu(), type, kMsgPreferredAppChosen);
680 }
681 
682 
683 void
684 FileTypesWindow::_UpdateIcon(BMimeType* type)
685 {
686 	if (type != NULL)
687 		fIconView->SetTo(*type);
688 	else
689 		fIconView->Unset();
690 }
691 
692 
693 void
694 FileTypesWindow::_SetType(BMimeType* type, int32 forceUpdate)
695 {
696 	bool enabled = type != NULL;
697 
698 	// update controls
699 
700 	if (type != NULL) {
701 		if (fCurrentType == *type) {
702 			if (!forceUpdate)
703 				return;
704 		} else
705 			forceUpdate = B_EVERYTHING_CHANGED;
706 
707 		if (&fCurrentType != type)
708 			fCurrentType.SetTo(type->Type());
709 
710 		fInternalNameView->SetText(type->Type());
711 
712 		char description[B_MIME_TYPE_LENGTH];
713 
714 		if ((forceUpdate & B_SHORT_DESCRIPTION_CHANGED) != 0) {
715 			if (type->GetShortDescription(description) != B_OK)
716 				description[0] = '\0';
717 			fTypeNameControl->SetText(description);
718 		}
719 
720 		if ((forceUpdate & B_LONG_DESCRIPTION_CHANGED) != 0) {
721 			if (type->GetLongDescription(description) != B_OK)
722 				description[0] = '\0';
723 			fDescriptionControl->SetText(description);
724 		}
725 
726 		if ((forceUpdate & B_SNIFFER_RULE_CHANGED) != 0) {
727 			BString rule;
728 			if (type->GetSnifferRule(&rule) != B_OK)
729 				rule = "";
730 			fRuleControl->SetText(rule.String());
731 		}
732 
733 		fExtensionListView->SetType(&fCurrentType);
734 	} else {
735 		fCurrentType.Unset();
736 		fInternalNameView->SetText(NULL);
737 		fTypeNameControl->SetText(NULL);
738 		fDescriptionControl->SetText(NULL);
739 		fRuleControl->SetText(NULL);
740 		fPreferredField->Menu()->ItemAt(0)->SetMarked(true);
741 		fExtensionListView->SetType(NULL);
742 	}
743 
744 	if ((forceUpdate & B_FILE_EXTENSIONS_CHANGED) != 0)
745 		_UpdateExtensions(type);
746 
747 	if ((forceUpdate & B_PREFERRED_APP_CHANGED) != 0)
748 		_UpdatePreferredApps(type);
749 
750 	if ((forceUpdate & (B_ICON_CHANGED | B_PREFERRED_APP_CHANGED)) != 0)
751 		_UpdateIcon(type);
752 
753 	if ((forceUpdate & B_ATTR_INFO_CHANGED) != 0)
754 		fAttributeListView->SetTo(type);
755 
756 	// enable/disable controls
757 
758 	fIconView->SetEnabled(enabled);
759 
760 	fInternalNameView->SetEnabled(enabled);
761 	fTypeNameControl->SetEnabled(enabled);
762 	fDescriptionControl->SetEnabled(enabled);
763 	fPreferredField->SetEnabled(enabled);
764 
765 	fRemoveTypeButton->SetEnabled(enabled);
766 
767 	fSelectButton->SetEnabled(enabled);
768 	fSameAsButton->SetEnabled(enabled);
769 
770 	fExtensionLabel->SetEnabled(enabled);
771 	fAddExtensionButton->SetEnabled(enabled);
772 	fRemoveExtensionButton->SetEnabled(false);
773 	fRuleControl->SetEnabled(enabled);
774 
775 	fAddAttributeButton->SetEnabled(enabled);
776 	fRemoveAttributeButton->SetEnabled(false);
777 }
778 
779 
780 void
781 FileTypesWindow::PlaceSubWindow(BWindow* window)
782 {
783 	window->MoveTo(Frame().left + (Frame().Width() - window->Frame().Width()) / 2.0f,
784 		Frame().top + (Frame().Height() - window->Frame().Height()) / 2.0f);
785 }
786 
787 
788 void
789 FileTypesWindow::MessageReceived(BMessage* message)
790 {
791 	switch (message->what) {
792 		case B_SIMPLE_DATA:
793 			type_code type;
794 			if (message->GetInfo("refs", &type) == B_OK
795 				&& type == B_REF_TYPE) {
796 				be_app->PostMessage(message);
797 			}
798 			break;
799 
800 		case kMsgToggleIcons:
801 		{
802 			BMenuItem* item;
803 			if (message->FindPointer("source", (void **)&item) != B_OK)
804 				break;
805 
806 			item->SetMarked(!fTypeListView->IsShowingIcons());
807 			fTypeListView->ShowIcons(item->IsMarked());
808 
809 			// update settings
810 			BMessage update(kMsgSettingsChanged);
811 			update.AddBool("show_icons", item->IsMarked());
812 			be_app_messenger.SendMessage(&update);
813 			break;
814 		}
815 
816 		case kMsgToggleRule:
817 		{
818 			BMenuItem* item;
819 			if (message->FindPointer("source", (void **)&item) != B_OK)
820 				break;
821 
822 			item->SetMarked(fRuleControl->IsHidden());
823 			_ShowSnifferRule(item->IsMarked());
824 
825 			// update settings
826 			BMessage update(kMsgSettingsChanged);
827 			update.AddBool("show_rule", item->IsMarked());
828 			be_app_messenger.SendMessage(&update);
829 			break;
830 		}
831 
832 		case kMsgTypeSelected:
833 		{
834 			int32 index;
835 			if (message->FindInt32("index", &index) == B_OK) {
836 				MimeTypeItem* item = (MimeTypeItem*)fTypeListView->ItemAt(index);
837 				if (item != NULL) {
838 					BMimeType type(item->Type());
839 					_SetType(&type);
840 				} else
841 					_SetType(NULL);
842 			}
843 			break;
844 		}
845 
846 		case kMsgAddType:
847 		{
848 			if (fNewTypeWindow == NULL) {
849 				fNewTypeWindow = new NewFileTypeWindow(this, fCurrentType.Type());
850 				fNewTypeWindow->Show();
851 			} else
852 				fNewTypeWindow->Activate();
853 			break;
854 		}
855 		case kMsgNewTypeWindowClosed:
856 			fNewTypeWindow = NULL;
857 			break;
858 
859 		case kMsgRemoveType:
860 		{
861 			if (fCurrentType.Type() == NULL)
862 				break;
863 
864 			BAlert* alert;
865 			if (fCurrentType.IsSupertypeOnly()) {
866 				alert = new BPrivate::OverrideAlert("FileTypes Request",
867 					"Removing a super type cannot be reverted.\n"
868 					"All file types that belong to this super type "
869 					"will be lost!\n\n"
870 					"Are you sure you want to do this? To remove the whole "
871 					"group, hold down the Shift key and press \"Remove\".",
872 					"Remove", B_SHIFT_KEY, "Cancel", 0, NULL, 0,
873 					B_WIDTH_AS_USUAL, B_STOP_ALERT);
874 			} else {
875 				alert = new BAlert("FileTypes Request",
876 					"Removing a file type cannot be reverted.\n"
877 					"Are you sure you want to remove it?",
878 					"Remove", "Cancel", NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
879 			}
880 			if (alert->Go())
881 				break;
882 
883 			status_t status = fCurrentType.Delete();
884 			if (status != B_OK)
885 				fprintf(stderr, "Could not remove file type: %s\n", strerror(status));
886 			break;
887 		}
888 
889 		case kMsgSelectNewType:
890 		{
891 			const char* type;
892 			if (message->FindString("type", &type) == B_OK)
893 				fTypeListView->SelectNewType(type);
894 			break;
895 		}
896 
897 		// File Recognition group
898 
899 		case kMsgExtensionSelected:
900 		{
901 			int32 index;
902 			if (message->FindInt32("index", &index) == B_OK) {
903 				BStringItem* item = (BStringItem*)fExtensionListView->ItemAt(index);
904 				fRemoveExtensionButton->SetEnabled(item != NULL);
905 			}
906 			break;
907 		}
908 
909 		case kMsgExtensionInvoked:
910 		{
911 			if (fCurrentType.Type() == NULL)
912 				break;
913 
914 			int32 index;
915 			if (message->FindInt32("index", &index) == B_OK) {
916 				BStringItem* item = (BStringItem*)fExtensionListView->ItemAt(index);
917 				if (item == NULL)
918 					break;
919 
920 				BWindow* window = new ExtensionWindow(this, fCurrentType, item->Text());
921 				window->Show();
922 			}
923 			break;
924 		}
925 
926 		case kMsgAddExtension:
927 		{
928 			if (fCurrentType.Type() == NULL)
929 				break;
930 
931 			BWindow* window = new ExtensionWindow(this, fCurrentType, NULL);
932 			window->Show();
933 			break;
934 		}
935 
936 		case kMsgRemoveExtension:
937 		{
938 			int32 index = fExtensionListView->CurrentSelection();
939 			if (index < 0 || fCurrentType.Type() == NULL)
940 				break;
941 
942 			BMessage extensions;
943 			if (fCurrentType.GetFileExtensions(&extensions) == B_OK) {
944 				extensions.RemoveData("extensions", index);
945 				fCurrentType.SetFileExtensions(&extensions);
946 			}
947 			break;
948 		}
949 
950 		case kMsgRuleEntered:
951 		{
952 			// check rule
953 			BString parseError;
954 			if (BMimeType::CheckSnifferRule(fRuleControl->Text(), &parseError) != B_OK) {
955 				parseError.Prepend("Recognition rule is not valid:\n\n");
956 				error_alert(parseError.String());
957 			} else
958 				fCurrentType.SetSnifferRule(fRuleControl->Text());
959 			break;
960 		}
961 
962 		// Description group
963 
964 		case kMsgTypeEntered:
965 		{
966 			fCurrentType.SetShortDescription(fTypeNameControl->Text());
967 			break;
968 		}
969 
970 		case kMsgDescriptionEntered:
971 		{
972 			fCurrentType.SetLongDescription(fDescriptionControl->Text());
973 			break;
974 		}
975 
976 		// Preferred Application group
977 
978 		case kMsgPreferredAppChosen:
979 		{
980 			const char* signature;
981 			if (message->FindString("signature", &signature) != B_OK)
982 				signature = NULL;
983 
984 			fCurrentType.SetPreferredApp(signature);
985 			break;
986 		}
987 
988 		case kMsgSelectPreferredApp:
989 		{
990 			BMessage panel(kMsgOpenFilePanel);
991 			panel.AddString("title", "Select preferred application");
992 			panel.AddInt32("message", kMsgPreferredAppOpened);
993 			panel.AddMessenger("target", this);
994 
995 			be_app_messenger.SendMessage(&panel);
996 			break;
997 		}
998 		case kMsgPreferredAppOpened:
999 			_AdoptPreferredApplication(message, false);
1000 			break;
1001 
1002 		case kMsgSamePreferredAppAs:
1003 		{
1004 			BMessage panel(kMsgOpenFilePanel);
1005 			panel.AddString("title", "Select same preferred application as");
1006 			panel.AddInt32("message", kMsgSamePreferredAppAsOpened);
1007 			panel.AddMessenger("target", this);
1008 
1009 			be_app_messenger.SendMessage(&panel);
1010 			break;
1011 		}
1012 		case kMsgSamePreferredAppAsOpened:
1013 			_AdoptPreferredApplication(message, true);
1014 			break;
1015 
1016 		// Extra Attributes group
1017 
1018 		case kMsgAttributeSelected:
1019 		{
1020 			int32 index;
1021 			if (message->FindInt32("index", &index) == B_OK) {
1022 				AttributeItem* item = (AttributeItem*)fAttributeListView->ItemAt(index);
1023 				fRemoveAttributeButton->SetEnabled(item != NULL);
1024 			}
1025 			break;
1026 		}
1027 
1028 		case kMsgAttributeInvoked:
1029 		{
1030 			if (fCurrentType.Type() == NULL)
1031 				break;
1032 
1033 			int32 index;
1034 			if (message->FindInt32("index", &index) == B_OK) {
1035 				AttributeItem* item = (AttributeItem*)fAttributeListView->ItemAt(index);
1036 				if (item == NULL)
1037 					break;
1038 
1039 				BWindow* window = new AttributeWindow(this, fCurrentType,
1040 					item);
1041 				window->Show();
1042 			}
1043 			break;
1044 		}
1045 
1046 		case kMsgAddAttribute:
1047 		{
1048 			if (fCurrentType.Type() == NULL)
1049 				break;
1050 
1051 			BWindow* window = new AttributeWindow(this, fCurrentType, NULL);
1052 			window->Show();
1053 			break;
1054 		}
1055 
1056 		case kMsgRemoveAttribute:
1057 		{
1058 			int32 index = fAttributeListView->CurrentSelection();
1059 			if (index < 0 || fCurrentType.Type() == NULL)
1060 				break;
1061 
1062 			BMessage attributes;
1063 			if (fCurrentType.GetAttrInfo(&attributes) == B_OK) {
1064 				const char* kAttributeNames[] = {
1065 					"attr:public_name", "attr:name", "attr:type",
1066 					"attr:editable", "attr:viewable", "attr:extra",
1067 					"attr:alignment", "attr:width", "attr:display_as"
1068 				};
1069 
1070 				for (uint32 i = 0; i <
1071 						sizeof(kAttributeNames) / sizeof(kAttributeNames[0]); i++) {
1072 					attributes.RemoveData(kAttributeNames[i], index);
1073 				}
1074 
1075 				fCurrentType.SetAttrInfo(&attributes);
1076 			}
1077 			break;
1078 		}
1079 
1080 		case B_META_MIME_CHANGED:
1081 		{
1082 			const char* type;
1083 			int32 which;
1084 			if (message->FindString("be:type", &type) != B_OK
1085 				|| message->FindInt32("be:which", &which) != B_OK)
1086 				break;
1087 
1088 			if (fCurrentType.Type() == NULL)
1089 				break;
1090 
1091 			if (!strcasecmp(fCurrentType.Type(), type)) {
1092 				if (which != B_MIME_TYPE_DELETED)
1093 					_SetType(&fCurrentType, which);
1094 				else
1095 					_SetType(NULL);
1096 			} else {
1097 				// this change could still affect our current type
1098 
1099 				if (which == B_MIME_TYPE_DELETED
1100 #ifdef __HAIKU__
1101 					|| which == B_SUPPORTED_TYPES_CHANGED
1102 #endif
1103 					|| which == B_PREFERRED_APP_CHANGED)
1104 					_UpdatePreferredApps(&fCurrentType);
1105 			}
1106 			break;
1107 		}
1108 
1109 		default:
1110 			BWindow::MessageReceived(message);
1111 	}
1112 }
1113 
1114 
1115 bool
1116 FileTypesWindow::QuitRequested()
1117 {
1118 	BMessage update(kMsgSettingsChanged);
1119 	update.AddRect("file_types_frame", Frame());
1120 	be_app_messenger.SendMessage(&update);
1121 
1122 	be_app->PostMessage(kMsgTypesWindowClosed);
1123 	return true;
1124 }
1125 
1126 
1127