xref: /haiku/src/preferences/filetypes/FileTypesWindow.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
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(BRect frame)
236 	: BWindow(frame, "FileTypes", B_TITLED_WINDOW,
237 		B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS),
238 	fNewTypeWindow(NULL)
239 {
240 	// add the menu
241 
242 	BMenuBar* menuBar = new BMenuBar(BRect(0, 0, 0, 0), NULL);
243 	AddChild(menuBar);
244 
245 	BMenu* menu = new BMenu("File");
246 	BMenuItem* item;
247 	menu->AddItem(item = new BMenuItem("New Resource File" B_UTF8_ELLIPSIS,
248 		NULL, 'N', B_COMMAND_KEY));
249 	item->SetEnabled(false);
250 
251 	BMenu* recentsMenu = BRecentFilesList::NewFileListMenu("Open" B_UTF8_ELLIPSIS,
252 		NULL, NULL, be_app, 10, false, NULL, kSignature);
253 	item = new BMenuItem(recentsMenu, new BMessage(kMsgOpenFilePanel));
254 	item->SetShortcut('O', B_COMMAND_KEY);
255 	menu->AddItem(item);
256 	menu->AddItem(new BMenuItem("Application Types" B_UTF8_ELLIPSIS,
257 		new BMessage(kMsgOpenApplicationTypesWindow)));
258 	menu->AddSeparatorItem();
259 
260 	menu->AddItem(new BMenuItem("About FileTypes" B_UTF8_ELLIPSIS,
261 		new BMessage(B_ABOUT_REQUESTED)));
262 	menu->AddSeparatorItem();
263 
264 	menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED),
265 		'Q', B_COMMAND_KEY));
266 	menu->SetTargetForItems(be_app);
267 	menuBar->AddItem(menu);
268 
269 	menu = new BMenu("Settings");
270 	item = new BMenuItem("Show Icons in List", new BMessage(kMsgToggleIcons));
271 	item->SetTarget(this);
272 	menu->AddItem(item);
273 
274 	item = new BMenuItem("Show Recognition Rule", new BMessage(kMsgToggleRule));
275 	item->SetTarget(this);
276 	menu->AddItem(item);
277 	menuBar->AddItem(menu);
278 
279 	// MIME Types list
280 
281 	BRect rect = Bounds();
282 	rect.top = menuBar->Bounds().Height() + 1.0f;
283 	BView* topView = new BView(rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW);
284 	topView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
285 	AddChild(topView);
286 
287 	BButton* button = new BButton(rect, "add", "Add" B_UTF8_ELLIPSIS,
288 		new BMessage(kMsgAddType), B_FOLLOW_BOTTOM);
289 	button->ResizeToPreferred();
290 	button->MoveTo(8.0f, topView->Bounds().bottom - 8.0f - button->Bounds().Height());
291 	topView->AddChild(button);
292 
293 	rect = button->Frame();
294 	rect.OffsetBy(rect.Width() + 8.0f, 0.0f);
295 	fRemoveTypeButton = new BButton(rect, "remove", "Remove",
296 		new BMessage(kMsgRemoveType), B_FOLLOW_BOTTOM);
297 	fRemoveTypeButton->ResizeToPreferred();
298 	topView->AddChild(fRemoveTypeButton);
299 
300 	rect.bottom = rect.top - 10.0f;
301 	rect.top = 10.0f;
302 	rect.left = 10.0f;
303 	rect.right -= B_V_SCROLL_BAR_WIDTH + 2.0f;
304 	if (rect.right < 180)
305 		rect.right = 180;
306 
307 	fTypeListView = new MimeTypeListView(rect, "typeview", NULL, false, false,
308 		B_FOLLOW_LEFT | B_FOLLOW_TOP_BOTTOM);
309 	fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected));
310 
311 	BScrollView* scrollView = new BScrollView("scrollview", fTypeListView,
312 		B_FOLLOW_LEFT | B_FOLLOW_TOP_BOTTOM, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
313 	topView->AddChild(scrollView);
314 
315 	// "Icon" group
316 
317 	font_height plainHeight;
318 	be_plain_font->GetHeight(&plainHeight);
319 	float height = ceilf(plainHeight.ascent + plainHeight.descent
320 		+ plainHeight.leading) + 2.0f;
321 
322 	BFont font(be_bold_font);
323 	float labelWidth = font.StringWidth("Icon");
324 	font_height boldHeight;
325 	font.GetHeight(&boldHeight);
326 
327 	BRect innerRect;
328 	fIconView = new TypeIconView(innerRect, "icon", NULL,
329 		B_FOLLOW_LEFT | B_FOLLOW_V_CENTER);
330 	fIconView->ResizeToPreferred();
331 
332 	rect.left = rect.right + 12.0f + B_V_SCROLL_BAR_WIDTH;
333 	rect.right = rect.left + max_c(fIconView->Bounds().Width(), labelWidth) + 16.0f;
334 	rect.bottom = rect.top + ceilf(boldHeight.ascent)
335 		+ max_c(fIconView->Bounds().Height(),
336 			button->Bounds().Height() * 2.0f + height + 4.0f) + 12.0f;
337 	rect.top -= 2.0f;
338 	fIconBox = new BBox(rect);
339 	fIconBox->SetLabel("Icon");
340 	topView->AddChild(fIconBox);
341 
342 	innerRect.left = 8.0f;
343 	innerRect.top = plainHeight.ascent + 3.0f
344 		+ (rect.Height() - boldHeight.ascent - fIconView->Bounds().Height()) / 2.0f;
345 	if (innerRect.top + fIconView->Bounds().Height() > fIconBox->Bounds().Height() - 6.0f)
346 		innerRect.top = fIconBox->Bounds().Height() - 6.0f - fIconView->Bounds().Height();
347 	fIconView->MoveTo(innerRect.LeftTop());
348 	fIconBox->AddChild(fIconView);
349 
350 	// "File Recognition" group
351 
352 	BRect rightRect(rect);
353 	rightRect.left = rect.right + 8.0f;
354 	rightRect.right = topView->Bounds().Width() - 8.0f;
355 	fRecognitionBox = new BBox(rightRect, NULL, B_FOLLOW_LEFT_RIGHT);
356 	fRecognitionBox->SetLabel("File Recognition");
357 	topView->AddChild(fRecognitionBox);
358 
359 	innerRect = fRecognitionBox->Bounds().InsetByCopy(8.0f, 4.0f);
360 	innerRect.top += ceilf(boldHeight.ascent);
361 	fExtensionLabel = new StringView(innerRect, "extension", "Extensions:", NULL);
362 	fExtensionLabel->SetAlignment(B_ALIGN_LEFT, B_ALIGN_LEFT);
363 	fExtensionLabel->ResizeToPreferred();
364 	fRecognitionBox->AddChild(fExtensionLabel);
365 
366 	innerRect.top += fExtensionLabel->Bounds().Height() + 2.0f;
367 	innerRect.left = innerRect.right - button->StringWidth("Remove") - 16.0f;
368 	innerRect.bottom = innerRect.top + button->Bounds().Height();
369 	fAddExtensionButton = new BButton(innerRect, "add ext", "Add" B_UTF8_ELLIPSIS,
370 		new BMessage(kMsgAddExtension), B_FOLLOW_RIGHT);
371 	fRecognitionBox->AddChild(fAddExtensionButton);
372 
373 	innerRect.OffsetBy(0, innerRect.Height() + 4.0f);
374 	fRemoveExtensionButton = new BButton(innerRect, "remove ext", "Remove",
375 		new BMessage(kMsgRemoveExtension), B_FOLLOW_RIGHT);
376 	fRecognitionBox->AddChild(fRemoveExtensionButton);
377 
378 	innerRect.right = innerRect.left - 10.0f - B_V_SCROLL_BAR_WIDTH;
379 	innerRect.left = 10.0f;
380 	innerRect.top = fAddExtensionButton->Frame().top + 2.0f;
381 	innerRect.bottom = innerRect.bottom - 2.0f;
382 		// take scrollview border into account
383 	fExtensionListView = new BListView(innerRect, "listview ext",
384 		B_SINGLE_SELECTION_LIST, B_FOLLOW_LEFT_RIGHT);
385 	fExtensionListView->SetSelectionMessage(new BMessage(kMsgExtensionSelected));
386 	fExtensionListView->SetInvocationMessage(new BMessage(kMsgExtensionInvoked));
387 
388 	scrollView = new BScrollView("scrollview ext", fExtensionListView,
389 		B_FOLLOW_LEFT_RIGHT, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
390 	fRecognitionBox->AddChild(scrollView);
391 
392 	innerRect.left = 8.0f;
393 	innerRect.top = innerRect.bottom + 10.0f;
394 	innerRect.right = fRecognitionBox->Bounds().right - 8.0f;
395 	innerRect.bottom = innerRect.top + 20.0f;
396 	fRuleControl = new BTextControl(innerRect, "rule", "Rule:", "",
397 		new BMessage(kMsgRuleEntered), B_FOLLOW_LEFT_RIGHT);
398 	//fRuleControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
399 	fRuleControl->SetDivider(fRuleControl->StringWidth(fRuleControl->Label()) + 6.0f);
400 	fRuleControl->Hide();
401 	fRecognitionBox->AddChild(fRuleControl);
402 
403 	// "Description" group
404 
405 	rect.top = rect.bottom + 8.0f;
406 	rect.bottom = rect.top + ceilf(boldHeight.ascent) + 24.0f;
407 	rect.right = rightRect.right;
408 	fDescriptionBox = new BBox(rect, NULL, B_FOLLOW_LEFT_RIGHT);
409 	fDescriptionBox->SetLabel("Description");
410 	topView->AddChild(fDescriptionBox);
411 
412 	innerRect = fDescriptionBox->Bounds().InsetByCopy(8.0f, 6.0f);
413 	innerRect.top += ceilf(boldHeight.ascent);
414 	innerRect.bottom = innerRect.top + button->Bounds().Height();
415 	fInternalNameView = new StringView(innerRect, "internal", "Internal Name:", "",
416 		B_FOLLOW_LEFT_RIGHT);
417 	labelWidth = fInternalNameView->StringWidth(fInternalNameView->Label()) + 2.0f;
418 	fInternalNameView->SetDivider(labelWidth);
419 	fInternalNameView->SetEnabled(false);
420 	fInternalNameView->ResizeToPreferred();
421 	fDescriptionBox->AddChild(fInternalNameView);
422 
423 	innerRect.OffsetBy(0, fInternalNameView->Bounds().Height() + 5.0f);
424 	fTypeNameControl = new BTextControl(innerRect, "type", "Type Name:", "",
425 		new BMessage(kMsgTypeEntered), B_FOLLOW_LEFT_RIGHT);
426 	fTypeNameControl->SetDivider(labelWidth);
427 	fTypeNameControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
428 	fDescriptionBox->ResizeBy(0, fInternalNameView->Bounds().Height()
429 		+ fTypeNameControl->Bounds().Height() * 2.0f);
430 	fDescriptionBox->AddChild(fTypeNameControl);
431 
432 	innerRect.OffsetBy(0, fTypeNameControl->Bounds().Height() + 5.0f);
433 	fDescriptionControl = new BTextControl(innerRect, "description", "Description:", "",
434 		new BMessage(kMsgDescriptionEntered), B_FOLLOW_LEFT_RIGHT);
435 	fDescriptionControl->SetDivider(labelWidth);
436 	fDescriptionControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
437 	fDescriptionBox->AddChild(fDescriptionControl);
438 
439 	// "Preferred Application" group
440 
441 	rect = fDescriptionBox->Frame();
442 	rect.top = rect.bottom + 8.0f;
443 	rect.bottom = rect.top + ceilf(boldHeight.ascent)
444 		+ button->Bounds().Height() + 14.0f;
445 	fPreferredBox = new BBox(rect, NULL, B_FOLLOW_LEFT_RIGHT);
446 	fPreferredBox->SetLabel("Preferred Application");
447 	topView->AddChild(fPreferredBox);
448 
449 	innerRect = fPreferredBox->Bounds().InsetByCopy(8.0f, 6.0f);
450 	innerRect.top += ceilf(boldHeight.ascent);
451 	innerRect.left = innerRect.right - button->StringWidth("Same As" B_UTF8_ELLIPSIS) - 24.0f;
452 	innerRect.bottom = innerRect.top + button->Bounds().Height();
453 	fSameAsButton = new BButton(innerRect, "same as", "Same As" B_UTF8_ELLIPSIS,
454 		new BMessage(kMsgSamePreferredAppAs), B_FOLLOW_RIGHT);
455 	fPreferredBox->AddChild(fSameAsButton);
456 
457 	innerRect.OffsetBy(-innerRect.Width() - 6.0f, 0.0f);
458 	fSelectButton = new BButton(innerRect, "select", "Select" B_UTF8_ELLIPSIS,
459 		new BMessage(kMsgSelectPreferredApp), B_FOLLOW_RIGHT);
460 	fPreferredBox->AddChild(fSelectButton);
461 
462 	menu = new BPopUpMenu("preferred");
463 	menu->AddItem(item = new BMenuItem("None", new BMessage(kMsgPreferredAppChosen)));
464 	item->SetMarked(true);
465 
466 	innerRect.right = innerRect.left - 6.0f;
467 	innerRect.left = 8.0f;
468 	BView* constrainingView = new BView(innerRect, NULL, B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW);
469 	constrainingView->SetViewColor(topView->ViewColor());
470 
471 	fPreferredField = new BMenuField(innerRect.OffsetToCopy(B_ORIGIN), "preferred",
472 		NULL, menu);
473 	float width;
474 	fPreferredField->GetPreferredSize(&width, &height);
475 	fPreferredField->ResizeTo(innerRect.Width(), height);
476 	fPreferredField->MoveBy(0.0f, (innerRect.Height() - height) / 2.0f);
477 	constrainingView->AddChild(fPreferredField);
478 		// we embed the menu field in another view to make it behave like
479 		// we want so that it can't obscure other elements with larger
480 		// labels
481 
482 	fPreferredBox->AddChild(constrainingView);
483 
484 	// "Extra Attributes" group
485 
486 	rect.top = rect.bottom + 8.0f;
487 	rect.bottom = topView->Bounds().Height() - 8.0f;
488 	fAttributeBox = new BBox(rect, NULL, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP_BOTTOM);
489 	fAttributeBox->SetLabel("Extra Attributes");
490 	topView->AddChild(fAttributeBox);
491 
492 	innerRect = fAttributeBox->Bounds().InsetByCopy(8.0f, 6.0f);
493 	innerRect.top += ceilf(boldHeight.ascent);
494 	innerRect.left = innerRect.right - button->StringWidth("Remove") - 16.0f;
495 	innerRect.bottom = innerRect.top + button->Bounds().Height();
496 	fAddAttributeButton = new BButton(innerRect, "add attr", "Add" B_UTF8_ELLIPSIS,
497 		new BMessage(kMsgAddAttribute), B_FOLLOW_RIGHT);
498 	fAttributeBox->AddChild(fAddAttributeButton);
499 
500 	innerRect.OffsetBy(0, innerRect.Height() + 4.0f);
501 	fRemoveAttributeButton = new BButton(innerRect, "remove attr", "Remove",
502 		new BMessage(kMsgRemoveAttribute), B_FOLLOW_RIGHT);
503 	fAttributeBox->AddChild(fRemoveAttributeButton);
504 /*
505 	innerRect.OffsetBy(0, innerRect.Height() + 4.0f);
506 	button = new BButton(innerRect, "push attr", "Push Up",
507 		new BMessage(kMsgRemoveAttribute), B_FOLLOW_RIGHT);
508 	fAttributeBox->AddChild(button);
509 */
510 	innerRect.right = innerRect.left - 10.0f - B_V_SCROLL_BAR_WIDTH;
511 	innerRect.left = 10.0f;
512 	innerRect.top = 8.0f + ceilf(boldHeight.ascent);
513 	innerRect.bottom = fAttributeBox->Bounds().bottom - 10.0f;
514 		// take scrollview border into account
515 	fAttributeListView = new AttributeListView(innerRect, "listview attr",
516 		B_FOLLOW_ALL);
517 	fAttributeListView->SetSelectionMessage(new BMessage(kMsgAttributeSelected));
518 	fAttributeListView->SetInvocationMessage(new BMessage(kMsgAttributeInvoked));
519 
520 	scrollView = new BScrollView("scrollview attr", fAttributeListView,
521 		B_FOLLOW_ALL, B_FRAME_EVENTS | B_WILL_DRAW, false, true);
522 	fAttributeBox->AddChild(scrollView);
523 
524 	SetSizeLimits(rightRect.left + 72.0f + font.StringWidth("jpg")
525 		+ font.StringWidth(fRecognitionBox->Label()), 32767.0f,
526 		rect.top + 2.0f * button->Bounds().Height() + boldHeight.ascent
527 		+ 32.0f + menuBar->Bounds().Height(), 32767.0f);
528 
529 	_SetType(NULL);
530 	BMimeType::StartWatching(this);
531 }
532 
533 
534 FileTypesWindow::~FileTypesWindow()
535 {
536 	BMimeType::StopWatching(this);
537 }
538 
539 
540 void
541 FileTypesWindow::_ShowSnifferRule(bool show)
542 {
543 	if (fRuleControl->IsHidden() == !show)
544 		return;
545 
546 	float minWidth, maxWidth, minHeight, maxHeight;
547 	GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
548 
549 	float diff = fRuleControl->Bounds().Height() + 8.0f;
550 
551 	if (!show) {
552 		fRuleControl->Hide();
553 		diff = -diff;
554 	}
555 
556 	// adjust other controls to make space or take it again
557 
558 	fIconBox->ResizeBy(0.0f, diff);
559 	fRecognitionBox->ResizeBy(0.0f, diff);
560 	fDescriptionBox->MoveBy(0.0f, diff);
561 	fPreferredBox->MoveBy(0.0f, diff);
562 	fAttributeBox->MoveBy(0.0f, diff);
563 	fAttributeBox->ResizeBy(0.0f, -diff);
564 
565 	if (show)
566 		fRuleControl->Show();
567 
568 	SetSizeLimits(minWidth, maxWidth, minHeight + diff, maxHeight);
569 }
570 
571 
572 void
573 FileTypesWindow::_UpdateExtensions(BMimeType* type)
574 {
575 	// clear list
576 
577 	for (int32 i = fExtensionListView->CountItems(); i-- > 0;) {
578 		delete fExtensionListView->ItemAt(i);
579 	}
580 	fExtensionListView->MakeEmpty();
581 
582 	// fill it again
583 
584 	if (type == NULL)
585 		return;
586 
587 	BMessage extensions;
588 	if (type->GetFileExtensions(&extensions) != B_OK)
589 		return;
590 
591 	const char* extension;
592 	int32 i = 0;
593 	while (extensions.FindString("extensions", i++, &extension) == B_OK) {
594 		char dotExtension[B_FILE_NAME_LENGTH];
595 		snprintf(dotExtension, B_FILE_NAME_LENGTH, ".%s", extension);
596 
597 		fExtensionListView->AddItem(new BStringItem(dotExtension));
598 	}
599 }
600 
601 
602 void
603 FileTypesWindow::_AdoptPreferredApplication(BMessage* message, bool sameAs)
604 {
605 	if (fCurrentType.Type() == NULL)
606 		return;
607 
608 	BString preferred;
609 	if (retrieve_preferred_app(message, sameAs, fCurrentType.Type(), preferred) != B_OK)
610 		return;
611 
612 	status_t status = fCurrentType.SetPreferredApp(preferred.String());
613 	if (status != B_OK)
614 		error_alert("Could not set preferred application", status);
615 }
616 
617 
618 void
619 FileTypesWindow::_UpdatePreferredApps(BMimeType* type)
620 {
621 	update_preferred_app_menu(fPreferredField->Menu(), type, kMsgPreferredAppChosen);
622 }
623 
624 
625 void
626 FileTypesWindow::_UpdateIcon(BMimeType* type)
627 {
628 	fIconView->SetTo(type);
629 }
630 
631 
632 void
633 FileTypesWindow::_SetType(BMimeType* type, int32 forceUpdate)
634 {
635 	bool enabled = type != NULL;
636 
637 	// update controls
638 
639 	if (type != NULL) {
640 		if (fCurrentType == *type) {
641 			if (!forceUpdate)
642 				return;
643 		} else
644 			forceUpdate = B_EVERYTHING_CHANGED;
645 
646 		if (&fCurrentType != type)
647 			fCurrentType.SetTo(type->Type());
648 
649 		fInternalNameView->SetText(type->Type());
650 
651 		char description[B_MIME_TYPE_LENGTH];
652 
653 		if ((forceUpdate & B_SHORT_DESCRIPTION_CHANGED) != 0) {
654 			if (type->GetShortDescription(description) != B_OK)
655 				description[0] = '\0';
656 			fTypeNameControl->SetText(description);
657 		}
658 
659 		if ((forceUpdate & B_LONG_DESCRIPTION_CHANGED) != 0) {
660 			if (type->GetLongDescription(description) != B_OK)
661 				description[0] = '\0';
662 			fDescriptionControl->SetText(description);
663 		}
664 
665 		if ((forceUpdate & B_SNIFFER_RULE_CHANGED) != 0) {
666 			BString rule;
667 			if (type->GetSnifferRule(&rule) != B_OK)
668 				rule = "";
669 			fRuleControl->SetText(rule.String());
670 		}
671 	} else {
672 		fCurrentType.Unset();
673 		fInternalNameView->SetText(NULL);
674 		fTypeNameControl->SetText(NULL);
675 		fDescriptionControl->SetText(NULL);
676 		fRuleControl->SetText(NULL);
677 		fPreferredField->Menu()->ItemAt(0)->SetMarked(true);
678 	}
679 
680 	if ((forceUpdate & B_FILE_EXTENSIONS_CHANGED) != 0)
681 		_UpdateExtensions(type);
682 
683 	if ((forceUpdate & B_PREFERRED_APP_CHANGED) != 0)
684 		_UpdatePreferredApps(type);
685 
686 	if ((forceUpdate & (B_ICON_CHANGED | B_PREFERRED_APP_CHANGED)) != 0)
687 		_UpdateIcon(type);
688 
689 	if ((forceUpdate & B_ATTR_INFO_CHANGED) != 0)
690 		fAttributeListView->SetTo(type);
691 
692 	// enable/disable controls
693 
694 	fIconView->SetEnabled(enabled);
695 
696 	fInternalNameView->SetEnabled(enabled);
697 	fTypeNameControl->SetEnabled(enabled);
698 	fDescriptionControl->SetEnabled(enabled);
699 	fPreferredField->SetEnabled(enabled);
700 
701 	fRemoveTypeButton->SetEnabled(enabled);
702 
703 	fSelectButton->SetEnabled(enabled);
704 	fSameAsButton->SetEnabled(enabled);
705 
706 	fExtensionLabel->SetEnabled(enabled);
707 	fAddExtensionButton->SetEnabled(enabled);
708 	fRemoveExtensionButton->SetEnabled(false);
709 	fRuleControl->SetEnabled(enabled);
710 
711 	fAddAttributeButton->SetEnabled(enabled);
712 	fRemoveAttributeButton->SetEnabled(false);
713 }
714 
715 
716 void
717 FileTypesWindow::PlaceSubWindow(BWindow* window)
718 {
719 	window->MoveTo(Frame().left + (Frame().Width() - window->Frame().Width()) / 2.0f,
720 		Frame().top + (Frame().Height() - window->Frame().Height()) / 2.0f);
721 }
722 
723 
724 void
725 FileTypesWindow::MessageReceived(BMessage* message)
726 {
727 	switch (message->what) {
728 		case B_SIMPLE_DATA:
729 			type_code type;
730 			if (message->GetInfo("refs", &type) == B_OK
731 				&& type == B_REF_TYPE) {
732 				be_app->PostMessage(message);
733 			}
734 			break;
735 
736 		case kMsgToggleIcons:
737 		{
738 			BMenuItem* item;
739 			if (message->FindPointer("source", (void **)&item) != B_OK)
740 				break;
741 
742 			item->SetMarked(!fTypeListView->IsShowingIcons());
743 			fTypeListView->ShowIcons(item->IsMarked());
744 			break;
745 		}
746 
747 		case kMsgToggleRule:
748 		{
749 			BMenuItem* item;
750 			if (message->FindPointer("source", (void **)&item) != B_OK)
751 				break;
752 
753 			item->SetMarked(fRuleControl->IsHidden());
754 			_ShowSnifferRule(item->IsMarked());
755 			break;
756 		}
757 
758 		case kMsgTypeSelected:
759 		{
760 			int32 index;
761 			if (message->FindInt32("index", &index) == B_OK) {
762 				MimeTypeItem* item = (MimeTypeItem*)fTypeListView->ItemAt(index);
763 				if (item != NULL) {
764 					BMimeType type(item->Type());
765 					_SetType(&type);
766 				} else
767 					_SetType(NULL);
768 			}
769 			break;
770 		}
771 
772 		case kMsgAddType:
773 		{
774 			if (fNewTypeWindow == NULL) {
775 				fNewTypeWindow = new NewFileTypeWindow(this, fCurrentType.Type());
776 				fNewTypeWindow->Show();
777 			} else
778 				fNewTypeWindow->Activate();
779 			break;
780 		}
781 		case kMsgNewTypeWindowClosed:
782 			fNewTypeWindow = NULL;
783 			break;
784 
785 		case kMsgRemoveType:
786 		{
787 			if (fCurrentType.Type() == NULL)
788 				break;
789 
790 			BAlert* alert;
791 			if (fCurrentType.IsSupertypeOnly()) {
792 				alert = new BPrivate::OverrideAlert("FileTypes Request",
793 					"Removing a super type cannot be reverted.\n"
794 					"All file types that belong to this super type "
795 					"will be lost!\n\n"
796 					"Are you sure you want to do this? To remove the whole "
797 					"group, hold down the Shift key and press \"Remove\".",
798 					"Remove", B_SHIFT_KEY, "Cancel", 0, NULL, 0,
799 					B_WIDTH_AS_USUAL, B_STOP_ALERT);
800 			} else {
801 				alert = new BAlert("FileTypes Request",
802 					"Removing a file type cannot be reverted.\n"
803 					"Are you sure you want to remove it?",
804 					"Remove", "Cancel", NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
805 			}
806 			if (alert->Go())
807 				break;
808 
809 			status_t status = fCurrentType.Delete();
810 			if (status != B_OK)
811 				fprintf(stderr, "Could not remove file type: %s\n", strerror(status));
812 			break;
813 		}
814 
815 		case kMsgSelectNewType:
816 		{
817 			const char* type;
818 			if (message->FindString("type", &type) == B_OK)
819 				fTypeListView->SelectNewType(type);
820 			break;
821 		}
822 
823 		// File Recognition group
824 
825 		case kMsgExtensionSelected:
826 		{
827 			int32 index;
828 			if (message->FindInt32("index", &index) == B_OK) {
829 				BStringItem* item = (BStringItem*)fExtensionListView->ItemAt(index);
830 				fRemoveExtensionButton->SetEnabled(item != NULL);
831 			}
832 			break;
833 		}
834 
835 		case kMsgExtensionInvoked:
836 		{
837 			if (fCurrentType.Type() == NULL)
838 				break;
839 
840 			int32 index;
841 			if (message->FindInt32("index", &index) == B_OK) {
842 				BStringItem* item = (BStringItem*)fExtensionListView->ItemAt(index);
843 				if (item == NULL)
844 					break;
845 
846 				BWindow* window = new ExtensionWindow(this, fCurrentType, item->Text());
847 				window->Show();
848 			}
849 			break;
850 		}
851 
852 		case kMsgAddExtension:
853 		{
854 			if (fCurrentType.Type() == NULL)
855 				break;
856 
857 			BWindow* window = new ExtensionWindow(this, fCurrentType, NULL);
858 			window->Show();
859 			break;
860 		}
861 
862 		case kMsgRemoveExtension:
863 		{
864 			int32 index = fExtensionListView->CurrentSelection();
865 			if (index < 0 || fCurrentType.Type() == NULL)
866 				break;
867 
868 			BMessage extensions;
869 			if (fCurrentType.GetFileExtensions(&extensions) == B_OK) {
870 				extensions.RemoveData("extensions", index);
871 				fCurrentType.SetFileExtensions(&extensions);
872 			}
873 			break;
874 		}
875 
876 		case kMsgRuleEntered:
877 		{
878 			// check rule
879 			BString parseError;
880 			if (BMimeType::CheckSnifferRule(fRuleControl->Text(), &parseError) != B_OK) {
881 				parseError.Prepend("Recognition rule is not valid:\n\n");
882 				error_alert(parseError.String());
883 			} else
884 				fCurrentType.SetSnifferRule(fRuleControl->Text());
885 			break;
886 		}
887 
888 		// Description group
889 
890 		case kMsgTypeEntered:
891 		{
892 			fCurrentType.SetShortDescription(fTypeNameControl->Text());
893 			break;
894 		}
895 
896 		case kMsgDescriptionEntered:
897 		{
898 			fCurrentType.SetLongDescription(fDescriptionControl->Text());
899 			break;
900 		}
901 
902 		// Preferred Application group
903 
904 		case kMsgPreferredAppChosen:
905 		{
906 			const char* signature;
907 			if (message->FindString("signature", &signature) != B_OK)
908 				signature = NULL;
909 
910 			fCurrentType.SetPreferredApp(signature);
911 			break;
912 		}
913 
914 		case kMsgSelectPreferredApp:
915 		{
916 			BMessage panel(kMsgOpenFilePanel);
917 			panel.AddString("title", "Select Preferred Application");
918 			panel.AddInt32("message", kMsgPreferredAppOpened);
919 			panel.AddMessenger("target", this);
920 
921 			be_app_messenger.SendMessage(&panel);
922 			break;
923 		}
924 		case kMsgPreferredAppOpened:
925 			_AdoptPreferredApplication(message, false);
926 			break;
927 
928 		case kMsgSamePreferredAppAs:
929 		{
930 			BMessage panel(kMsgOpenFilePanel);
931 			panel.AddString("title", "Select Same Preferred Application As");
932 			panel.AddInt32("message", kMsgSamePreferredAppAsOpened);
933 			panel.AddMessenger("target", this);
934 
935 			be_app_messenger.SendMessage(&panel);
936 			break;
937 		}
938 		case kMsgSamePreferredAppAsOpened:
939 			_AdoptPreferredApplication(message, true);
940 			break;
941 
942 		// Extra Attributes group
943 
944 		case kMsgAttributeSelected:
945 		{
946 			int32 index;
947 			if (message->FindInt32("index", &index) == B_OK) {
948 				AttributeItem* item = (AttributeItem*)fAttributeListView->ItemAt(index);
949 				fRemoveAttributeButton->SetEnabled(item != NULL);
950 			}
951 			break;
952 		}
953 
954 		case kMsgAttributeInvoked:
955 		{
956 			if (fCurrentType.Type() == NULL)
957 				break;
958 
959 			int32 index;
960 			if (message->FindInt32("index", &index) == B_OK) {
961 				AttributeItem* item = (AttributeItem*)fAttributeListView->ItemAt(index);
962 				if (item == NULL)
963 					break;
964 
965 				BWindow* window = new AttributeWindow(this, fCurrentType,
966 					item);
967 				window->Show();
968 			}
969 			break;
970 		}
971 
972 		case kMsgAddAttribute:
973 		{
974 			if (fCurrentType.Type() == NULL)
975 				break;
976 
977 			BWindow* window = new AttributeWindow(this, fCurrentType, NULL);
978 			window->Show();
979 			break;
980 		}
981 
982 		case kMsgRemoveAttribute:
983 		{
984 			int32 index = fAttributeListView->CurrentSelection();
985 			if (index < 0 || fCurrentType.Type() == NULL)
986 				break;
987 
988 			BMessage attributes;
989 			if (fCurrentType.GetAttrInfo(&attributes) == B_OK) {
990 				const char* kAttributeNames[] = {
991 					"attr:public_name", "attr:name", "attr:type",
992 					"attr:editable", "attr:viewable", "attr:extra",
993 					"attr:alignment", "attr:width", "attr:display_as"
994 				};
995 
996 				for (uint32 i = 0; i <
997 						sizeof(kAttributeNames) / sizeof(kAttributeNames[0]); i++) {
998 					attributes.RemoveData(kAttributeNames[i], index);
999 				}
1000 
1001 				fCurrentType.SetAttrInfo(&attributes);
1002 			}
1003 			break;
1004 		}
1005 
1006 		case B_META_MIME_CHANGED:
1007 		{
1008 			const char* type;
1009 			int32 which;
1010 			if (message->FindString("be:type", &type) != B_OK
1011 				|| message->FindInt32("be:which", &which) != B_OK)
1012 				break;
1013 
1014 			if (fCurrentType.Type() == NULL)
1015 				break;
1016 
1017 			if (!strcasecmp(fCurrentType.Type(), type)) {
1018 				if (which != B_MIME_TYPE_DELETED)
1019 					_SetType(&fCurrentType, which);
1020 				else
1021 					_SetType(NULL);
1022 			} else {
1023 				// this change could still affect our current type
1024 
1025 				if (which == B_MIME_TYPE_DELETED
1026 					|| which == B_PREFERRED_APP_CHANGED
1027 #ifdef __HAIKU__
1028 					|| which == B_SUPPORTED_TYPES_CHANGED
1029 #endif
1030 					|| which == B_ICON_FOR_TYPE_CHANGED) {
1031 					_UpdatePreferredApps(&fCurrentType);
1032 					_UpdateIcon(&fCurrentType);
1033 				}
1034 			}
1035 			break;
1036 		}
1037 
1038 		default:
1039 			BWindow::MessageReceived(message);
1040 	}
1041 }
1042 
1043 
1044 bool
1045 FileTypesWindow::QuitRequested()
1046 {
1047 	be_app->PostMessage(kMsgTypesWindowClosed);
1048 	return true;
1049 }
1050 
1051 
1052