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