xref: /haiku/src/preferences/filetypes/ApplicationTypeWindow.cpp (revision 1294543de9ac0eff000eaea1b18368c36435d08e)
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(0, 0)
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 / 2, 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 / 2, 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 / 2)
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 / 2, 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