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