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