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