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 "ExtensionWindow.h"
8#include "FileTypes.h"
9#include "FileTypesWindow.h"
10
11#include <Button.h>
12#include <Catalog.h>
13#include <ControlLook.h>
14#include <LayoutBuilder.h>
15#include <Locale.h>
16#include <MenuField.h>
17#include <MenuItem.h>
18#include <Mime.h>
19#include <PopUpMenu.h>
20#include <String.h>
21#include <TextControl.h>
22
23#include <strings.h>
24
25
26#undef B_TRANSLATION_CONTEXT
27#define B_TRANSLATION_CONTEXT "Extension Window"
28
29
30const uint32 kMsgExtensionUpdated = 'exup';
31const uint32 kMsgAccept = 'acpt';
32
33
34static int
35compare_extensions(const void* _a, const void* _b)
36{
37	const char* a = *(const char **)_a;
38	const char* b = *(const char **)_b;
39
40	int compare = strcasecmp(a, b);
41	if (compare != 0)
42		return compare;
43
44	// sort lower case characters first
45	return -strcmp(a, b);
46}
47
48
49//! newExtensionsList contains all the entries (char*) which are to be added.
50status_t
51merge_extensions(BMimeType& type, const BList& newExtensionsList,
52	const char* removeExtension)
53{
54	BMessage extensions;
55	status_t status = type.GetFileExtensions(&extensions);
56	if (status < B_OK)
57		return status;
58
59	// replace the entry, and remove any equivalent entries
60	BList mergedList;
61	mergedList.AddList(&newExtensionsList);
62	int32 originalCount = mergedList.CountItems();
63
64	const char* extension;
65	for (int32 i = 0; extensions.FindString("extensions", i,
66			&extension) == B_OK; i++) {
67
68		for (int32 j = originalCount; j-- > 0;) {
69			if (!strcmp((const char*)mergedList.ItemAt(j), extension)) {
70				// Do not add this old item again, since it's already
71				// there.
72				mergedList.RemoveItem(j);
73				originalCount--;
74			}
75		}
76
77		// The item will be added behind "originalCount", so we cannot
78		// remove it accidentally in the next iterations, it's is added
79		// for good.
80		if (removeExtension == NULL || strcmp(removeExtension, extension))
81			mergedList.AddItem((void *)extension);
82	}
83
84	mergedList.SortItems(compare_extensions);
85
86	// Copy them to a new message (their memory is still part of the
87	// original BMessage)
88	BMessage newExtensions;
89	for (int32 i = 0; i < mergedList.CountItems(); i++) {
90		newExtensions.AddString("extensions",
91			(const char*)mergedList.ItemAt(i));
92	}
93
94	return type.SetFileExtensions(&newExtensions);
95}
96
97
98status_t
99replace_extension(BMimeType& type, const char* newExtension,
100	const char* oldExtension)
101{
102	BList list;
103	list.AddItem((void *)newExtension);
104
105	return merge_extensions(type, list, oldExtension);
106}
107
108
109//	#pragma mark -
110
111
112ExtensionWindow::ExtensionWindow(FileTypesWindow* target, BMimeType& type,
113		const char* extension)
114	: BWindow(BRect(100, 100, 350, 200), B_TRANSLATE("Extension"),
115		B_MODAL_WINDOW_LOOK, B_MODAL_SUBSET_WINDOW_FEEL,
116		B_NOT_ZOOMABLE | B_NOT_RESIZABLE
117			| B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
118	fTarget(target),
119	fMimeType(type.Type()),
120	fExtension(extension)
121{
122	fExtensionControl = new BTextControl(B_TRANSLATE("Extension:"),
123		extension, NULL);
124	fExtensionControl->SetModificationMessage(
125		new BMessage(kMsgExtensionUpdated));
126	fExtensionControl->SetAlignment(B_ALIGN_LEFT, B_ALIGN_LEFT);
127
128	// filter out invalid characters that can't be part of an extension
129	BTextView* textView = fExtensionControl->TextView();
130	const char* disallowedCharacters = "/:";
131	for (int32 i = 0; disallowedCharacters[i]; i++) {
132		textView->DisallowChar(disallowedCharacters[i]);
133	}
134
135	fAcceptButton = new BButton(extension
136		? B_TRANSLATE("Done") : B_TRANSLATE("Add"),
137		new BMessage(kMsgAccept));
138	fAcceptButton->SetEnabled(false);
139
140	BButton* cancelButton = new BButton(B_TRANSLATE("Cancel"),
141		new BMessage(B_QUIT_REQUESTED));
142
143	float padding = be_control_look->DefaultItemSpacing();
144	BLayoutBuilder::Grid<>(this, padding, padding)
145		.SetInsets(padding, padding, padding, padding)
146		.AddTextControl(fExtensionControl, 0, 0, B_ALIGN_HORIZONTAL_UNSET, 1, 2)
147		.Add(BSpaceLayoutItem::CreateGlue(), 0, 1)
148		.Add(cancelButton, 1, 1)
149		.Add(fAcceptButton, 2, 1);
150
151	// omit the leading dot
152	if (fExtension.ByteAt(0) == '.')
153		fExtension.Remove(0, 1);
154
155	fAcceptButton->MakeDefault(true);
156	fExtensionControl->MakeFocus(true);
157
158	target->PlaceSubWindow(this);
159	AddToSubset(target);
160}
161
162
163ExtensionWindow::~ExtensionWindow()
164{
165}
166
167
168void
169ExtensionWindow::MessageReceived(BMessage* message)
170{
171	switch (message->what) {
172		case kMsgExtensionUpdated:
173		{
174			bool enabled = fExtensionControl->Text() != NULL
175				&& fExtensionControl->Text()[0] != '\0';
176			if (enabled) {
177				// There is some text, but we only accept it, if it
178				// changed the previous extension
179				enabled = strcmp(fExtensionControl->Text(), fExtension.String());
180			}
181
182			if (fAcceptButton->IsEnabled() != enabled)
183				fAcceptButton->SetEnabled(enabled);
184			break;
185		}
186
187		case kMsgAccept:
188		{
189			const char* newExtension = fExtensionControl->Text();
190			// omit the leading dot
191			if (newExtension[0] == '.')
192				newExtension++;
193
194			status_t status = replace_extension(fMimeType, newExtension,
195				fExtension.String());
196			if (status != B_OK)
197				error_alert(B_TRANSLATE("Could not change file extensions"),
198					status);
199
200			PostMessage(B_QUIT_REQUESTED);
201			break;
202		}
203
204		default:
205			BWindow::MessageReceived(message);
206			break;
207	}
208}
209