xref: /haiku/src/preferences/filetypes/ApplicationTypeWindow.cpp (revision 6f80a9801fedbe7355c4360bd204ba746ec3ec2d)
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(const BMessage& settings, const BEntry& entry)
302 	:
303 	BWindow(_Frame(settings), B_TRANSLATE("Application type"), B_TITLED_WINDOW,
304 		B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS | B_FRAME_EVENTS | B_AUTO_UPDATE_SIZE_LIMITS),
305 	fChangedProperties(0)
306 {
307 	float padding = be_control_look->DefaultItemSpacing();
308 	BAlignment labelAlignment = be_control_look->DefaultLabelAlignment();
309 
310 	BMenuBar* menuBar = new BMenuBar((char*)NULL);
311 	menuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
312 
313 	BMenu* menu = new BMenu(B_TRANSLATE("File"));
314 	fSaveMenuItem = new BMenuItem(B_TRANSLATE("Save"),
315 		new BMessage(kMsgSave), 'S');
316 	fSaveMenuItem->SetEnabled(false);
317 	menu->AddItem(fSaveMenuItem);
318 	BMenuItem* item;
319 	menu->AddItem(item = new BMenuItem(
320 		B_TRANSLATE("Save into resource file" B_UTF8_ELLIPSIS), NULL));
321 	item->SetEnabled(false);
322 
323 	menu->AddSeparatorItem();
324 	menu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
325 		new BMessage(B_QUIT_REQUESTED), 'W', B_COMMAND_KEY));
326 	menuBar->AddItem(menu);
327 
328 	// Signature
329 
330 	fSignatureControl = new BTextControl("signature",
331 		B_TRANSLATE("Signature:"), NULL, new BMessage(kMsgSignatureChanged));
332 	fSignatureControl->SetModificationMessage(
333 		new BMessage(kMsgSignatureChanged));
334 
335 	// filter out invalid characters that can't be part of a MIME type name
336 	BTextView* textView = fSignatureControl->TextView();
337 	textView->SetMaxBytes(B_MIME_TYPE_LENGTH);
338 	const char* disallowedCharacters = "<>@,;:\"()[]?= ";
339 	for (int32 i = 0; disallowedCharacters[i]; i++) {
340 		textView->DisallowChar(disallowedCharacters[i]);
341 	}
342 
343 	// "Application Flags" group
344 
345 	BBox* flagsBox = new BBox("flagsBox");
346 
347 	fFlagsCheckBox = new BCheckBox("flags", B_TRANSLATE("Application flags"),
348 		new BMessage(kMsgToggleAppFlags));
349 	fFlagsCheckBox->SetValue(B_CONTROL_ON);
350 
351 	fSingleLaunchButton = new BRadioButton("single",
352 		B_TRANSLATE("Single launch"), new BMessage(kMsgAppFlagsChanged));
353 
354 	fMultipleLaunchButton = new BRadioButton("multiple",
355 		B_TRANSLATE("Multiple launch"), new BMessage(kMsgAppFlagsChanged));
356 
357 	fExclusiveLaunchButton = new BRadioButton("exclusive",
358 		B_TRANSLATE("Exclusive launch"), new BMessage(kMsgAppFlagsChanged));
359 
360 	fArgsOnlyCheckBox = new BCheckBox("args only", B_TRANSLATE("Args only"),
361 		new BMessage(kMsgAppFlagsChanged));
362 
363 	fBackgroundAppCheckBox = new BCheckBox("background",
364 		B_TRANSLATE("Background app"), new BMessage(kMsgAppFlagsChanged));
365 
366 	BLayoutBuilder::Grid<>(flagsBox, 0, 0)
367 		.SetInsets(padding, padding * 2, padding, padding)
368 		.Add(fSingleLaunchButton, 0, 0).Add(fArgsOnlyCheckBox, 1, 0)
369 		.Add(fMultipleLaunchButton, 0, 1).Add(fBackgroundAppCheckBox, 1, 1)
370 		.Add(fExclusiveLaunchButton, 0, 2);
371 	flagsBox->SetLabel(fFlagsCheckBox);
372 
373 	// "Icon" group
374 
375 	BBox* iconBox = new BBox("IconBox");
376 	iconBox->SetLabel(B_TRANSLATE("Icon"));
377 	fIconView = new IconView("icon");
378 	fIconView->SetModificationMessage(new BMessage(kMsgIconChanged));
379 	BLayoutBuilder::Group<>(iconBox, B_HORIZONTAL)
380 		.SetInsets(padding, padding * 2, padding, padding)
381 		.Add(fIconView);
382 
383 	// "Supported Types" group
384 
385 	BBox* typeBox = new BBox("typesBox");
386 	typeBox->SetLabel(B_TRANSLATE("Supported types"));
387 
388 	fTypeListView = new SupportedTypeListView("Suppported Types",
389 		B_SINGLE_SELECTION_LIST);
390 	fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected));
391 
392 	BScrollView* scrollView = new BScrollView("type scrollview", fTypeListView,
393 		B_FRAME_EVENTS | B_WILL_DRAW, false, true);
394 
395 	fAddTypeButton = new BButton("add type",
396 		B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddType));
397 
398 	fRemoveTypeButton = new BButton("remove type", B_TRANSLATE("Remove"),
399 		new BMessage(kMsgRemoveType));
400 
401 	fTypeIconView = new IconView("type icon");
402 	BGroupView* iconHolder = new BGroupView(B_HORIZONTAL);
403 	iconHolder->AddChild(fTypeIconView);
404 	fTypeIconView->SetModificationMessage(new BMessage(kMsgTypeIconsChanged));
405 
406 	BLayoutBuilder::Grid<>(typeBox, padding, padding)
407 		.SetInsets(padding, padding * 2, padding, padding)
408 		.Add(scrollView, 0, 0, 1, 5)
409 		.Add(fAddTypeButton, 1, 0, 1, 2)
410 		.Add(fRemoveTypeButton, 1, 2, 1, 2)
411 		.Add(iconHolder, 2, 1, 1, 2)
412 		.SetColumnWeight(0, 3)
413 		.SetColumnWeight(1, 2)
414 		.SetColumnWeight(2, 1);
415 
416 	iconHolder->SetExplicitAlignment(
417 		BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE));
418 
419 	// "Version Info" group
420 
421 	BBox* versionBox = new BBox("versionBox");
422 	versionBox->SetLabel(B_TRANSLATE("Version info"));
423 
424 	fMajorVersionControl = new BTextControl("version major",
425 		B_TRANSLATE("Version:"), NULL, new BMessage(kMsgVersionInfoChanged));
426 	_MakeNumberTextControl(fMajorVersionControl);
427 
428 	fMiddleVersionControl = new BTextControl("version middle", ".", NULL,
429 		new BMessage(kMsgVersionInfoChanged));
430 	_MakeNumberTextControl(fMiddleVersionControl);
431 
432 	fMinorVersionControl = new BTextControl("version minor", ".", NULL,
433 		new BMessage(kMsgVersionInfoChanged));
434 	_MakeNumberTextControl(fMinorVersionControl);
435 
436 	fVarietyMenu = new BPopUpMenu("variety", true, true);
437 	fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Development"),
438 		new BMessage(kMsgVersionInfoChanged)));
439 	fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Alpha"),
440 		new BMessage(kMsgVersionInfoChanged)));
441 	fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Beta"),
442 		new BMessage(kMsgVersionInfoChanged)));
443 	fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Gamma"),
444 		new BMessage(kMsgVersionInfoChanged)));
445 	item = new BMenuItem(B_TRANSLATE("Golden master"),
446 		new BMessage(kMsgVersionInfoChanged));
447 	fVarietyMenu->AddItem(item);
448 	item->SetMarked(true);
449 	fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Final"),
450 		new BMessage(kMsgVersionInfoChanged)));
451 
452 	BMenuField* varietyField = new BMenuField("", fVarietyMenu);
453 	fInternalVersionControl = new BTextControl("version internal", "/", NULL,
454 		new BMessage(kMsgVersionInfoChanged));
455 	fShortDescriptionControl = new BTextControl("short description",
456 		B_TRANSLATE("Short description:"), NULL,
457 		new BMessage(kMsgVersionInfoChanged));
458 
459 	// TODO: workaround for a GCC 4.1.0 bug? Or is that really what the standard says?
460 	version_info versionInfo;
461 	fShortDescriptionControl->TextView()->SetMaxBytes(
462 		sizeof(versionInfo.short_info));
463 
464 	BStringView* longLabel = new BStringView(NULL,
465 		B_TRANSLATE("Long description:"));
466 	longLabel->SetExplicitAlignment(labelAlignment);
467 	fLongDescriptionView = new TabFilteringTextView("long desc",
468 		kMsgVersionInfoChanged);
469 	fLongDescriptionView->SetMaxBytes(sizeof(versionInfo.long_info));
470 
471 	scrollView = new BScrollView("desc scrollview", fLongDescriptionView,
472 		B_FRAME_EVENTS | B_WILL_DRAW, false, true);
473 
474 	// TODO: remove workaround (bug #5678)
475 	BSize minScrollSize = scrollView->ScrollBar(B_VERTICAL)->MinSize();
476 	minScrollSize.width += fLongDescriptionView->MinSize().width;
477 	scrollView->SetExplicitMinSize(minScrollSize);
478 
479 	// Manually set a minimum size for the version text controls
480 	// TODO: the same does not work when applied to the layout items
481 	float width = be_plain_font->StringWidth("99") + 16;
482 	fMajorVersionControl->TextView()->SetExplicitMinSize(
483 		BSize(width, B_SIZE_UNSET));
484 	fMiddleVersionControl->TextView()->SetExplicitMinSize(
485 		BSize(width, B_SIZE_UNSET));
486 	fMinorVersionControl->TextView()->SetExplicitMinSize(
487 		BSize(width, B_SIZE_UNSET));
488 	fInternalVersionControl->TextView()->SetExplicitMinSize(
489 		BSize(width, B_SIZE_UNSET));
490 
491 	BLayoutBuilder::Grid<>(versionBox, padding / 2, padding / 2)
492 		.SetInsets(padding, padding * 2, padding, padding)
493 		.AddTextControl(fMajorVersionControl, 0, 0)
494 		.Add(fMiddleVersionControl, 2, 0, 2)
495 		.Add(fMinorVersionControl, 4, 0, 2)
496 		.Add(varietyField, 6, 0, 3)
497 		.Add(fInternalVersionControl, 9, 0, 2)
498 		.AddTextControl(fShortDescriptionControl, 0, 1,
499 			B_ALIGN_HORIZONTAL_UNSET, 1, 10)
500 		.Add(longLabel, 0, 2)
501 		.Add(scrollView, 1, 2, 10, 3)
502 		.SetRowWeight(3, 3);
503 
504 	// put it all together
505 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
506 		.SetInsets(0, 0, 0, 0)
507 		.Add(menuBar)
508 		.AddGroup(B_VERTICAL, padding)
509 			.SetInsets(padding, padding, padding, padding)
510 			.Add(fSignatureControl)
511 			.AddGroup(B_HORIZONTAL, padding)
512 				.Add(flagsBox, 3)
513 				.Add(iconBox, 1)
514 				.End()
515 			.Add(typeBox, 2)
516 			.Add(versionBox);
517 
518 	SetKeyMenuBar(menuBar);
519 
520 	fSignatureControl->MakeFocus(true);
521 	BMimeType::StartWatching(this);
522 	_SetTo(entry);
523 
524 	Layout(false);
525 }
526 
527 
528 ApplicationTypeWindow::~ApplicationTypeWindow()
529 {
530 	BMimeType::StopWatching(this);
531 }
532 
533 BRect
534 ApplicationTypeWindow::_Frame(const BMessage& settings) const
535 {
536 	BRect rect;
537 	if (settings.FindRect("app_type_next_frame", &rect) == B_OK)
538 		return rect;
539 
540 	return BRect(100.0f, 110.0f, 250.0f, 340.0f);
541 }
542 
543 BString
544 ApplicationTypeWindow::_Title(const BEntry& entry)
545 {
546 	char name[B_FILE_NAME_LENGTH];
547 	if (entry.GetName(name) != B_OK)
548 		strcpy(name, "\"-\"");
549 
550 	BString title = B_TRANSLATE("%1 application type");
551 	title.ReplaceFirst("%1", name);
552 	return title;
553 }
554 
555 
556 void
557 ApplicationTypeWindow::_SetTo(const BEntry& entry)
558 {
559 	SetTitle(_Title(entry).String());
560 	fEntry = entry;
561 
562 	// Retrieve Info
563 
564 	BFile file(&entry, B_READ_ONLY);
565 	if (file.InitCheck() != B_OK)
566 		return;
567 
568 	BAppFileInfo info(&file);
569 	if (info.InitCheck() != B_OK)
570 		return;
571 
572 	char signature[B_MIME_TYPE_LENGTH];
573 	if (info.GetSignature(signature) != B_OK)
574 		signature[0] = '\0';
575 
576 	bool gotFlags = false;
577 	uint32 flags;
578 	if (info.GetAppFlags(&flags) == B_OK)
579 		gotFlags = true;
580 	else
581 		flags = B_MULTIPLE_LAUNCH;
582 
583 	version_info versionInfo;
584 	if (info.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND) != B_OK)
585 		memset(&versionInfo, 0, sizeof(version_info));
586 
587 	// Set Controls
588 
589 	fSignatureControl->SetModificationMessage(NULL);
590 	fSignatureControl->SetText(signature);
591 	fSignatureControl->SetModificationMessage(
592 		new BMessage(kMsgSignatureChanged));
593 
594 	// flags
595 
596 	switch (flags & (B_SINGLE_LAUNCH | B_MULTIPLE_LAUNCH | B_EXCLUSIVE_LAUNCH)) {
597 		case B_SINGLE_LAUNCH:
598 			fSingleLaunchButton->SetValue(B_CONTROL_ON);
599 			break;
600 
601 		case B_EXCLUSIVE_LAUNCH:
602 			fExclusiveLaunchButton->SetValue(B_CONTROL_ON);
603 			break;
604 
605 		case B_MULTIPLE_LAUNCH:
606 		default:
607 			fMultipleLaunchButton->SetValue(B_CONTROL_ON);
608 			break;
609 	}
610 
611 	fArgsOnlyCheckBox->SetValue((flags & B_ARGV_ONLY) != 0);
612 	fBackgroundAppCheckBox->SetValue((flags & B_BACKGROUND_APP) != 0);
613 	fFlagsCheckBox->SetValue(gotFlags);
614 
615 	_UpdateAppFlagsEnabled();
616 
617 	// icon
618 
619 	entry_ref ref;
620 	if (entry.GetRef(&ref) == B_OK)
621 		fIcon.SetTo(ref);
622 	else
623 		fIcon.Unset();
624 
625 	fIconView->SetModificationMessage(NULL);
626 	fIconView->SetTo(&fIcon);
627 	fIconView->SetModificationMessage(new BMessage(kMsgIconChanged));
628 
629 	// supported types
630 
631 	BMessage supportedTypes;
632 	info.GetSupportedTypes(&supportedTypes);
633 
634 	for (int32 i = fTypeListView->CountItems(); i-- > 0;) {
635 		BListItem* item = fTypeListView->RemoveItem(i);
636 		delete item;
637 	}
638 
639 	const char* type;
640 	for (int32 i = 0; supportedTypes.FindString("types", i, &type) == B_OK; i++) {
641 		SupportedTypeItem* item = new SupportedTypeItem(type);
642 
643 		entry_ref ref;
644 		if (fEntry.GetRef(&ref) == B_OK)
645 			item->SetIcon(ref, type);
646 
647 		fTypeListView->AddItem(item);
648 	}
649 	fTypeListView->SortItems(&SupportedTypeItem::Compare);
650 	fTypeIconView->SetModificationMessage(NULL);
651 	fTypeIconView->SetTo(NULL);
652 	fTypeIconView->SetModificationMessage(new BMessage(kMsgTypeIconsChanged));
653 	fTypeIconView->SetEnabled(false);
654 	fRemoveTypeButton->SetEnabled(false);
655 
656 	// version info
657 
658 	char text[256];
659 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.major);
660 	fMajorVersionControl->SetText(text);
661 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.middle);
662 	fMiddleVersionControl->SetText(text);
663 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.minor);
664 	fMinorVersionControl->SetText(text);
665 
666 	if (versionInfo.variety >= (uint32)fVarietyMenu->CountItems())
667 		versionInfo.variety = 0;
668 	BMenuItem* item = fVarietyMenu->ItemAt(versionInfo.variety);
669 	if (item != NULL)
670 		item->SetMarked(true);
671 
672 	snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.internal);
673 	fInternalVersionControl->SetText(text);
674 
675 	fShortDescriptionControl->SetText(versionInfo.short_info);
676 	fLongDescriptionView->SetText(versionInfo.long_info);
677 
678 	// store original data
679 
680 	fOriginalInfo.signature = signature;
681 	fOriginalInfo.gotFlags = gotFlags;
682 	fOriginalInfo.flags = gotFlags ? flags : 0;
683 	fOriginalInfo.versionInfo = versionInfo;
684 	fOriginalInfo.supportedTypes = _SupportedTypes();
685 		// The list view has the types sorted possibly differently
686 		// to the supportedTypes message, so don't use that here, but
687 		// get the sorted message instead.
688 	fOriginalInfo.iconChanged = false;
689 	fOriginalInfo.typeIconsChanged = false;
690 
691 	fChangedProperties = 0;
692 	_CheckSaveMenuItem(0);
693 }
694 
695 
696 void
697 ApplicationTypeWindow::_UpdateAppFlagsEnabled()
698 {
699 	bool enabled = fFlagsCheckBox->Value() != B_CONTROL_OFF;
700 
701 	fSingleLaunchButton->SetEnabled(enabled);
702 	fMultipleLaunchButton->SetEnabled(enabled);
703 	fExclusiveLaunchButton->SetEnabled(enabled);
704 	fArgsOnlyCheckBox->SetEnabled(enabled);
705 	fBackgroundAppCheckBox->SetEnabled(enabled);
706 }
707 
708 
709 void
710 ApplicationTypeWindow::_MakeNumberTextControl(BTextControl* control)
711 {
712 	// filter out invalid characters that can't be part of a MIME type name
713 	BTextView* textView = control->TextView();
714 	textView->SetMaxBytes(10);
715 
716 	for (int32 i = 0; i < 256; i++) {
717 		if (!isdigit(i))
718 			textView->DisallowChar(i);
719 	}
720 }
721 
722 
723 void
724 ApplicationTypeWindow::_Save()
725 {
726 	BFile file;
727 	status_t status = file.SetTo(&fEntry, B_READ_WRITE);
728 	if (status != B_OK)
729 		return;
730 
731 	BAppFileInfo info(&file);
732 	status = info.InitCheck();
733 	if (status != B_OK)
734 		return;
735 
736 	// Retrieve Info
737 
738 	uint32 flags = 0;
739 	bool gotFlags = _Flags(flags);
740 	BMessage supportedTypes = _SupportedTypes();
741 	version_info versionInfo = _VersionInfo();
742 
743 	// Save
744 
745 	status = info.SetSignature(fSignatureControl->Text());
746 	if (status == B_OK) {
747 		if (gotFlags)
748 			status = info.SetAppFlags(flags);
749 		else
750 			status = info.RemoveAppFlags();
751 	}
752 	if (status == B_OK)
753 		status = info.SetVersionInfo(&versionInfo, B_APP_VERSION_KIND);
754 	if (status == B_OK)
755 		fIcon.CopyTo(info, NULL, true);
756 
757 	// supported types and their icons
758 	if (status == B_OK)
759 		status = info.SetSupportedTypes(&supportedTypes);
760 
761 	for (int32 i = 0; i < fTypeListView->CountItems(); i++) {
762 		SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
763 			fTypeListView->ItemAt(i));
764 
765 		if (item != NULL)
766 			item->Icon().CopyTo(info, item->Type(), true);
767 	}
768 
769 	// reset the saved info
770 	fOriginalInfo.signature = fSignatureControl->Text();
771 	fOriginalInfo.gotFlags = gotFlags;
772 	fOriginalInfo.flags = flags;
773 	fOriginalInfo.versionInfo = versionInfo;
774 	fOriginalInfo.supportedTypes = supportedTypes;
775 	fOriginalInfo.iconChanged = false;
776 	fOriginalInfo.typeIconsChanged = false;
777 
778 	fChangedProperties = 0;
779 	_CheckSaveMenuItem(0);
780 }
781 
782 
783 void
784 ApplicationTypeWindow::_CheckSaveMenuItem(uint32 flags)
785 {
786 	fChangedProperties = _NeedsSaving(flags);
787 	fSaveMenuItem->SetEnabled(fChangedProperties != 0);
788 }
789 
790 
791 bool
792 operator!=(const version_info& a, const version_info& b)
793 {
794 	return a.major != b.major || a.middle != b.middle || a.minor != b.minor
795 		|| a.variety != b.variety || a.internal != b.internal
796 		|| strcmp(a.short_info, b.short_info) != 0
797 		|| strcmp(a.long_info, b.long_info) != 0;
798 }
799 
800 
801 uint32
802 ApplicationTypeWindow::_NeedsSaving(uint32 _flags) const
803 {
804 	uint32 flags = fChangedProperties;
805 	if (_flags & CHECK_SIGNATUR) {
806 		if (fOriginalInfo.signature != fSignatureControl->Text())
807 			flags |= CHECK_SIGNATUR;
808 		else
809 			flags &= ~CHECK_SIGNATUR;
810 	}
811 
812 	if (_flags & CHECK_FLAGS) {
813 		uint32 appFlags = 0;
814 		bool gotFlags = _Flags(appFlags);
815 		if (fOriginalInfo.gotFlags != gotFlags
816 			|| fOriginalInfo.flags != appFlags) {
817 			flags |= CHECK_FLAGS;
818 		} else
819 			flags &= ~CHECK_FLAGS;
820 	}
821 
822 	if (_flags & CHECK_VERSION) {
823 		if (fOriginalInfo.versionInfo != _VersionInfo())
824 			flags |= CHECK_VERSION;
825 		else
826 			flags &= ~CHECK_VERSION;
827 	}
828 
829 	if (_flags & CHECK_ICON) {
830 		if (fOriginalInfo.iconChanged)
831 			flags |= CHECK_ICON;
832 		else
833 			flags &= ~CHECK_ICON;
834 	}
835 
836 	if (_flags & CHECK_TYPES) {
837 		if (!fOriginalInfo.supportedTypes.HasSameData(_SupportedTypes()))
838 			flags |= CHECK_TYPES;
839 		else
840 			flags &= ~CHECK_TYPES;
841 	}
842 
843 	if (_flags & CHECK_TYPE_ICONS) {
844 		if (fOriginalInfo.typeIconsChanged)
845 			flags |= CHECK_TYPE_ICONS;
846 		else
847 			flags &= ~CHECK_TYPE_ICONS;
848 	}
849 
850 	return flags;
851 }
852 
853 
854 // #pragma mark -
855 
856 
857 bool
858 ApplicationTypeWindow::_Flags(uint32& flags) const
859 {
860 	flags = 0;
861 	if (fFlagsCheckBox->Value() != B_CONTROL_OFF) {
862 		if (fSingleLaunchButton->Value() != B_CONTROL_OFF)
863 			flags |= B_SINGLE_LAUNCH;
864 		else if (fMultipleLaunchButton->Value() != B_CONTROL_OFF)
865 			flags |= B_MULTIPLE_LAUNCH;
866 		else if (fExclusiveLaunchButton->Value() != B_CONTROL_OFF)
867 			flags |= B_EXCLUSIVE_LAUNCH;
868 
869 		if (fArgsOnlyCheckBox->Value() != B_CONTROL_OFF)
870 			flags |= B_ARGV_ONLY;
871 		if (fBackgroundAppCheckBox->Value() != B_CONTROL_OFF)
872 			flags |= B_BACKGROUND_APP;
873 		return true;
874 	}
875 	return false;
876 }
877 
878 
879 BMessage
880 ApplicationTypeWindow::_SupportedTypes() const
881 {
882 	BMessage supportedTypes;
883 	for (int32 i = 0; i < fTypeListView->CountItems(); i++) {
884 		SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
885 			fTypeListView->ItemAt(i));
886 
887 		if (item != NULL)
888 			supportedTypes.AddString("types", item->Type());
889 	}
890 	return supportedTypes;
891 }
892 
893 
894 version_info
895 ApplicationTypeWindow::_VersionInfo() const
896 {
897 	version_info versionInfo;
898 	versionInfo.major = atol(fMajorVersionControl->Text());
899 	versionInfo.middle = atol(fMiddleVersionControl->Text());
900 	versionInfo.minor = atol(fMinorVersionControl->Text());
901 	versionInfo.variety = fVarietyMenu->IndexOf(fVarietyMenu->FindMarked());
902 	versionInfo.internal = atol(fInternalVersionControl->Text());
903 	strlcpy(versionInfo.short_info, fShortDescriptionControl->Text(),
904 		sizeof(versionInfo.short_info));
905 	strlcpy(versionInfo.long_info, fLongDescriptionView->Text(),
906 		sizeof(versionInfo.long_info));
907 	return versionInfo;
908 }
909 
910 
911 // #pragma mark -
912 
913 
914 void
915 ApplicationTypeWindow::MessageReceived(BMessage* message)
916 {
917 	switch (message->what) {
918 		case kMsgToggleAppFlags:
919 			_UpdateAppFlagsEnabled();
920 			_CheckSaveMenuItem(CHECK_FLAGS);
921 			break;
922 
923 		case kMsgSignatureChanged:
924 			_CheckSaveMenuItem(CHECK_SIGNATUR);
925 			break;
926 
927 		case kMsgAppFlagsChanged:
928 			_CheckSaveMenuItem(CHECK_FLAGS);
929 			break;
930 
931 		case kMsgIconChanged:
932 			fOriginalInfo.iconChanged = true;
933 			_CheckSaveMenuItem(CHECK_ICON);
934 			break;
935 
936 		case kMsgTypeIconsChanged:
937 			fOriginalInfo.typeIconsChanged = true;
938 			_CheckSaveMenuItem(CHECK_TYPE_ICONS);
939 			break;
940 
941 		case kMsgVersionInfoChanged:
942 			_CheckSaveMenuItem(CHECK_VERSION);
943 			break;
944 
945 		case kMsgSave:
946 			_Save();
947 			break;
948 
949 		case kMsgTypeSelected:
950 		{
951 			int32 index;
952 			if (message->FindInt32("index", &index) == B_OK) {
953 				SupportedTypeItem* item
954 					= (SupportedTypeItem*)fTypeListView->ItemAt(index);
955 
956 				fTypeIconView->SetModificationMessage(NULL);
957 				fTypeIconView->SetTo(item != NULL ? &item->Icon() : NULL);
958 				fTypeIconView->SetModificationMessage(
959 					new BMessage(kMsgTypeIconsChanged));
960 				fTypeIconView->SetEnabled(item != NULL);
961 				fRemoveTypeButton->SetEnabled(item != NULL);
962 
963 				_CheckSaveMenuItem(CHECK_TYPES);
964 			}
965 			break;
966 		}
967 
968 		case kMsgAddType:
969 		{
970 			BWindow* window = new TypeListWindow(NULL,
971 				kMsgTypeAdded, this);
972 			window->Show();
973 			break;
974 		}
975 
976 		case kMsgTypeAdded:
977 		{
978 			const char* type;
979 			if (message->FindString("type", &type) != B_OK)
980 				break;
981 
982 			// check if this type already exists
983 
984 			SupportedTypeItem* newItem = new SupportedTypeItem(type);
985 			int32 insertAt = 0;
986 
987 			for (int32 i = fTypeListView->CountItems(); i-- > 0;) {
988 				SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
989 					fTypeListView->ItemAt(i));
990 				if (item == NULL)
991 					continue;
992 
993 				int compare = strcasecmp(item->Type(), type);
994 				if (!compare) {
995 					// type does already exist, select it and bail out
996 					delete newItem;
997 					newItem = NULL;
998 					fTypeListView->Select(i);
999 					break;
1000 				}
1001 				if (compare < 0)
1002 					insertAt = i + 1;
1003 			}
1004 
1005 			if (newItem == NULL)
1006 				break;
1007 
1008 			fTypeListView->AddItem(newItem, insertAt);
1009 			fTypeListView->Select(insertAt);
1010 
1011 			_CheckSaveMenuItem(CHECK_TYPES);
1012 			break;
1013 		}
1014 
1015 		case kMsgRemoveType:
1016 		{
1017 			int32 index = fTypeListView->CurrentSelection();
1018 			if (index < 0)
1019 				break;
1020 
1021 			delete fTypeListView->RemoveItem(index);
1022 			fTypeIconView->SetModificationMessage(NULL);
1023 			fTypeIconView->SetTo(NULL);
1024 			fTypeIconView->SetModificationMessage(
1025 				new BMessage(kMsgTypeIconsChanged));
1026 			fTypeIconView->SetEnabled(false);
1027 			fRemoveTypeButton->SetEnabled(false);
1028 
1029 			_CheckSaveMenuItem(CHECK_TYPES);
1030 			break;
1031 		}
1032 
1033 		case B_SIMPLE_DATA:
1034 		{
1035 			entry_ref ref;
1036 			if (message->FindRef("refs", &ref) != B_OK)
1037 				break;
1038 
1039 			// TODO: add to supported types
1040 			break;
1041 		}
1042 
1043 		case B_META_MIME_CHANGED:
1044 			const char* type;
1045 			int32 which;
1046 			if (message->FindString("be:type", &type) != B_OK
1047 				|| message->FindInt32("be:which", &which) != B_OK)
1048 				break;
1049 
1050 			// TODO: update supported types names
1051 //			if (which == B_MIME_TYPE_DELETED)
1052 
1053 //			_CheckSaveMenuItem(...);
1054 			break;
1055 
1056 		default:
1057 			BWindow::MessageReceived(message);
1058 	}
1059 }
1060 
1061 
1062 bool
1063 ApplicationTypeWindow::QuitRequested()
1064 {
1065 	if (_NeedsSaving(CHECK_ALL) != 0) {
1066 		BAlert* alert = new BAlert(B_TRANSLATE("Save request"),
1067 			B_TRANSLATE("Save changes before closing?"),
1068 			B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"),
1069 			B_TRANSLATE("Save"), B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
1070 			B_WARNING_ALERT);
1071 		alert->SetShortcut(0, B_ESCAPE);
1072 		alert->SetShortcut(1, 'd');
1073 		alert->SetShortcut(2, 's');
1074 
1075 		int32 choice = alert->Go();
1076 		switch (choice) {
1077 			case 0:
1078 				return false;
1079 			case 1:
1080 				break;
1081 			case 2:
1082 				_Save();
1083 				break;
1084 		}
1085 	}
1086 
1087 	BMessage update(kMsgSettingsChanged);
1088 	update.AddRect("app_type_next_frame", Frame());
1089 	be_app_messenger.SendMessage(&update);
1090 
1091 	be_app->PostMessage(kMsgTypeWindowClosed);
1092 	return true;
1093 }
1094 
1095