xref: /haiku/src/preferences/filetypes/ApplicationTypeWindow.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
1 /*
2  * Copyright 2006-2010, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "ApplicationTypeWindow.h"
8 #include "DropTargetListView.h"
9 #include "FileTypes.h"
10 #include "IconView.h"
11 #include "PreferredAppMenu.h"
12 #include "StringView.h"
13 #include "TypeListWindow.h"
14 
15 #include <Application.h>
16 #include <Bitmap.h>
17 #include <Box.h>
18 #include <Button.h>
19 #include <Catalog.h>
20 #include <CheckBox.h>
21 #include <ControlLook.h>
22 #include <File.h>
23 #include <GroupView.h>
24 #include <LayoutBuilder.h>
25 #include <ListView.h>
26 #include <Locale.h>
27 #include <MenuBar.h>
28 #include <MenuField.h>
29 #include <MenuItem.h>
30 #include <Mime.h>
31 #include <NodeInfo.h>
32 #include <PopUpMenu.h>
33 #include <RadioButton.h>
34 #include <Roster.h>
35 #include <ScrollView.h>
36 #include <StringView.h>
37 #include <TextControl.h>
38 
39 #include <ctype.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <strings.h>
43 
44 
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "Application Type Window"
47 
48 
49 const uint32 kMsgSave = 'save';
50 const uint32 kMsgSignatureChanged = 'sgch';
51 const uint32 kMsgToggleAppFlags = 'tglf';
52 const uint32 kMsgAppFlagsChanged = 'afch';
53 
54 const uint32 kMsgIconChanged = 'icch';
55 const uint32 kMsgTypeIconsChanged = 'tich';
56 
57 const uint32 kMsgVersionInfoChanged = 'tvch';
58 
59 const uint32 kMsgTypeSelected = 'tpsl';
60 const uint32 kMsgAddType = 'adtp';
61 const uint32 kMsgTypeAdded = 'tpad';
62 const uint32 kMsgRemoveType = 'rmtp';
63 const uint32 kMsgTypeRemoved = 'tprm';
64 
65 
66 //! TextView that filters the tab key to be able to tab-navigate while editing
67 class TabFilteringTextView : public BTextView {
68 public:
69 								TabFilteringTextView(const char* name,
70 									uint32 changedMessageWhat = 0);
71 	virtual						~TabFilteringTextView();
72 
73 	virtual void				InsertText(const char* text, int32 length,
74 									int32 offset, const text_run_array* runs);
75 	virtual	void				DeleteText(int32 fromOffset, int32 toOffset);
76 	virtual	void				KeyDown(const char* bytes, int32 count);
77 	virtual	void				TargetedByScrollView(BScrollView* scroller);
78 	virtual	void				MakeFocus(bool focused = true);
79 
80 private:
81 			BScrollView*		fScrollView;
82 			uint32				fChangedMessageWhat;
83 };
84 
85 
86 class SupportedTypeItem : public BStringItem {
87 public:
88 								SupportedTypeItem(const char* type);
89 	virtual						~SupportedTypeItem();
90 
91 			const char*			Type() const { return fType.String(); }
92 			::Icon&				Icon() { return fIcon; }
93 
94 			void				SetIcon(::Icon* icon);
95 			void				SetIcon(entry_ref& ref, const char* type);
96 
97 	static	int					Compare(const void* _a, const void* _b);
98 
99 private:
100 			BString				fType;
101 			::Icon				fIcon;
102 };
103 
104 
105 class SupportedTypeListView : public DropTargetListView {
106 public:
107 								SupportedTypeListView(const char* name,
108 									list_view_type
109 										type = B_SINGLE_SELECTION_LIST,
110 									uint32 flags = B_WILL_DRAW
111 										| B_FRAME_EVENTS | B_NAVIGABLE);
112 	virtual						~SupportedTypeListView();
113 
114 	virtual	void				MessageReceived(BMessage* message);
115 	virtual	bool				AcceptsDrag(const BMessage* message);
116 };
117 
118 
119 // #pragma mark -
120 
121 
122 TabFilteringTextView::TabFilteringTextView(const char* name,
123 	uint32 changedMessageWhat)
124 	:
125 	BTextView(name, B_WILL_DRAW | B_PULSE_NEEDED | B_NAVIGABLE),
126 	fScrollView(NULL),
127 	fChangedMessageWhat(changedMessageWhat)
128 {
129 }
130 
131 
132 TabFilteringTextView::~TabFilteringTextView()
133 {
134 }
135 
136 
137 void
138 TabFilteringTextView::InsertText(const char* text, int32 length, int32 offset,
139 	const text_run_array* runs)
140 {
141 	BTextView::InsertText(text, length, offset, runs);
142 	if (fChangedMessageWhat != 0)
143 		Window()->PostMessage(fChangedMessageWhat);
144 }
145 
146 
147 void
148 TabFilteringTextView::DeleteText(int32 fromOffset, int32 toOffset)
149 {
150 	BTextView::DeleteText(fromOffset, toOffset);
151 	if (fChangedMessageWhat != 0)
152 		Window()->PostMessage(fChangedMessageWhat);
153 }
154 
155 
156 void
157 TabFilteringTextView::KeyDown(const char* bytes, int32 count)
158 {
159 	if (bytes[0] == B_TAB)
160 		BView::KeyDown(bytes, count);
161 	else
162 		BTextView::KeyDown(bytes, count);
163 }
164 
165 
166 void
167 TabFilteringTextView::TargetedByScrollView(BScrollView* scroller)
168 {
169 	fScrollView = scroller;
170 }
171 
172 
173 void
174 TabFilteringTextView::MakeFocus(bool focused)
175 {
176 	BTextView::MakeFocus(focused);
177 
178 	if (fScrollView)
179 		fScrollView->SetBorderHighlighted(focused);
180 }
181 
182 
183 // #pragma mark -
184 
185 
186 SupportedTypeItem::SupportedTypeItem(const char* type)
187 	: BStringItem(type),
188 	fType(type)
189 {
190 	BMimeType mimeType(type);
191 
192 	char description[B_MIME_TYPE_LENGTH];
193 	if (mimeType.GetShortDescription(description) == B_OK && description[0])
194 		SetText(description);
195 }
196 
197 
198 SupportedTypeItem::~SupportedTypeItem()
199 {
200 }
201 
202 
203 void
204 SupportedTypeItem::SetIcon(::Icon* icon)
205 {
206 	if (icon != NULL)
207 		fIcon = *icon;
208 	else
209 		fIcon.Unset();
210 }
211 
212 
213 void
214 SupportedTypeItem::SetIcon(entry_ref& ref, const char* type)
215 {
216 	fIcon.SetTo(ref, type);
217 }
218 
219 
220 /*static*/
221 int
222 SupportedTypeItem::Compare(const void* _a, const void* _b)
223 {
224 	const SupportedTypeItem* a = *(const SupportedTypeItem**)_a;
225 	const SupportedTypeItem* b = *(const SupportedTypeItem**)_b;
226 
227 	int compare = strcasecmp(a->Text(), b->Text());
228 	if (compare != 0)
229 		return compare;
230 
231 	return strcasecmp(a->Type(), b->Type());
232 }
233 
234 
235 //	#pragma mark -
236 
237 
238 SupportedTypeListView::SupportedTypeListView(const char* name,
239 	list_view_type type, uint32 flags)
240 	:
241 	DropTargetListView(name, type, flags)
242 {
243 }
244 
245 
246 SupportedTypeListView::~SupportedTypeListView()
247 {
248 }
249 
250 
251 void
252 SupportedTypeListView::MessageReceived(BMessage* message)
253 {
254 	if (message->WasDropped() && AcceptsDrag(message)) {
255 		// Add unique types
256 		entry_ref ref;
257 		for (int32 index = 0; message->FindRef("refs", index++, &ref) == B_OK; ) {
258 			BNode node(&ref);
259 			BNodeInfo info(&node);
260 			if (node.InitCheck() != B_OK || info.InitCheck() != B_OK)
261 				continue;
262 
263 			// TODO: we could identify the file in case it doesn't have a type...
264 			char type[B_MIME_TYPE_LENGTH];
265 			if (info.GetType(type) != B_OK)
266 				continue;
267 
268 			// check if that type is already in our list
269 			bool found = false;
270 			for (int32 i = CountItems(); i-- > 0;) {
271 				SupportedTypeItem* item = (SupportedTypeItem*)ItemAt(i);
272 				if (!strcmp(item->Text(), type)) {
273 					found = true;
274 					break;
275 				}
276 			}
277 
278 			if (!found) {
279 				// add type
280 				AddItem(new SupportedTypeItem(type));
281 			}
282 		}
283 
284 		SortItems(&SupportedTypeItem::Compare);
285 	} else
286 		DropTargetListView::MessageReceived(message);
287 }
288 
289 
290 bool
291 SupportedTypeListView::AcceptsDrag(const BMessage* message)
292 {
293 	type_code type;
294 	return message->GetInfo("refs", &type) == B_OK && type == B_REF_TYPE;
295 }
296 
297 
298 //	#pragma mark -
299 
300 
301 ApplicationTypeWindow::ApplicationTypeWindow(BPoint position,
302 	const BEntry& entry)
303 	:
304 	BWindow(BRect(0.0f, 0.0f, 250.0f, 340.0f).OffsetBySelf(position),
305 		B_TRANSLATE("Application type"), B_TITLED_WINDOW,
306 		B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS |
307 			B_FRAME_EVENTS | B_AUTO_UPDATE_SIZE_LIMITS),
308 	fChangedProperties(0)
309 {
310 	float padding = be_control_look->DefaultItemSpacing();
311 	BAlignment labelAlignment = be_control_look->DefaultLabelAlignment();
312 
313 	BMenuBar* menuBar = new BMenuBar((char*)NULL);
314 	menuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
315 
316 	BMenu* menu = new BMenu(B_TRANSLATE("File"));
317 	fSaveMenuItem = new BMenuItem(B_TRANSLATE("Save"),
318 		new BMessage(kMsgSave), 'S');
319 	fSaveMenuItem->SetEnabled(false);
320 	menu->AddItem(fSaveMenuItem);
321 	BMenuItem* item;
322 	menu->AddItem(item = new BMenuItem(
323 		B_TRANSLATE("Save into resource file" B_UTF8_ELLIPSIS), NULL));
324 	item->SetEnabled(false);
325 
326 	menu->AddSeparatorItem();
327 	menu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
328 		new BMessage(B_QUIT_REQUESTED), 'W', B_COMMAND_KEY));
329 	menuBar->AddItem(menu);
330 
331 	// Signature
332 
333 	fSignatureControl = new BTextControl("signature",
334 		B_TRANSLATE("Signature:"), NULL, new BMessage(kMsgSignatureChanged));
335 	fSignatureControl->SetModificationMessage(
336 		new BMessage(kMsgSignatureChanged));
337 
338 	// filter out invalid characters that can't be part of a MIME type name
339 	BTextView* textView = fSignatureControl->TextView();
340 	textView->SetMaxBytes(B_MIME_TYPE_LENGTH);
341 	const char* disallowedCharacters = "<>@,;:\"()[]?= ";
342 	for (int32 i = 0; disallowedCharacters[i]; i++) {
343 		textView->DisallowChar(disallowedCharacters[i]);
344 	}
345 
346 	// "Application Flags" group
347 
348 	BBox* flagsBox = new BBox("flagsBox");
349 
350 	fFlagsCheckBox = new BCheckBox("flags", B_TRANSLATE("Application flags"),
351 		new BMessage(kMsgToggleAppFlags));
352 	fFlagsCheckBox->SetValue(B_CONTROL_ON);
353 
354 	fSingleLaunchButton = new BRadioButton("single",
355 		B_TRANSLATE("Single launch"), new BMessage(kMsgAppFlagsChanged));
356 
357 	fMultipleLaunchButton = new BRadioButton("multiple",
358 		B_TRANSLATE("Multiple launch"), new BMessage(kMsgAppFlagsChanged));
359 
360 	fExclusiveLaunchButton = new BRadioButton("exclusive",
361 		B_TRANSLATE("Exclusive launch"), new BMessage(kMsgAppFlagsChanged));
362 
363 	fArgsOnlyCheckBox = new BCheckBox("args only", B_TRANSLATE("Args only"),
364 		new BMessage(kMsgAppFlagsChanged));
365 
366 	fBackgroundAppCheckBox = new BCheckBox("background",
367 		B_TRANSLATE("Background app"), new BMessage(kMsgAppFlagsChanged));
368 
369 	BLayoutBuilder::Grid<>(flagsBox, 0, 0)
370 		.SetInsets(padding, padding * 2, padding, padding)
371 		.Add(fSingleLaunchButton, 0, 0).Add(fArgsOnlyCheckBox, 1, 0)
372 		.Add(fMultipleLaunchButton, 0, 1).Add(fBackgroundAppCheckBox, 1, 1)
373 		.Add(fExclusiveLaunchButton, 0, 2);
374 	flagsBox->SetLabel(fFlagsCheckBox);
375 
376 	// "Icon" group
377 
378 	BBox* iconBox = new BBox("IconBox");
379 	iconBox->SetLabel(B_TRANSLATE("Icon"));
380 	fIconView = new IconView("icon");
381 	fIconView->SetModificationMessage(new BMessage(kMsgIconChanged));
382 	BLayoutBuilder::Group<>(iconBox, B_HORIZONTAL)
383 		.SetInsets(padding, padding * 2, padding, padding)
384 		.Add(fIconView);
385 
386 	// "Supported Types" group
387 
388 	BBox* typeBox = new BBox("typesBox");
389 	typeBox->SetLabel(B_TRANSLATE("Supported types"));
390 
391 	fTypeListView = new SupportedTypeListView("Suppported Types",
392 		B_SINGLE_SELECTION_LIST);
393 	fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected));
394 
395 	BScrollView* scrollView = new BScrollView("type scrollview", fTypeListView,
396 		B_FRAME_EVENTS | B_WILL_DRAW, false, true);
397 
398 	fAddTypeButton = new BButton("add type",
399 		B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddType));
400 
401 	fRemoveTypeButton = new BButton("remove type", B_TRANSLATE("Remove"),
402 		new BMessage(kMsgRemoveType));
403 
404 	fTypeIconView = new IconView("type icon");
405 	BGroupView* iconHolder = new BGroupView(B_HORIZONTAL);
406 	iconHolder->AddChild(fTypeIconView);
407 	fTypeIconView->SetModificationMessage(new BMessage(kMsgTypeIconsChanged));
408 
409 	BLayoutBuilder::Grid<>(typeBox, padding, padding)
410 		.SetInsets(padding, padding * 2, padding, padding)
411 		.Add(scrollView, 0, 0, 1, 4)
412 		.Add(fAddTypeButton, 1, 0, 1, 2)
413 		.Add(fRemoveTypeButton, 1, 2, 1, 2)
414 		.Add(iconHolder, 2, 1, 1, 2)
415 		.SetColumnWeight(0, 3)
416 		.SetColumnWeight(1, 2)
417 		.SetColumnWeight(2, 1);
418 
419 	iconHolder->SetExplicitAlignment(
420 		BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE));
421 
422 	// "Version Info" group
423 
424 	BBox* versionBox = new BBox("versionBox");
425 	versionBox->SetLabel(B_TRANSLATE("Version info"));
426 
427 	fMajorVersionControl = new BTextControl("version major",
428 		B_TRANSLATE("Version:"), NULL, new BMessage(kMsgVersionInfoChanged));
429 	_MakeNumberTextControl(fMajorVersionControl);
430 
431 	fMiddleVersionControl = new BTextControl("version middle", ".", NULL,
432 		new BMessage(kMsgVersionInfoChanged));
433 	_MakeNumberTextControl(fMiddleVersionControl);
434 
435 	fMinorVersionControl = new BTextControl("version minor", ".", NULL,
436 		new BMessage(kMsgVersionInfoChanged));
437 	_MakeNumberTextControl(fMinorVersionControl);
438 
439 	fVarietyMenu = new BPopUpMenu("variety", true, true);
440 	fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Development"),
441 		new BMessage(kMsgVersionInfoChanged)));
442 	fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Alpha"),
443 		new BMessage(kMsgVersionInfoChanged)));
444 	fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Beta"),
445 		new BMessage(kMsgVersionInfoChanged)));
446 	fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Gamma"),
447 		new BMessage(kMsgVersionInfoChanged)));
448 	item = new BMenuItem(B_TRANSLATE("Golden master"),
449 		new BMessage(kMsgVersionInfoChanged));
450 	fVarietyMenu->AddItem(item);
451 	item->SetMarked(true);
452 	fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Final"),
453 		new BMessage(kMsgVersionInfoChanged)));
454 
455 	BMenuField* varietyField = new BMenuField("", fVarietyMenu);
456 	fInternalVersionControl = new BTextControl("version internal", "/", NULL,
457 		new BMessage(kMsgVersionInfoChanged));
458 	fShortDescriptionControl = new BTextControl("short description",
459 		B_TRANSLATE("Short description:"), NULL,
460 		new BMessage(kMsgVersionInfoChanged));
461 
462 	// TODO: workaround for a GCC 4.1.0 bug? Or is that really what the standard says?
463 	version_info versionInfo;
464 	fShortDescriptionControl->TextView()->SetMaxBytes(
465 		sizeof(versionInfo.short_info));
466 
467 	BStringView* longLabel = new BStringView(NULL,
468 		B_TRANSLATE("Long description:"));
469 	longLabel->SetExplicitAlignment(labelAlignment);
470 	fLongDescriptionView = new TabFilteringTextView("long desc",
471 		kMsgVersionInfoChanged);
472 	fLongDescriptionView->SetMaxBytes(sizeof(versionInfo.long_info));
473 
474 	scrollView = new BScrollView("desc scrollview", fLongDescriptionView,
475 		B_FRAME_EVENTS | B_WILL_DRAW, false, true);
476 
477 	// TODO: remove workaround (bug #5678)
478 	BSize minScrollSize = scrollView->ScrollBar(B_VERTICAL)->MinSize();
479 	minScrollSize.width += fLongDescriptionView->MinSize().width;
480 	scrollView->SetExplicitMinSize(minScrollSize);
481 
482 	// Manually set a minimum size for the version text controls
483 	// TODO: the same does not work when applied to the layout items
484 	float width = be_plain_font->StringWidth("99") + 16;
485 	fMajorVersionControl->TextView()->SetExplicitMinSize(
486 		BSize(width, fMajorVersionControl->MinSize().height));
487 	fMiddleVersionControl->TextView()->SetExplicitMinSize(
488 		BSize(width, fMiddleVersionControl->MinSize().height));
489 	fMinorVersionControl->TextView()->SetExplicitMinSize(
490 		BSize(width, fMinorVersionControl->MinSize().height));
491 	fInternalVersionControl->TextView()->SetExplicitMinSize(
492 		BSize(width, fInternalVersionControl->MinSize().height));
493 
494 	BLayoutBuilder::Grid<>(versionBox, padding / 2, padding / 2)
495 		.SetInsets(padding, padding * 2, padding, padding)
496 		.AddTextControl(fMajorVersionControl, 0, 0)
497 		.Add(fMiddleVersionControl, 2, 0, 2)
498 		.Add(fMinorVersionControl, 4, 0, 2)
499 		.Add(varietyField, 6, 0, 3)
500 		.Add(fInternalVersionControl, 9, 0, 2)
501 		.AddTextControl(fShortDescriptionControl, 0, 1)
502 		.Add(longLabel, 0, 2)
503 		.Add(scrollView, 1, 2, 10, 3)
504 		.SetRowWeight(3, 3);
505 
506 	// put it all together
507 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
508 		.SetInsets(0, 0, 0, 0)
509 		.Add(menuBar)
510 		.AddGroup(B_VERTICAL, padding)
511 			.SetInsets(padding, padding, padding, padding)
512 			.Add(fSignatureControl)
513 			.AddGroup(B_HORIZONTAL, padding)
514 				.Add(flagsBox, 3)
515 				.Add(iconBox, 1)
516 				.End()
517 			.Add(typeBox)
518 			.Add(versionBox);
519 
520 	SetKeyMenuBar(menuBar);
521 
522 	fSignatureControl->MakeFocus(true);
523 	BMimeType::StartWatching(this);
524 	_SetTo(entry);
525 }
526 
527 
528 ApplicationTypeWindow::~ApplicationTypeWindow()
529 {
530 	BMimeType::StopWatching(this);
531 }
532 
533 
534 BString
535 ApplicationTypeWindow::_Title(const BEntry& entry)
536 {
537 	char name[B_FILE_NAME_LENGTH];
538 	if (entry.GetName(name) != B_OK)
539 		strcpy(name, "\"-\"");
540 
541 	BString title = B_TRANSLATE("%1 application type");
542 	title.ReplaceFirst("%1", name);
543 	return title;
544 }
545 
546 
547 void
548 ApplicationTypeWindow::_SetTo(const BEntry& entry)
549 {
550 	SetTitle(_Title(entry).String());
551 	fEntry = entry;
552 
553 	// Retrieve Info
554 
555 	BFile file(&entry, B_READ_ONLY);
556 	if (file.InitCheck() != B_OK)
557 		return;
558 
559 	BAppFileInfo info(&file);
560 	if (info.InitCheck() != B_OK)
561 		return;
562 
563 	char signature[B_MIME_TYPE_LENGTH];
564 	if (info.GetSignature(signature) != B_OK)
565 		signature[0] = '\0';
566 
567 	bool gotFlags = false;
568 	uint32 flags;
569 	if (info.GetAppFlags(&flags) == B_OK)
570 		gotFlags = true;
571 	else
572 		flags = B_MULTIPLE_LAUNCH;
573 
574 	version_info versionInfo;
575 	if (info.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND) != B_OK)
576 		memset(&versionInfo, 0, sizeof(version_info));
577 
578 	// Set Controls
579 
580 	fSignatureControl->SetModificationMessage(NULL);
581 	fSignatureControl->SetText(signature);
582 	fSignatureControl->SetModificationMessage(
583 		new BMessage(kMsgSignatureChanged));
584 
585 	// flags
586 
587 	switch (flags & (B_SINGLE_LAUNCH | B_MULTIPLE_LAUNCH | B_EXCLUSIVE_LAUNCH)) {
588 		case B_SINGLE_LAUNCH:
589 			fSingleLaunchButton->SetValue(B_CONTROL_ON);
590 			break;
591 
592 		case B_EXCLUSIVE_LAUNCH:
593 			fExclusiveLaunchButton->SetValue(B_CONTROL_ON);
594 			break;
595 
596 		case B_MULTIPLE_LAUNCH:
597 		default:
598 			fMultipleLaunchButton->SetValue(B_CONTROL_ON);
599 			break;
600 	}
601 
602 	fArgsOnlyCheckBox->SetValue((flags & B_ARGV_ONLY) != 0);
603 	fBackgroundAppCheckBox->SetValue((flags & B_BACKGROUND_APP) != 0);
604 	fFlagsCheckBox->SetValue(gotFlags);
605 
606 	_UpdateAppFlagsEnabled();
607 
608 	// icon
609 
610 	entry_ref ref;
611 	if (entry.GetRef(&ref) == B_OK)
612 		fIcon.SetTo(ref);
613 	else
614 		fIcon.Unset();
615 
616 	fIconView->SetModificationMessage(NULL);
617 	fIconView->SetTo(&fIcon);
618 	fIconView->SetModificationMessage(new BMessage(kMsgIconChanged));
619 
620 	// supported types
621 
622 	BMessage supportedTypes;
623 	info.GetSupportedTypes(&supportedTypes);
624 
625 	for (int32 i = fTypeListView->CountItems(); i-- > 0;) {
626 		BListItem* item = fTypeListView->RemoveItem(i);
627 		delete item;
628 	}
629 
630 	const char* type;
631 	for (int32 i = 0; supportedTypes.FindString("types", i, &type) == B_OK; i++) {
632 		SupportedTypeItem* item = new SupportedTypeItem(type);
633 
634 		entry_ref ref;
635 		if (fEntry.GetRef(&ref) == B_OK)
636 			item->SetIcon(ref, type);
637 
638 		fTypeListView->AddItem(item);
639 	}
640 	fTypeListView->SortItems(&SupportedTypeItem::Compare);
641 	fTypeIconView->SetModificationMessage(NULL);
642 	fTypeIconView->SetTo(NULL);
643 	fTypeIconView->SetModificationMessage(new BMessage(kMsgTypeIconsChanged));
644 	fTypeIconView->SetEnabled(false);
645 	fRemoveTypeButton->SetEnabled(false);
646 
647 	// version info
648 
649 	char text[256];
650 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.major);
651 	fMajorVersionControl->SetText(text);
652 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.middle);
653 	fMiddleVersionControl->SetText(text);
654 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.minor);
655 	fMinorVersionControl->SetText(text);
656 
657 	if (versionInfo.variety >= (uint32)fVarietyMenu->CountItems())
658 		versionInfo.variety = 0;
659 	BMenuItem* item = fVarietyMenu->ItemAt(versionInfo.variety);
660 	if (item != NULL)
661 		item->SetMarked(true);
662 
663 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.internal);
664 	fInternalVersionControl->SetText(text);
665 
666 	fShortDescriptionControl->SetText(versionInfo.short_info);
667 	fLongDescriptionView->SetText(versionInfo.long_info);
668 
669 	// store original data
670 
671 	fOriginalInfo.signature = signature;
672 	fOriginalInfo.gotFlags = gotFlags;
673 	fOriginalInfo.flags = gotFlags ? flags : 0;
674 	fOriginalInfo.versionInfo = versionInfo;
675 	fOriginalInfo.supportedTypes = _SupportedTypes();
676 		// The list view has the types sorted possibly differently
677 		// to the supportedTypes message, so don't use that here, but
678 		// get the sorted message instead.
679 	fOriginalInfo.iconChanged = false;
680 	fOriginalInfo.typeIconsChanged = false;
681 
682 	fChangedProperties = 0;
683 	_CheckSaveMenuItem(0);
684 }
685 
686 
687 void
688 ApplicationTypeWindow::_UpdateAppFlagsEnabled()
689 {
690 	bool enabled = fFlagsCheckBox->Value() != B_CONTROL_OFF;
691 
692 	fSingleLaunchButton->SetEnabled(enabled);
693 	fMultipleLaunchButton->SetEnabled(enabled);
694 	fExclusiveLaunchButton->SetEnabled(enabled);
695 	fArgsOnlyCheckBox->SetEnabled(enabled);
696 	fBackgroundAppCheckBox->SetEnabled(enabled);
697 }
698 
699 
700 void
701 ApplicationTypeWindow::_MakeNumberTextControl(BTextControl* control)
702 {
703 	// filter out invalid characters that can't be part of a MIME type name
704 	BTextView* textView = control->TextView();
705 	textView->SetMaxBytes(10);
706 
707 	for (int32 i = 0; i < 256; i++) {
708 		if (!isdigit(i))
709 			textView->DisallowChar(i);
710 	}
711 }
712 
713 
714 void
715 ApplicationTypeWindow::_Save()
716 {
717 	BFile file;
718 	status_t status = file.SetTo(&fEntry, B_READ_WRITE);
719 	if (status != B_OK)
720 		return;
721 
722 	BAppFileInfo info(&file);
723 	status = info.InitCheck();
724 	if (status != B_OK)
725 		return;
726 
727 	// Retrieve Info
728 
729 	uint32 flags = 0;
730 	bool gotFlags = _Flags(flags);
731 	BMessage supportedTypes = _SupportedTypes();
732 	version_info versionInfo = _VersionInfo();
733 
734 	// Save
735 
736 	status = info.SetSignature(fSignatureControl->Text());
737 	if (status == B_OK) {
738 		if (gotFlags)
739 			status = info.SetAppFlags(flags);
740 		else
741 			status = info.RemoveAppFlags();
742 	}
743 	if (status == B_OK)
744 		status = info.SetVersionInfo(&versionInfo, B_APP_VERSION_KIND);
745 	if (status == B_OK)
746 		fIcon.CopyTo(info, NULL, true);
747 
748 	// supported types and their icons
749 	if (status == B_OK)
750 		status = info.SetSupportedTypes(&supportedTypes);
751 
752 	for (int32 i = 0; i < fTypeListView->CountItems(); i++) {
753 		SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
754 			fTypeListView->ItemAt(i));
755 
756 		if (item != NULL)
757 			item->Icon().CopyTo(info, item->Type(), true);
758 	}
759 
760 	// reset the saved info
761 	fOriginalInfo.signature = fSignatureControl->Text();
762 	fOriginalInfo.gotFlags = gotFlags;
763 	fOriginalInfo.flags = flags;
764 	fOriginalInfo.versionInfo = versionInfo;
765 	fOriginalInfo.supportedTypes = supportedTypes;
766 	fOriginalInfo.iconChanged = false;
767 	fOriginalInfo.typeIconsChanged = false;
768 
769 	fChangedProperties = 0;
770 	_CheckSaveMenuItem(0);
771 }
772 
773 
774 void
775 ApplicationTypeWindow::_CheckSaveMenuItem(uint32 flags)
776 {
777 	fChangedProperties = _NeedsSaving(flags);
778 	fSaveMenuItem->SetEnabled(fChangedProperties != 0);
779 }
780 
781 
782 bool
783 operator!=(const version_info& a, const version_info& b)
784 {
785 	return a.major != b.major || a.middle != b.middle || a.minor != b.minor
786 		|| a.variety != b.variety || a.internal != b.internal
787 		|| strcmp(a.short_info, b.short_info) != 0
788 		|| strcmp(a.long_info, b.long_info) != 0;
789 }
790 
791 
792 uint32
793 ApplicationTypeWindow::_NeedsSaving(uint32 _flags) const
794 {
795 	uint32 flags = fChangedProperties;
796 	if (_flags & CHECK_SIGNATUR) {
797 		if (fOriginalInfo.signature != fSignatureControl->Text())
798 			flags |= CHECK_SIGNATUR;
799 		else
800 			flags &= ~CHECK_SIGNATUR;
801 	}
802 
803 	if (_flags & CHECK_FLAGS) {
804 		uint32 appFlags = 0;
805 		bool gotFlags = _Flags(appFlags);
806 		if (fOriginalInfo.gotFlags != gotFlags
807 			|| fOriginalInfo.flags != appFlags) {
808 			flags |= CHECK_FLAGS;
809 		} else
810 			flags &= ~CHECK_FLAGS;
811 	}
812 
813 	if (_flags & CHECK_VERSION) {
814 		if (fOriginalInfo.versionInfo != _VersionInfo())
815 			flags |= CHECK_VERSION;
816 		else
817 			flags &= ~CHECK_VERSION;
818 	}
819 
820 	if (_flags & CHECK_ICON) {
821 		if (fOriginalInfo.iconChanged)
822 			flags |= CHECK_ICON;
823 		else
824 			flags &= ~CHECK_ICON;
825 	}
826 
827 	if (_flags & CHECK_TYPES) {
828 		if (!fOriginalInfo.supportedTypes.HasSameData(_SupportedTypes()))
829 			flags |= CHECK_TYPES;
830 		else
831 			flags &= ~CHECK_TYPES;
832 	}
833 
834 	if (_flags & CHECK_TYPE_ICONS) {
835 		if (fOriginalInfo.typeIconsChanged)
836 			flags |= CHECK_TYPE_ICONS;
837 		else
838 			flags &= ~CHECK_TYPE_ICONS;
839 	}
840 
841 	return flags;
842 }
843 
844 
845 // #pragma mark -
846 
847 
848 bool
849 ApplicationTypeWindow::_Flags(uint32& flags) const
850 {
851 	flags = 0;
852 	if (fFlagsCheckBox->Value() != B_CONTROL_OFF) {
853 		if (fSingleLaunchButton->Value() != B_CONTROL_OFF)
854 			flags |= B_SINGLE_LAUNCH;
855 		else if (fMultipleLaunchButton->Value() != B_CONTROL_OFF)
856 			flags |= B_MULTIPLE_LAUNCH;
857 		else if (fExclusiveLaunchButton->Value() != B_CONTROL_OFF)
858 			flags |= B_EXCLUSIVE_LAUNCH;
859 
860 		if (fArgsOnlyCheckBox->Value() != B_CONTROL_OFF)
861 			flags |= B_ARGV_ONLY;
862 		if (fBackgroundAppCheckBox->Value() != B_CONTROL_OFF)
863 			flags |= B_BACKGROUND_APP;
864 		return true;
865 	}
866 	return false;
867 }
868 
869 
870 BMessage
871 ApplicationTypeWindow::_SupportedTypes() const
872 {
873 	BMessage supportedTypes;
874 	for (int32 i = 0; i < fTypeListView->CountItems(); i++) {
875 		SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
876 			fTypeListView->ItemAt(i));
877 
878 		if (item != NULL)
879 			supportedTypes.AddString("types", item->Type());
880 	}
881 	return supportedTypes;
882 }
883 
884 
885 version_info
886 ApplicationTypeWindow::_VersionInfo() const
887 {
888 	version_info versionInfo;
889 	versionInfo.major = atol(fMajorVersionControl->Text());
890 	versionInfo.middle = atol(fMiddleVersionControl->Text());
891 	versionInfo.minor = atol(fMinorVersionControl->Text());
892 	versionInfo.variety = fVarietyMenu->IndexOf(fVarietyMenu->FindMarked());
893 	versionInfo.internal = atol(fInternalVersionControl->Text());
894 	strlcpy(versionInfo.short_info, fShortDescriptionControl->Text(),
895 		sizeof(versionInfo.short_info));
896 	strlcpy(versionInfo.long_info, fLongDescriptionView->Text(),
897 		sizeof(versionInfo.long_info));
898 	return versionInfo;
899 }
900 
901 
902 // #pragma mark -
903 
904 
905 void
906 ApplicationTypeWindow::FrameResized(float width, float height)
907 {
908 	// This works around a flaw of BTextView
909 	fLongDescriptionView->SetTextRect(fLongDescriptionView->Bounds());
910 }
911 
912 
913 void
914 ApplicationTypeWindow::MessageReceived(BMessage* message)
915 {
916 	switch (message->what) {
917 		case kMsgToggleAppFlags:
918 			_UpdateAppFlagsEnabled();
919 			_CheckSaveMenuItem(CHECK_FLAGS);
920 			break;
921 
922 		case kMsgSignatureChanged:
923 			_CheckSaveMenuItem(CHECK_SIGNATUR);
924 			break;
925 
926 		case kMsgAppFlagsChanged:
927 			_CheckSaveMenuItem(CHECK_FLAGS);
928 			break;
929 
930 		case kMsgIconChanged:
931 			fOriginalInfo.iconChanged = true;
932 			_CheckSaveMenuItem(CHECK_ICON);
933 			break;
934 
935 		case kMsgTypeIconsChanged:
936 			fOriginalInfo.typeIconsChanged = true;
937 			_CheckSaveMenuItem(CHECK_TYPE_ICONS);
938 			break;
939 
940 		case kMsgVersionInfoChanged:
941 			_CheckSaveMenuItem(CHECK_VERSION);
942 			break;
943 
944 		case kMsgSave:
945 			_Save();
946 			break;
947 
948 		case kMsgTypeSelected:
949 		{
950 			int32 index;
951 			if (message->FindInt32("index", &index) == B_OK) {
952 				SupportedTypeItem* item
953 					= (SupportedTypeItem*)fTypeListView->ItemAt(index);
954 
955 				fTypeIconView->SetModificationMessage(NULL);
956 				fTypeIconView->SetTo(item != NULL ? &item->Icon() : NULL);
957 				fTypeIconView->SetModificationMessage(
958 					new BMessage(kMsgTypeIconsChanged));
959 				fTypeIconView->SetEnabled(item != NULL);
960 				fRemoveTypeButton->SetEnabled(item != NULL);
961 
962 				_CheckSaveMenuItem(CHECK_TYPES);
963 			}
964 			break;
965 		}
966 
967 		case kMsgAddType:
968 		{
969 			BWindow* window = new TypeListWindow(NULL,
970 				kMsgTypeAdded, this);
971 			window->Show();
972 			break;
973 		}
974 
975 		case kMsgTypeAdded:
976 		{
977 			const char* type;
978 			if (message->FindString("type", &type) != B_OK)
979 				break;
980 
981 			// check if this type already exists
982 
983 			SupportedTypeItem* newItem = new SupportedTypeItem(type);
984 			int32 insertAt = 0;
985 
986 			for (int32 i = fTypeListView->CountItems(); i-- > 0;) {
987 				SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
988 					fTypeListView->ItemAt(i));
989 				if (item == NULL)
990 					continue;
991 
992 				int compare = strcasecmp(item->Type(), type);
993 				if (!compare) {
994 					// type does already exist, select it and bail out
995 					delete newItem;
996 					newItem = NULL;
997 					fTypeListView->Select(i);
998 					break;
999 				}
1000 				if (compare < 0)
1001 					insertAt = i + 1;
1002 			}
1003 
1004 			if (newItem == NULL)
1005 				break;
1006 
1007 			fTypeListView->AddItem(newItem, insertAt);
1008 			fTypeListView->Select(insertAt);
1009 
1010 			_CheckSaveMenuItem(CHECK_TYPES);
1011 			break;
1012 		}
1013 
1014 		case kMsgRemoveType:
1015 		{
1016 			int32 index = fTypeListView->CurrentSelection();
1017 			if (index < 0)
1018 				break;
1019 
1020 			delete fTypeListView->RemoveItem(index);
1021 			fTypeIconView->SetModificationMessage(NULL);
1022 			fTypeIconView->SetTo(NULL);
1023 			fTypeIconView->SetModificationMessage(
1024 				new BMessage(kMsgTypeIconsChanged));
1025 			fTypeIconView->SetEnabled(false);
1026 			fRemoveTypeButton->SetEnabled(false);
1027 
1028 			_CheckSaveMenuItem(CHECK_TYPES);
1029 			break;
1030 		}
1031 
1032 		case B_SIMPLE_DATA:
1033 		{
1034 			entry_ref ref;
1035 			if (message->FindRef("refs", &ref) != B_OK)
1036 				break;
1037 
1038 			// TODO: add to supported types
1039 			break;
1040 		}
1041 
1042 		case B_META_MIME_CHANGED:
1043 			const char* type;
1044 			int32 which;
1045 			if (message->FindString("be:type", &type) != B_OK
1046 				|| message->FindInt32("be:which", &which) != B_OK)
1047 				break;
1048 
1049 			// TODO: update supported types names
1050 //			if (which == B_MIME_TYPE_DELETED)
1051 
1052 //			_CheckSaveMenuItem(...);
1053 			break;
1054 
1055 		default:
1056 			BWindow::MessageReceived(message);
1057 	}
1058 }
1059 
1060 
1061 bool
1062 ApplicationTypeWindow::QuitRequested()
1063 {
1064 	if (_NeedsSaving(CHECK_ALL) != 0) {
1065 		BAlert* alert = new BAlert(B_TRANSLATE("Save request"),
1066 			B_TRANSLATE("Save changes before closing?"),
1067 			B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"),
1068 			B_TRANSLATE("Save"), B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
1069 			B_WARNING_ALERT);
1070 		alert->SetShortcut(0, B_ESCAPE);
1071 		alert->SetShortcut(1, 'd');
1072 		alert->SetShortcut(2, 's');
1073 
1074 		int32 choice = alert->Go();
1075 		switch (choice) {
1076 			case 0:
1077 				return false;
1078 			case 1:
1079 				break;
1080 			case 2:
1081 				_Save();
1082 				break;
1083 		}
1084 	}
1085 
1086 	be_app->PostMessage(kMsgTypeWindowClosed);
1087 	return true;
1088 }
1089 
1090