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