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