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