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