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