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