xref: /haiku/src/preferences/filetypes/ApplicationTypeWindow.cpp (revision 37c7d5d83a2372a6971e383411d5bacbeef0ebdc)
1 /*
2  * Copyright 2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
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 TR_CONTEXT
47 #define TR_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 		TR("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 = 3.0f;
249 	BAlignment labelAlignment = BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP);
250 	if (be_control_look){
251 		// padding = be_control_look->DefaultItemSpacing();
252 			// seems too big
253 		labelAlignment = be_control_look->DefaultLabelAlignment();
254 	}
255 
256 	BMenuBar* menuBar = new BMenuBar((char*)NULL);
257 	menuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
258 
259 	BMenu* menu = new BMenu(TR("File"));
260 	fSaveMenuItem = new BMenuItem(TR("Save"), new BMessage(kMsgSave), 'S');
261 	fSaveMenuItem->SetEnabled(false);
262 	menu->AddItem(fSaveMenuItem);
263 	BMenuItem* item;
264 	menu->AddItem(item = new BMenuItem(
265 		TR("Save into resource file" B_UTF8_ELLIPSIS), NULL));
266 	item->SetEnabled(false);
267 
268 	menu->AddSeparatorItem();
269 	menu->AddItem(new BMenuItem(TR("Close"), new BMessage(B_QUIT_REQUESTED),
270 		'W', B_COMMAND_KEY));
271 	menuBar->AddItem(menu);
272 
273 
274 	// Signature
275 
276 	fSignatureControl = new BTextControl(TR("Signature:"), NULL,
277 		new BMessage(kMsgSignatureChanged));
278 	fSignatureControl->SetModificationMessage(
279 		new BMessage(kMsgSignatureChanged));
280 
281 	// filter out invalid characters that can't be part of a MIME type name
282 	BTextView* textView = fSignatureControl->TextView();
283 	textView->SetMaxBytes(B_MIME_TYPE_LENGTH);
284 	const char* disallowedCharacters = "<>@,;:\"()[]?=";
285 	for (int32 i = 0; disallowedCharacters[i]; i++) {
286 		textView->DisallowChar(disallowedCharacters[i]);
287 	}
288 
289 	// "Application Flags" group
290 
291 	BBox* flagsBox = new BBox("flagsBox");
292 
293 	fFlagsCheckBox = new BCheckBox("flags", TR("Application flags"),
294 		new BMessage(kMsgToggleAppFlags));
295 	fFlagsCheckBox->SetValue(B_CONTROL_ON);
296 
297 	fSingleLaunchButton = new BRadioButton("single", TR("Single launch"),
298 		new BMessage(kMsgAppFlagsChanged));
299 
300 	fMultipleLaunchButton = new BRadioButton("multiple",
301 		TR("Multiple launch"), new BMessage(kMsgAppFlagsChanged));
302 
303 	fExclusiveLaunchButton = new BRadioButton("exclusive",
304 		TR("Exclusive launch"), new BMessage(kMsgAppFlagsChanged));
305 
306 	fArgsOnlyCheckBox = new BCheckBox("args only", TR("Args only"),
307 		new BMessage(kMsgAppFlagsChanged));
308 
309 	fBackgroundAppCheckBox = new BCheckBox("background",
310 		TR("Background app"), new BMessage(kMsgAppFlagsChanged));
311 
312 	flagsBox->AddChild(BGridLayoutBuilder(padding, padding)
313 		.Add(fSingleLaunchButton, 0, 0).Add(fArgsOnlyCheckBox, 1, 0)
314 		.Add(fMultipleLaunchButton, 0, 1).Add(fBackgroundAppCheckBox, 1, 1)
315 		.Add(fExclusiveLaunchButton, 0, 2)
316 		.SetInsets(padding, padding, padding, padding));
317 	flagsBox->SetLabel(fFlagsCheckBox);
318 
319 	// "Icon" group
320 
321 	BBox* iconBox = new BBox("IconBox");
322 	iconBox->SetLabel(TR("Icon"));
323 	fIconView = new IconView("icon");
324 	fIconView->SetModificationMessage(new BMessage(kMsgIconChanged));
325 	iconBox->AddChild(
326 		BGroupLayoutBuilder(B_HORIZONTAL)
327 		.Add(fIconView)
328 		.SetInsets(padding, padding, padding, padding)
329 	);
330 
331 	// "Supported Types" group
332 
333 	BBox* typeBox = new BBox("typesBox");
334 	typeBox->SetLabel(TR("Supported types"));
335 
336 	fTypeListView = new SupportedTypeListView("Suppported Types",
337 		B_SINGLE_SELECTION_LIST);
338 	fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected));
339 
340 	BScrollView* scrollView = new BScrollView("type scrollview", fTypeListView,
341 		B_FRAME_EVENTS | B_WILL_DRAW, false, true);
342 
343 	fAddTypeButton = new BButton("add type", TR("Add" B_UTF8_ELLIPSIS),
344 		new BMessage(kMsgAddType));
345 
346 	fRemoveTypeButton = new BButton("remove type", TR("Remove"),
347 		new BMessage(kMsgRemoveType));
348 
349 	fTypeIconView = new IconView("type icon");
350 	BView* iconHolder = BGroupLayoutBuilder(B_HORIZONTAL).Add(fTypeIconView);
351 	fTypeIconView->SetModificationMessage(new BMessage(kMsgTypeIconsChanged));
352 
353 	typeBox->AddChild(BGridLayoutBuilder(padding, padding)
354 		.Add(scrollView, 0, 0, 1, 4)
355 		.Add(fAddTypeButton, 1, 0, 1, 2)
356 		.Add(fRemoveTypeButton, 1, 2, 1, 2)
357 		.Add(iconHolder, 2, 1, 1, 2)
358 		.SetInsets(padding, padding, padding, padding)
359 		.SetColumnWeight(0, 3)
360 		.SetColumnWeight(1, 2)
361 		.SetColumnWeight(2, 1)
362 	);
363 	iconHolder->SetExplicitAlignment(BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE));
364 
365 	// "Version Info" group
366 
367 	BBox* versionBox = new BBox("versionBox");
368 	versionBox->SetLabel(TR("Version info"));
369 
370 	fMajorVersionControl = new BTextControl(TR("Version:"), NULL, NULL);
371 	_MakeNumberTextControl(fMajorVersionControl);
372 
373 	fMiddleVersionControl = new BTextControl(".", NULL, NULL);
374 	_MakeNumberTextControl(fMiddleVersionControl);
375 
376 	fMinorVersionControl = new BTextControl(".", NULL, NULL);
377 	_MakeNumberTextControl(fMinorVersionControl);
378 
379 	fVarietyMenu = new BPopUpMenu("variety", true, true);
380 	fVarietyMenu->AddItem(new BMenuItem(TR("Development"), NULL));
381 	fVarietyMenu->AddItem(new BMenuItem(TR("Alpha"), NULL));
382 	fVarietyMenu->AddItem(new BMenuItem(TR("Beta"), NULL));
383 	fVarietyMenu->AddItem(new BMenuItem(TR("Gamma"), NULL));
384 	item = new BMenuItem(TR("Golden master"), NULL);
385 	fVarietyMenu->AddItem(item);
386 	item->SetMarked(true);
387 	fVarietyMenu->AddItem(new BMenuItem(TR("Final"), NULL));
388 
389 	BMenuField* varietyField = new BMenuField("", fVarietyMenu);
390 	fInternalVersionControl = new BTextControl("/", NULL, NULL);
391 	fShortDescriptionControl =
392 		new BTextControl(TR("Short description:"), NULL, NULL);
393 
394 	// TODO: workaround for a GCC 4.1.0 bug? Or is that really what the standard says?
395 	version_info versionInfo;
396 	fShortDescriptionControl->TextView()->SetMaxBytes(
397 		sizeof(versionInfo.short_info));
398 
399 	BStringView* longLabel = new BStringView(NULL, TR("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, 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 	);
426 
427 	// put it all together
428 	SetLayout(new BGroupLayout(B_VERTICAL));
429 	AddChild(menuBar);
430 	AddChild(BGroupLayoutBuilder(B_VERTICAL, padding)
431 		.Add(fSignatureControl)
432 		.Add(BGroupLayoutBuilder(B_HORIZONTAL, padding)
433 			.Add(flagsBox, 3)
434 			.Add(iconBox, 1)
435 		)
436 		.Add(typeBox)
437 		.Add(versionBox)
438 		.SetInsets(padding, padding, padding, padding)
439 	);
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(name);
463 	title.Append(" application type");
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(TR("Save request"), TR("Do you want to "
983 			"save the changes?"), TR("Quit, don't save"), TR("Cancel"),
984 			TR("Save"), B_WIDTH_AS_USUAL, B_WARNING_ALERT);
985 		int32 choice = alert->Go();
986 		switch (choice) {
987 			case 0:
988 				break;
989 			case 1:
990 				return false;
991 			case 2:
992 				_Save();
993 				break;
994 		}
995 	}
996 
997 	be_app->PostMessage(kMsgTypeWindowClosed);
998 	return true;
999 }
1000 
1001