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