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