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