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