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