xref: /haiku/src/preferences/filetypes/ApplicationTypeWindow.cpp (revision 1deede7388b04dbeec5af85cae7164735ea9e70d)
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, B_SIZE_UNSET));
487 	fMiddleVersionControl->TextView()->SetExplicitMinSize(
488 		BSize(width, B_SIZE_UNSET));
489 	fMinorVersionControl->TextView()->SetExplicitMinSize(
490 		BSize(width, B_SIZE_UNSET));
491 	fInternalVersionControl->TextView()->SetExplicitMinSize(
492 		BSize(width, B_SIZE_UNSET));
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 			B_ALIGN_HORIZONTAL_UNSET, 1, 10)
503 		.Add(longLabel, 0, 2)
504 		.Add(scrollView, 1, 2, 10, 3)
505 		.SetRowWeight(3, 3);
506 
507 	// put it all together
508 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
509 		.SetInsets(0, 0, 0, 0)
510 		.Add(menuBar)
511 		.AddGroup(B_VERTICAL, padding)
512 			.SetInsets(padding, padding, padding, padding)
513 			.Add(fSignatureControl)
514 			.AddGroup(B_HORIZONTAL, padding)
515 				.Add(flagsBox, 3)
516 				.Add(iconBox, 1)
517 				.End()
518 			.Add(typeBox)
519 			.Add(versionBox);
520 
521 	SetKeyMenuBar(menuBar);
522 
523 	fSignatureControl->MakeFocus(true);
524 	BMimeType::StartWatching(this);
525 	_SetTo(entry);
526 }
527 
528 
529 ApplicationTypeWindow::~ApplicationTypeWindow()
530 {
531 	BMimeType::StopWatching(this);
532 }
533 
534 
535 BString
536 ApplicationTypeWindow::_Title(const BEntry& entry)
537 {
538 	char name[B_FILE_NAME_LENGTH];
539 	if (entry.GetName(name) != B_OK)
540 		strcpy(name, "\"-\"");
541 
542 	BString title = B_TRANSLATE("%1 application type");
543 	title.ReplaceFirst("%1", name);
544 	return title;
545 }
546 
547 
548 void
549 ApplicationTypeWindow::_SetTo(const BEntry& entry)
550 {
551 	SetTitle(_Title(entry).String());
552 	fEntry = entry;
553 
554 	// Retrieve Info
555 
556 	BFile file(&entry, B_READ_ONLY);
557 	if (file.InitCheck() != B_OK)
558 		return;
559 
560 	BAppFileInfo info(&file);
561 	if (info.InitCheck() != B_OK)
562 		return;
563 
564 	char signature[B_MIME_TYPE_LENGTH];
565 	if (info.GetSignature(signature) != B_OK)
566 		signature[0] = '\0';
567 
568 	bool gotFlags = false;
569 	uint32 flags;
570 	if (info.GetAppFlags(&flags) == B_OK)
571 		gotFlags = true;
572 	else
573 		flags = B_MULTIPLE_LAUNCH;
574 
575 	version_info versionInfo;
576 	if (info.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND) != B_OK)
577 		memset(&versionInfo, 0, sizeof(version_info));
578 
579 	// Set Controls
580 
581 	fSignatureControl->SetModificationMessage(NULL);
582 	fSignatureControl->SetText(signature);
583 	fSignatureControl->SetModificationMessage(
584 		new BMessage(kMsgSignatureChanged));
585 
586 	// flags
587 
588 	switch (flags & (B_SINGLE_LAUNCH | B_MULTIPLE_LAUNCH | B_EXCLUSIVE_LAUNCH)) {
589 		case B_SINGLE_LAUNCH:
590 			fSingleLaunchButton->SetValue(B_CONTROL_ON);
591 			break;
592 
593 		case B_EXCLUSIVE_LAUNCH:
594 			fExclusiveLaunchButton->SetValue(B_CONTROL_ON);
595 			break;
596 
597 		case B_MULTIPLE_LAUNCH:
598 		default:
599 			fMultipleLaunchButton->SetValue(B_CONTROL_ON);
600 			break;
601 	}
602 
603 	fArgsOnlyCheckBox->SetValue((flags & B_ARGV_ONLY) != 0);
604 	fBackgroundAppCheckBox->SetValue((flags & B_BACKGROUND_APP) != 0);
605 	fFlagsCheckBox->SetValue(gotFlags);
606 
607 	_UpdateAppFlagsEnabled();
608 
609 	// icon
610 
611 	entry_ref ref;
612 	if (entry.GetRef(&ref) == B_OK)
613 		fIcon.SetTo(ref);
614 	else
615 		fIcon.Unset();
616 
617 	fIconView->SetModificationMessage(NULL);
618 	fIconView->SetTo(&fIcon);
619 	fIconView->SetModificationMessage(new BMessage(kMsgIconChanged));
620 
621 	// supported types
622 
623 	BMessage supportedTypes;
624 	info.GetSupportedTypes(&supportedTypes);
625 
626 	for (int32 i = fTypeListView->CountItems(); i-- > 0;) {
627 		BListItem* item = fTypeListView->RemoveItem(i);
628 		delete item;
629 	}
630 
631 	const char* type;
632 	for (int32 i = 0; supportedTypes.FindString("types", i, &type) == B_OK; i++) {
633 		SupportedTypeItem* item = new SupportedTypeItem(type);
634 
635 		entry_ref ref;
636 		if (fEntry.GetRef(&ref) == B_OK)
637 			item->SetIcon(ref, type);
638 
639 		fTypeListView->AddItem(item);
640 	}
641 	fTypeListView->SortItems(&SupportedTypeItem::Compare);
642 	fTypeIconView->SetModificationMessage(NULL);
643 	fTypeIconView->SetTo(NULL);
644 	fTypeIconView->SetModificationMessage(new BMessage(kMsgTypeIconsChanged));
645 	fTypeIconView->SetEnabled(false);
646 	fRemoveTypeButton->SetEnabled(false);
647 
648 	// version info
649 
650 	char text[256];
651 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.major);
652 	fMajorVersionControl->SetText(text);
653 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.middle);
654 	fMiddleVersionControl->SetText(text);
655 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.minor);
656 	fMinorVersionControl->SetText(text);
657 
658 	if (versionInfo.variety >= (uint32)fVarietyMenu->CountItems())
659 		versionInfo.variety = 0;
660 	BMenuItem* item = fVarietyMenu->ItemAt(versionInfo.variety);
661 	if (item != NULL)
662 		item->SetMarked(true);
663 
664 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.internal);
665 	fInternalVersionControl->SetText(text);
666 
667 	fShortDescriptionControl->SetText(versionInfo.short_info);
668 	fLongDescriptionView->SetText(versionInfo.long_info);
669 
670 	// store original data
671 
672 	fOriginalInfo.signature = signature;
673 	fOriginalInfo.gotFlags = gotFlags;
674 	fOriginalInfo.flags = gotFlags ? flags : 0;
675 	fOriginalInfo.versionInfo = versionInfo;
676 	fOriginalInfo.supportedTypes = _SupportedTypes();
677 		// The list view has the types sorted possibly differently
678 		// to the supportedTypes message, so don't use that here, but
679 		// get the sorted message instead.
680 	fOriginalInfo.iconChanged = false;
681 	fOriginalInfo.typeIconsChanged = false;
682 
683 	fChangedProperties = 0;
684 	_CheckSaveMenuItem(0);
685 }
686 
687 
688 void
689 ApplicationTypeWindow::_UpdateAppFlagsEnabled()
690 {
691 	bool enabled = fFlagsCheckBox->Value() != B_CONTROL_OFF;
692 
693 	fSingleLaunchButton->SetEnabled(enabled);
694 	fMultipleLaunchButton->SetEnabled(enabled);
695 	fExclusiveLaunchButton->SetEnabled(enabled);
696 	fArgsOnlyCheckBox->SetEnabled(enabled);
697 	fBackgroundAppCheckBox->SetEnabled(enabled);
698 }
699 
700 
701 void
702 ApplicationTypeWindow::_MakeNumberTextControl(BTextControl* control)
703 {
704 	// filter out invalid characters that can't be part of a MIME type name
705 	BTextView* textView = control->TextView();
706 	textView->SetMaxBytes(10);
707 
708 	for (int32 i = 0; i < 256; i++) {
709 		if (!isdigit(i))
710 			textView->DisallowChar(i);
711 	}
712 }
713 
714 
715 void
716 ApplicationTypeWindow::_Save()
717 {
718 	BFile file;
719 	status_t status = file.SetTo(&fEntry, B_READ_WRITE);
720 	if (status != B_OK)
721 		return;
722 
723 	BAppFileInfo info(&file);
724 	status = info.InitCheck();
725 	if (status != B_OK)
726 		return;
727 
728 	// Retrieve Info
729 
730 	uint32 flags = 0;
731 	bool gotFlags = _Flags(flags);
732 	BMessage supportedTypes = _SupportedTypes();
733 	version_info versionInfo = _VersionInfo();
734 
735 	// Save
736 
737 	status = info.SetSignature(fSignatureControl->Text());
738 	if (status == B_OK) {
739 		if (gotFlags)
740 			status = info.SetAppFlags(flags);
741 		else
742 			status = info.RemoveAppFlags();
743 	}
744 	if (status == B_OK)
745 		status = info.SetVersionInfo(&versionInfo, B_APP_VERSION_KIND);
746 	if (status == B_OK)
747 		fIcon.CopyTo(info, NULL, true);
748 
749 	// supported types and their icons
750 	if (status == B_OK)
751 		status = info.SetSupportedTypes(&supportedTypes);
752 
753 	for (int32 i = 0; i < fTypeListView->CountItems(); i++) {
754 		SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
755 			fTypeListView->ItemAt(i));
756 
757 		if (item != NULL)
758 			item->Icon().CopyTo(info, item->Type(), true);
759 	}
760 
761 	// reset the saved info
762 	fOriginalInfo.signature = fSignatureControl->Text();
763 	fOriginalInfo.gotFlags = gotFlags;
764 	fOriginalInfo.flags = flags;
765 	fOriginalInfo.versionInfo = versionInfo;
766 	fOriginalInfo.supportedTypes = supportedTypes;
767 	fOriginalInfo.iconChanged = false;
768 	fOriginalInfo.typeIconsChanged = false;
769 
770 	fChangedProperties = 0;
771 	_CheckSaveMenuItem(0);
772 }
773 
774 
775 void
776 ApplicationTypeWindow::_CheckSaveMenuItem(uint32 flags)
777 {
778 	fChangedProperties = _NeedsSaving(flags);
779 	fSaveMenuItem->SetEnabled(fChangedProperties != 0);
780 }
781 
782 
783 bool
784 operator!=(const version_info& a, const version_info& b)
785 {
786 	return a.major != b.major || a.middle != b.middle || a.minor != b.minor
787 		|| a.variety != b.variety || a.internal != b.internal
788 		|| strcmp(a.short_info, b.short_info) != 0
789 		|| strcmp(a.long_info, b.long_info) != 0;
790 }
791 
792 
793 uint32
794 ApplicationTypeWindow::_NeedsSaving(uint32 _flags) const
795 {
796 	uint32 flags = fChangedProperties;
797 	if (_flags & CHECK_SIGNATUR) {
798 		if (fOriginalInfo.signature != fSignatureControl->Text())
799 			flags |= CHECK_SIGNATUR;
800 		else
801 			flags &= ~CHECK_SIGNATUR;
802 	}
803 
804 	if (_flags & CHECK_FLAGS) {
805 		uint32 appFlags = 0;
806 		bool gotFlags = _Flags(appFlags);
807 		if (fOriginalInfo.gotFlags != gotFlags
808 			|| fOriginalInfo.flags != appFlags) {
809 			flags |= CHECK_FLAGS;
810 		} else
811 			flags &= ~CHECK_FLAGS;
812 	}
813 
814 	if (_flags & CHECK_VERSION) {
815 		if (fOriginalInfo.versionInfo != _VersionInfo())
816 			flags |= CHECK_VERSION;
817 		else
818 			flags &= ~CHECK_VERSION;
819 	}
820 
821 	if (_flags & CHECK_ICON) {
822 		if (fOriginalInfo.iconChanged)
823 			flags |= CHECK_ICON;
824 		else
825 			flags &= ~CHECK_ICON;
826 	}
827 
828 	if (_flags & CHECK_TYPES) {
829 		if (!fOriginalInfo.supportedTypes.HasSameData(_SupportedTypes()))
830 			flags |= CHECK_TYPES;
831 		else
832 			flags &= ~CHECK_TYPES;
833 	}
834 
835 	if (_flags & CHECK_TYPE_ICONS) {
836 		if (fOriginalInfo.typeIconsChanged)
837 			flags |= CHECK_TYPE_ICONS;
838 		else
839 			flags &= ~CHECK_TYPE_ICONS;
840 	}
841 
842 	return flags;
843 }
844 
845 
846 // #pragma mark -
847 
848 
849 bool
850 ApplicationTypeWindow::_Flags(uint32& flags) const
851 {
852 	flags = 0;
853 	if (fFlagsCheckBox->Value() != B_CONTROL_OFF) {
854 		if (fSingleLaunchButton->Value() != B_CONTROL_OFF)
855 			flags |= B_SINGLE_LAUNCH;
856 		else if (fMultipleLaunchButton->Value() != B_CONTROL_OFF)
857 			flags |= B_MULTIPLE_LAUNCH;
858 		else if (fExclusiveLaunchButton->Value() != B_CONTROL_OFF)
859 			flags |= B_EXCLUSIVE_LAUNCH;
860 
861 		if (fArgsOnlyCheckBox->Value() != B_CONTROL_OFF)
862 			flags |= B_ARGV_ONLY;
863 		if (fBackgroundAppCheckBox->Value() != B_CONTROL_OFF)
864 			flags |= B_BACKGROUND_APP;
865 		return true;
866 	}
867 	return false;
868 }
869 
870 
871 BMessage
872 ApplicationTypeWindow::_SupportedTypes() const
873 {
874 	BMessage supportedTypes;
875 	for (int32 i = 0; i < fTypeListView->CountItems(); i++) {
876 		SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
877 			fTypeListView->ItemAt(i));
878 
879 		if (item != NULL)
880 			supportedTypes.AddString("types", item->Type());
881 	}
882 	return supportedTypes;
883 }
884 
885 
886 version_info
887 ApplicationTypeWindow::_VersionInfo() const
888 {
889 	version_info versionInfo;
890 	versionInfo.major = atol(fMajorVersionControl->Text());
891 	versionInfo.middle = atol(fMiddleVersionControl->Text());
892 	versionInfo.minor = atol(fMinorVersionControl->Text());
893 	versionInfo.variety = fVarietyMenu->IndexOf(fVarietyMenu->FindMarked());
894 	versionInfo.internal = atol(fInternalVersionControl->Text());
895 	strlcpy(versionInfo.short_info, fShortDescriptionControl->Text(),
896 		sizeof(versionInfo.short_info));
897 	strlcpy(versionInfo.long_info, fLongDescriptionView->Text(),
898 		sizeof(versionInfo.long_info));
899 	return versionInfo;
900 }
901 
902 
903 // #pragma mark -
904 
905 
906 void
907 ApplicationTypeWindow::MessageReceived(BMessage* message)
908 {
909 	switch (message->what) {
910 		case kMsgToggleAppFlags:
911 			_UpdateAppFlagsEnabled();
912 			_CheckSaveMenuItem(CHECK_FLAGS);
913 			break;
914 
915 		case kMsgSignatureChanged:
916 			_CheckSaveMenuItem(CHECK_SIGNATUR);
917 			break;
918 
919 		case kMsgAppFlagsChanged:
920 			_CheckSaveMenuItem(CHECK_FLAGS);
921 			break;
922 
923 		case kMsgIconChanged:
924 			fOriginalInfo.iconChanged = true;
925 			_CheckSaveMenuItem(CHECK_ICON);
926 			break;
927 
928 		case kMsgTypeIconsChanged:
929 			fOriginalInfo.typeIconsChanged = true;
930 			_CheckSaveMenuItem(CHECK_TYPE_ICONS);
931 			break;
932 
933 		case kMsgVersionInfoChanged:
934 			_CheckSaveMenuItem(CHECK_VERSION);
935 			break;
936 
937 		case kMsgSave:
938 			_Save();
939 			break;
940 
941 		case kMsgTypeSelected:
942 		{
943 			int32 index;
944 			if (message->FindInt32("index", &index) == B_OK) {
945 				SupportedTypeItem* item
946 					= (SupportedTypeItem*)fTypeListView->ItemAt(index);
947 
948 				fTypeIconView->SetModificationMessage(NULL);
949 				fTypeIconView->SetTo(item != NULL ? &item->Icon() : NULL);
950 				fTypeIconView->SetModificationMessage(
951 					new BMessage(kMsgTypeIconsChanged));
952 				fTypeIconView->SetEnabled(item != NULL);
953 				fRemoveTypeButton->SetEnabled(item != NULL);
954 
955 				_CheckSaveMenuItem(CHECK_TYPES);
956 			}
957 			break;
958 		}
959 
960 		case kMsgAddType:
961 		{
962 			BWindow* window = new TypeListWindow(NULL,
963 				kMsgTypeAdded, this);
964 			window->Show();
965 			break;
966 		}
967 
968 		case kMsgTypeAdded:
969 		{
970 			const char* type;
971 			if (message->FindString("type", &type) != B_OK)
972 				break;
973 
974 			// check if this type already exists
975 
976 			SupportedTypeItem* newItem = new SupportedTypeItem(type);
977 			int32 insertAt = 0;
978 
979 			for (int32 i = fTypeListView->CountItems(); i-- > 0;) {
980 				SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
981 					fTypeListView->ItemAt(i));
982 				if (item == NULL)
983 					continue;
984 
985 				int compare = strcasecmp(item->Type(), type);
986 				if (!compare) {
987 					// type does already exist, select it and bail out
988 					delete newItem;
989 					newItem = NULL;
990 					fTypeListView->Select(i);
991 					break;
992 				}
993 				if (compare < 0)
994 					insertAt = i + 1;
995 			}
996 
997 			if (newItem == NULL)
998 				break;
999 
1000 			fTypeListView->AddItem(newItem, insertAt);
1001 			fTypeListView->Select(insertAt);
1002 
1003 			_CheckSaveMenuItem(CHECK_TYPES);
1004 			break;
1005 		}
1006 
1007 		case kMsgRemoveType:
1008 		{
1009 			int32 index = fTypeListView->CurrentSelection();
1010 			if (index < 0)
1011 				break;
1012 
1013 			delete fTypeListView->RemoveItem(index);
1014 			fTypeIconView->SetModificationMessage(NULL);
1015 			fTypeIconView->SetTo(NULL);
1016 			fTypeIconView->SetModificationMessage(
1017 				new BMessage(kMsgTypeIconsChanged));
1018 			fTypeIconView->SetEnabled(false);
1019 			fRemoveTypeButton->SetEnabled(false);
1020 
1021 			_CheckSaveMenuItem(CHECK_TYPES);
1022 			break;
1023 		}
1024 
1025 		case B_SIMPLE_DATA:
1026 		{
1027 			entry_ref ref;
1028 			if (message->FindRef("refs", &ref) != B_OK)
1029 				break;
1030 
1031 			// TODO: add to supported types
1032 			break;
1033 		}
1034 
1035 		case B_META_MIME_CHANGED:
1036 			const char* type;
1037 			int32 which;
1038 			if (message->FindString("be:type", &type) != B_OK
1039 				|| message->FindInt32("be:which", &which) != B_OK)
1040 				break;
1041 
1042 			// TODO: update supported types names
1043 //			if (which == B_MIME_TYPE_DELETED)
1044 
1045 //			_CheckSaveMenuItem(...);
1046 			break;
1047 
1048 		default:
1049 			BWindow::MessageReceived(message);
1050 	}
1051 }
1052 
1053 
1054 bool
1055 ApplicationTypeWindow::QuitRequested()
1056 {
1057 	if (_NeedsSaving(CHECK_ALL) != 0) {
1058 		BAlert* alert = new BAlert(B_TRANSLATE("Save request"),
1059 			B_TRANSLATE("Save changes before closing?"),
1060 			B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"),
1061 			B_TRANSLATE("Save"), B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
1062 			B_WARNING_ALERT);
1063 		alert->SetShortcut(0, B_ESCAPE);
1064 		alert->SetShortcut(1, 'd');
1065 		alert->SetShortcut(2, 's');
1066 
1067 		int32 choice = alert->Go();
1068 		switch (choice) {
1069 			case 0:
1070 				return false;
1071 			case 1:
1072 				break;
1073 			case 2:
1074 				_Save();
1075 				break;
1076 		}
1077 	}
1078 
1079 	be_app->PostMessage(kMsgTypeWindowClosed);
1080 	return true;
1081 }
1082 
1083