xref: /haiku/src/preferences/filetypes/AttributeWindow.cpp (revision 893988af824e65e49e55f517b157db8386e8002b)
1 /*
2  * Copyright 2006-2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "AttributeWindow.h"
8 #include "FileTypes.h"
9 #include "FileTypesWindow.h"
10 
11 #include <Box.h>
12 #include <Button.h>
13 #include <CheckBox.h>
14 #include <MenuField.h>
15 #include <MenuItem.h>
16 #include <Mime.h>
17 #include <PopUpMenu.h>
18 #include <String.h>
19 #include <TextControl.h>
20 
21 #include <ctype.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 
27 const uint32 kMsgAttributeUpdated = 'atup';
28 const uint32 kMsgTypeChosen = 'typc';
29 const uint32 kMsgDisplayAsChosen = 'dach';
30 const uint32 kMsgVisibilityChanged = 'vsch';
31 const uint32 kMsgAlignmentChosen = 'alnc';
32 const uint32 kMsgAccept = 'acpt';
33 
34 
35 static int
36 compare_attributes(const void* _a, const void* _b)
37 {
38 	AttributeItem* a = *(AttributeItem **)_a;
39 	AttributeItem* b = *(AttributeItem **)_b;
40 
41 	int compare = strcasecmp(a->PublicName(), b->PublicName());
42 	if (compare != 0)
43 		return compare;
44 
45 	return strcmp(a->Name(), b->Name());
46 }
47 
48 
49 static bool
50 compare_display_as(const char* a, const char* b)
51 {
52 	bool emptyA = a == NULL || !a[0];
53 	bool emptyB = b == NULL || !b[0];
54 
55 	if (emptyA && emptyB)
56 		return true;
57 	if (emptyA || emptyB)
58 		return false;
59 
60 	const char* end = strstr(a, ": ");
61 	int32 lengthA = end ? end - a : strlen(a);
62 	end = strstr(b, ": ");
63 	int32 lengthB = end ? end - b : strlen(b);
64 
65 	if (lengthA != lengthB)
66 		return false;
67 
68 	return !strncasecmp(a, b, lengthA);
69 }
70 
71 
72 static const char*
73 display_as_parameter(const char* special)
74 {
75 	const char* parameter = strstr(special, ": ");
76 	if (parameter != NULL)
77 		return parameter + 2;
78 
79 	return NULL;
80 }
81 
82 
83 //	#pragma mark -
84 
85 
86 AttributeWindow::AttributeWindow(FileTypesWindow* target, BMimeType& mimeType,
87 		AttributeItem* attributeItem)
88 	: BWindow(BRect(100, 100, 350, 200), "Attribute", B_MODAL_WINDOW_LOOK,
89 		B_MODAL_SUBSET_WINDOW_FEEL, B_NOT_ZOOMABLE | B_NOT_V_RESIZABLE
90 			| B_ASYNCHRONOUS_CONTROLS),
91 	fTarget(target),
92 	fMimeType(mimeType.Type())
93 {
94 	if (attributeItem != NULL)
95 		fAttribute = *attributeItem;
96 
97 	BRect rect = Bounds();
98 	BView* topView = new BView(rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW);
99 	topView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
100 	AddChild(topView);
101 
102 	rect.InsetBy(8.0f, 8.0f);
103 	fPublicNameControl = new BTextControl(rect, "public", "Attribute Name:",
104 		fAttribute.PublicName(), NULL, B_FOLLOW_LEFT_RIGHT);
105 	fPublicNameControl->SetModificationMessage(new BMessage(kMsgAttributeUpdated));
106 
107 	float labelWidth = fPublicNameControl->StringWidth(fPublicNameControl->Label()) + 2.0f;
108 	fPublicNameControl->SetDivider(labelWidth);
109 	fPublicNameControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
110 
111 	float width, height;
112 	fPublicNameControl->GetPreferredSize(&width, &height);
113 	fPublicNameControl->ResizeTo(rect.Width(), height);
114 	topView->AddChild(fPublicNameControl);
115 
116 	rect = fPublicNameControl->Frame();
117 	rect.OffsetBy(0.0f, rect.Height() + 5.0f);
118 	fAttributeControl = new BTextControl(rect, "internal", "Internal Name:",
119 		fAttribute.Name(), NULL, B_FOLLOW_LEFT_RIGHT);
120 	fAttributeControl->SetModificationMessage(new BMessage(kMsgAttributeUpdated));
121 	fAttributeControl->SetDivider(labelWidth);
122 	fAttributeControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
123 
124 	// filter out invalid characters that can't be part of an attribute
125 	BTextView* textView = fAttributeControl->TextView();
126 	const char* disallowedCharacters = "/";
127 	for (int32 i = 0; disallowedCharacters[i]; i++) {
128 		textView->DisallowChar(disallowedCharacters[i]);
129 	}
130 
131 	topView->AddChild(fAttributeControl);
132 
133 	fTypeMenu = new BPopUpMenu("type");
134 	BMenuItem* item = NULL;
135 	for (int32 i = 0; kTypeMap[i].name != NULL; i++) {
136 		BMessage* message = new BMessage(kMsgTypeChosen);
137 		message->AddInt32("type", kTypeMap[i].type);
138 
139 		item = new BMenuItem(kTypeMap[i].name, message);
140 		fTypeMenu->AddItem(item);
141 
142 		if (kTypeMap[i].type == fAttribute.Type())
143 			item->SetMarked(true);
144 	}
145 
146 	rect.OffsetBy(0.0f, rect.Height() + 4.0f);
147 	BMenuField* menuField = new BMenuField(rect, "types",
148 		"Type:", fTypeMenu);
149 	menuField->SetDivider(labelWidth);
150 	menuField->SetAlignment(B_ALIGN_RIGHT);
151 	menuField->GetPreferredSize(&width, &height);
152 	menuField->ResizeTo(rect.Width(), height);
153 	topView->AddChild(menuField);
154 
155 	rect.OffsetBy(0.0f, rect.Height() + 4.0f);
156 	rect.bottom = rect.top + fAttributeControl->Bounds().Height() * 2.0f + 18.0f;
157 	BBox* box = new BBox(rect, "", B_FOLLOW_LEFT_RIGHT);
158 	topView->AddChild(box);
159 
160 	fVisibleCheckBox = new BCheckBox(rect, "visible", "Visible",
161 		new BMessage(kMsgVisibilityChanged));
162 	fVisibleCheckBox->SetValue(fAttribute.Visible());
163 	fVisibleCheckBox->ResizeToPreferred();
164 	box->SetLabel(fVisibleCheckBox);
165 
166 	labelWidth -= 8.0f;
167 
168 	BMenu* menu = new BPopUpMenu("display as");
169 	for (int32 i = 0; kDisplayAsMap[i].name != NULL; i++) {
170 		BMessage* message = new BMessage(kMsgDisplayAsChosen);
171 		if (kDisplayAsMap[i].identifier != NULL) {
172 			message->AddString("identifier", kDisplayAsMap[i].identifier);
173 			for (int32 j = 0; kDisplayAsMap[i].supported[j]; j++) {
174 				message->AddInt32("supports", kDisplayAsMap[i].supported[j]);
175 			}
176 		}
177 
178 		item = new BMenuItem(kDisplayAsMap[i].name, message);
179 		menu->AddItem(item);
180 
181 		if (compare_display_as(kDisplayAsMap[i].identifier, fAttribute.DisplayAs()))
182 			item->SetMarked(true);
183 	}
184 
185 	rect.OffsetTo(8.0f, fVisibleCheckBox->Bounds().Height());
186 	rect.right -= 18.0f;
187 	fDisplayAsMenuField = new BMenuField(rect, "display as",
188 		"Display As:", menu);
189 	fDisplayAsMenuField->SetDivider(labelWidth);
190 	fDisplayAsMenuField->SetAlignment(B_ALIGN_RIGHT);
191 	fDisplayAsMenuField->ResizeTo(rect.Width(), height);
192 	box->AddChild(fDisplayAsMenuField);
193 
194 	fEditableCheckBox = new BCheckBox(rect, "editable", "Editable",
195 		new BMessage(kMsgAttributeUpdated), B_FOLLOW_RIGHT);
196 	fEditableCheckBox->SetValue(fAttribute.Editable());
197 	fEditableCheckBox->ResizeToPreferred();
198 	fEditableCheckBox->MoveTo(rect.right - fEditableCheckBox->Bounds().Width(),
199 		rect.top + (fDisplayAsMenuField->Bounds().Height()
200 		- fEditableCheckBox->Bounds().Height()) / 2.0f);
201 	box->AddChild(fEditableCheckBox);
202 
203 	rect.OffsetBy(0.0f, menuField->Bounds().Height() + 4.0f);
204 	rect.bottom = rect.top + fPublicNameControl->Bounds().Height();
205 	fSpecialControl = new BTextControl(rect, "special", "Special:",
206 		display_as_parameter(fAttribute.DisplayAs()), NULL,
207 		B_FOLLOW_LEFT_RIGHT);
208 	fSpecialControl->SetModificationMessage(new BMessage(kMsgAttributeUpdated));
209 	fSpecialControl->SetDivider(labelWidth);
210 	fSpecialControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
211 	fSpecialControl->SetEnabled(false);
212 	box->AddChild(fSpecialControl);
213 
214 	char text[64];
215 	snprintf(text, sizeof(text), "%ld", fAttribute.Width());
216 	rect.OffsetBy(0.0f, fSpecialControl->Bounds().Height() + 4.0f);
217 	fWidthControl = new BTextControl(rect, "width", "Width:",
218 		text, NULL, B_FOLLOW_LEFT_RIGHT);
219 	fWidthControl->SetModificationMessage(new BMessage(kMsgAttributeUpdated));
220 	fWidthControl->SetDivider(labelWidth);
221 	fWidthControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
222 
223 	// filter out invalid characters that can't be part of a width
224 	textView = fWidthControl->TextView();
225 	for (int32 i = 0; i < 256; i++) {
226 		if (!isdigit(i))
227 			textView->DisallowChar(i);
228 	}
229 	textView->SetMaxBytes(4);
230 
231 	box->AddChild(fWidthControl);
232 
233 	const struct alignment_map {
234 		int32		alignment;
235 		const char*	name;
236 	} kAlignmentMap[] = {
237 		{B_ALIGN_LEFT, "Left"},
238 		{B_ALIGN_RIGHT, "Right"},
239 		{B_ALIGN_CENTER, "Center"},
240 		{0, NULL}
241 	};
242 
243 	menu = new BPopUpMenu("alignment");
244 	for (int32 i = 0; kAlignmentMap[i].name != NULL; i++) {
245 		BMessage* message = new BMessage(kMsgAlignmentChosen);
246 		message->AddInt32("alignment", kAlignmentMap[i].alignment);
247 
248 		item = new BMenuItem(kAlignmentMap[i].name, message);
249 		menu->AddItem(item);
250 
251 		if (kAlignmentMap[i].alignment == fAttribute.Alignment())
252 			item->SetMarked(true);
253 	}
254 
255 	rect.OffsetBy(0.0f, menuField->Bounds().Height() + 1.0f);
256 	fAlignmentMenuField = new BMenuField(rect, "alignment",
257 		"Alignment:", menu);
258 	fAlignmentMenuField->SetDivider(labelWidth);
259 	fAlignmentMenuField->SetAlignment(B_ALIGN_RIGHT);
260 	fAlignmentMenuField->ResizeTo(rect.Width(), height);
261 	box->AddChild(fAlignmentMenuField);
262 	box->ResizeBy(0.0f, fAlignmentMenuField->Bounds().Height() * 2.0f
263 		+ fVisibleCheckBox->Bounds().Height());
264 
265 	fAcceptButton = new BButton(rect, "add", item ? "Done" : "Add",
266 		new BMessage(kMsgAccept), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
267 	fAcceptButton->ResizeToPreferred();
268 	fAcceptButton->MoveTo(Bounds().Width() - 8.0f - fAcceptButton->Bounds().Width(),
269 		Bounds().Height() - 8.0f - fAcceptButton->Bounds().Height());
270 	fAcceptButton->SetEnabled(false);
271 	topView->AddChild(fAcceptButton);
272 
273 	BButton* button = new BButton(rect, "cancel", "Cancel",
274 		new BMessage(B_QUIT_REQUESTED), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
275 	button->ResizeToPreferred();
276 	button->MoveTo(fAcceptButton->Frame().left - 10.0f - button->Bounds().Width(),
277 		fAcceptButton->Frame().top);
278 	topView->AddChild(button);
279 
280 	ResizeTo(labelWidth * 4.0f + 24.0f, box->Frame().bottom
281 		+ button->Bounds().Height() + 20.0f);
282 	SetSizeLimits(fEditableCheckBox->Bounds().Width() + button->Bounds().Width()
283 		+ fAcceptButton->Bounds().Width() + labelWidth + 24.0f,
284 		32767.0f, Frame().Height(), Frame().Height());
285 
286 	fAcceptButton->MakeDefault(true);
287 	fPublicNameControl->MakeFocus(true);
288 
289 	target->PlaceSubWindow(this);
290 	AddToSubset(target);
291 }
292 
293 
294 AttributeWindow::~AttributeWindow()
295 {
296 }
297 
298 
299 type_code
300 AttributeWindow::_CurrentType() const
301 {
302 	type_code type = B_STRING_TYPE;
303 	BMenuItem* item = fTypeMenu->FindMarked();
304 	if (item != NULL && item->Message() != NULL) {
305 		int32 value;
306 		if (item->Message()->FindInt32("type", &value) == B_OK)
307 			type = value;
308 	}
309 
310 	return type;
311 }
312 
313 
314 BMenuItem*
315 AttributeWindow::_DefaultDisplayAs() const
316 {
317 	return fDisplayAsMenuField->Menu()->ItemAt(0);
318 }
319 
320 
321 void
322 AttributeWindow::_CheckDisplayAs()
323 {
324 	// check display as suported types
325 
326 	type_code currentType = _CurrentType();
327 
328 	BMenu* menu = fDisplayAsMenuField->Menu();
329 	for (int32 i = menu->CountItems(); i-- > 0;) {
330 		BMenuItem* item = menu->ItemAt(i);
331 		bool supported = item == _DefaultDisplayAs();
332 			// the default type is always supported
333 		type_code type;
334 		for (int32 j = 0; item->Message()->FindInt32("supports",
335 				j, (int32*)&type) == B_OK; j++) {
336 			if (type == currentType) {
337 				supported = true;
338 				break;
339 			}
340 		}
341 
342 		item->SetEnabled(supported);
343 		if (item->IsMarked() && !supported)
344 			menu->ItemAt(0)->SetMarked(true);
345 	}
346 
347 	fSpecialControl->SetEnabled(!_DefaultDisplayAs()->IsMarked());
348 }
349 
350 
351 void
352 AttributeWindow::_CheckAcceptable()
353 {
354 	bool enabled = fAttributeControl->Text() != NULL
355 		&& fAttributeControl->Text()[0] != '\0'
356 		&& fPublicNameControl->Text() != NULL
357 		&& fPublicNameControl->Text()[0] != '\0';
358 
359 	if (enabled) {
360 		// check for equality
361 		AttributeItem* item = _NewItemFromCurrent();
362 		enabled = fAttribute != *item;
363 		delete item;
364 	}
365 
366 	// Update button
367 
368 	if (fAcceptButton->IsEnabled() != enabled)
369 		fAcceptButton->SetEnabled(enabled);
370 }
371 
372 
373 AttributeItem*
374 AttributeWindow::_NewItemFromCurrent()
375 {
376 	const char* newAttribute = fAttributeControl->Text();
377 
378 	type_code type = _CurrentType();
379 
380 	int32 alignment = B_ALIGN_LEFT;
381 	BMenuItem* item = fAlignmentMenuField->Menu()->FindMarked();
382 	if (item != NULL && item->Message() != NULL) {
383 		int32 value;
384 		if (item->Message()->FindInt32("alignment", &value) == B_OK)
385 			alignment = value;
386 	}
387 
388 	int32 width = atoi(fWidthControl->Text());
389 	if (width < 0)
390 		width = 0;
391 
392 	BString displayAs;
393 	item = fDisplayAsMenuField->Menu()->FindMarked();
394 	if (item != NULL) {
395 		const char* identifier;
396 		if (item->Message()->FindString("identifier", &identifier) == B_OK) {
397 			displayAs = identifier;
398 
399 			if (fSpecialControl->Text() && fSpecialControl->Text()[0]) {
400 				displayAs += ": ";
401 				displayAs += fSpecialControl->Text();
402 			}
403 		}
404 	}
405 
406 	return new AttributeItem(newAttribute,
407 		fPublicNameControl->Text(), type, displayAs.String(), alignment,
408 		width, fVisibleCheckBox->Value() == B_CONTROL_ON,
409 		fEditableCheckBox->Value() == B_CONTROL_ON);
410 }
411 
412 
413 void
414 AttributeWindow::MessageReceived(BMessage* message)
415 {
416 	switch (message->what) {
417 		case kMsgAttributeUpdated:
418 		case kMsgAlignmentChosen:
419 		case kMsgTypeChosen:
420 			_CheckDisplayAs();
421 			_CheckAcceptable();
422 			break;
423 
424 		case kMsgDisplayAsChosen:
425 			fSpecialControl->SetEnabled(!_DefaultDisplayAs()->IsMarked());
426 			_CheckAcceptable();
427 			break;
428 
429 		case kMsgVisibilityChanged:
430 		{
431 			bool enabled = fVisibleCheckBox->Value() != B_CONTROL_OFF;
432 
433 			fDisplayAsMenuField->SetEnabled(enabled);
434 			fWidthControl->SetEnabled(enabled);
435 			fAlignmentMenuField->SetEnabled(enabled);
436 			fEditableCheckBox->SetEnabled(enabled);
437 
438 			_CheckDisplayAs();
439 			_CheckAcceptable();
440 			break;
441 		}
442 
443 		case kMsgAccept:
444 		{
445 			BMessage attributes;
446 			status_t status = fMimeType.GetAttrInfo(&attributes);
447 			if (status == B_OK) {
448 				// replace the entry, and remove any equivalent entries
449 				BList list;
450 
451 				const char* newAttribute = fAttributeControl->Text();
452 				list.AddItem(_NewItemFromCurrent());
453 
454 				const char* attribute;
455 				for (int32 i = 0; attributes.FindString("attr:name", i,
456 						&attribute) == B_OK; i++) {
457 					if (!strcmp(fAttribute.Name(), attribute)
458 						|| !strcmp(newAttribute, attribute)) {
459 						// remove this item
460 						continue;
461 					}
462 
463 					AttributeItem* item = create_attribute_item(attributes, i);
464 					if (item != NULL)
465 						list.AddItem(item);
466 				}
467 
468 				list.SortItems(compare_attributes);
469 
470 				// Copy them to a new message (their memory is still part of the
471 				// original BMessage)
472 				BMessage newAttributes;
473 				for (int32 i = 0; i < list.CountItems(); i++) {
474 					AttributeItem* item = (AttributeItem*)list.ItemAt(i);
475 
476 					newAttributes.AddString("attr:name", item->Name());
477 					newAttributes.AddString("attr:public_name", item->PublicName());
478 					newAttributes.AddInt32("attr:type", (int32)item->Type());
479 					newAttributes.AddString("attr:display_as", item->DisplayAs());
480 					newAttributes.AddInt32("attr:alignment", item->Alignment());
481 					newAttributes.AddInt32("attr:width", item->Width());
482 					newAttributes.AddBool("attr:viewable", item->Visible());
483 					newAttributes.AddBool("attr:editable", item->Editable());
484 
485 					delete item;
486 				}
487 
488 				status = fMimeType.SetAttrInfo(&newAttributes);
489 			}
490 
491 			if (status != B_OK)
492 				error_alert("Could not change attributes", status);
493 
494 			PostMessage(B_QUIT_REQUESTED);
495 			break;
496 		}
497 
498 		default:
499 			BWindow::MessageReceived(message);
500 			break;
501 	}
502 }
503 
504 
505 bool
506 AttributeWindow::QuitRequested()
507 {
508 	return true;
509 }
510