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