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