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
30 const uint32 kMsgExtensionUpdated = 'exup';
31 const uint32 kMsgAccept = 'acpt';
32
33
34 static int
compare_extensions(const void * _a,const void * _b)35 compare_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.
50 status_t
merge_extensions(BMimeType & type,const BList & newExtensionsList,const char * removeExtension)51 merge_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
98 status_t
replace_extension(BMimeType & type,const char * newExtension,const char * oldExtension)99 replace_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
ExtensionWindow(FileTypesWindow * target,BMimeType & type,const char * extension)112 ExtensionWindow::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
~ExtensionWindow()163 ExtensionWindow::~ExtensionWindow()
164 {
165 }
166
167
168 void
MessageReceived(BMessage * message)169 ExtensionWindow::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