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 "AttributeListView.h"
8
9#include <stdio.h>
10
11#include <Catalog.h>
12#include <Locale.h>
13#include <ObjectList.h>
14
15#undef B_TRANSLATION_CONTEXT
16#define B_TRANSLATION_CONTEXT "Attribute ListView"
17
18const struct type_map kTypeMap[] = {
19	{B_TRANSLATE("String"),		B_STRING_TYPE},
20	{B_TRANSLATE("Boolean"),		B_BOOL_TYPE},
21	{B_TRANSLATE("Integer 8 bit"),	B_INT8_TYPE},
22	{B_TRANSLATE("Integer 16 bit"),	B_INT16_TYPE},
23	{B_TRANSLATE("Integer 32 bit"),	B_INT32_TYPE},
24	{B_TRANSLATE("Integer 64 bit"),	B_INT64_TYPE},
25	{B_TRANSLATE("Float"),			B_FLOAT_TYPE},
26	{B_TRANSLATE("Double"),			B_DOUBLE_TYPE},
27	{B_TRANSLATE("Time"),			B_TIME_TYPE},
28	{NULL,							0}
29};
30
31// TODO: in the future, have a (private) Tracker API that exports these
32//	as well as a nice GUI for them.
33const struct display_as_map kDisplayAsMap[] = {
34	{B_TRANSLATE("Default"),	NULL,		{}},
35	{B_TRANSLATE("Checkbox"),	B_TRANSLATE("checkbox"),
36		{B_BOOL_TYPE, B_INT8_TYPE, B_INT16_TYPE, B_INT32_TYPE}},
37	{B_TRANSLATE("Duration"),	B_TRANSLATE("duration"),
38		{B_TIME_TYPE, B_INT8_TYPE, B_INT16_TYPE, B_INT32_TYPE, B_INT64_TYPE}},
39	{B_TRANSLATE("Rating"),		B_TRANSLATE("rating"),
40		{B_INT8_TYPE, B_INT16_TYPE, B_INT32_TYPE}},
41	{NULL,			NULL,		{}}
42};
43
44
45static void
46add_display_as(BString& string, const char* displayAs)
47{
48	if (displayAs == NULL || !displayAs[0])
49		return;
50
51	BString base(displayAs);
52	int32 end = base.FindFirst(':');
53	if (end > 0)
54		base.Truncate(end);
55
56	for (int32 i = 0; kDisplayAsMap[i].name != NULL; i++) {
57		if (base.ICompare(kDisplayAsMap[i].identifier) == 0) {
58			string += ", ";
59			string += base;
60			return;
61		}
62	}
63}
64
65
66static void
67name_for_type(BString& string, type_code type, const char* displayAs)
68{
69	for (int32 i = 0; kTypeMap[i].name != NULL; i++) {
70		if (kTypeMap[i].type == type) {
71			string = kTypeMap[i].name;
72			add_display_as(string, displayAs);
73			return;
74		}
75	}
76
77	char buffer[32];
78	buffer[0] = '\'';
79	buffer[1] = 0xff & (type >> 24);
80	buffer[2] = 0xff & (type >> 16);
81	buffer[3] = 0xff & (type >> 8);
82	buffer[4] = 0xff & (type);
83	buffer[5] = '\'';
84	buffer[6] = 0;
85	for (int16 i = 0; i < 4; i++) {
86		if (buffer[i] < ' ')
87			buffer[i] = '.';
88	}
89
90	snprintf(buffer + 6, sizeof(buffer), " (0x%" B_PRIx32 ")", type);
91	string = buffer;
92}
93
94
95AttributeItem*
96create_attribute_item(BMessage& attributes, int32 index)
97{
98	const char* publicName;
99	if (attributes.FindString("attr:public_name", index, &publicName) != B_OK)
100		return NULL;
101
102	const char* name;
103	if (attributes.FindString("attr:name", index, &name) != B_OK)
104		name = "-";
105
106	type_code type;
107	if (attributes.FindInt32("attr:type", index, (int32 *)&type) != B_OK)
108		type = B_STRING_TYPE;
109
110	const char* displayAs;
111	if (attributes.FindString("attr:display_as", index, &displayAs) != B_OK)
112		displayAs = NULL;
113
114	bool editable;
115	if (attributes.FindBool("attr:editable", index, &editable) != B_OK)
116		editable = false;
117	bool visible;
118	if (attributes.FindBool("attr:viewable", index, &visible) != B_OK)
119		visible = false;
120
121	int32 alignment;
122	if (attributes.FindInt32("attr:alignment", index, &alignment) != B_OK)
123		alignment = B_ALIGN_LEFT;
124
125	int32 width;
126	if (attributes.FindInt32("attr:width", index, &width) != B_OK)
127		width = 50;
128
129	return new AttributeItem(name, publicName, type, displayAs, alignment,
130		width, visible, editable);
131}
132
133
134//	#pragma mark -
135
136
137AttributeItem::AttributeItem(const char* name, const char* publicName,
138		type_code type, const char* displayAs, int32 alignment,
139		int32 width, bool visible, bool editable)
140	:
141	BStringItem(publicName),
142	fName(name),
143	fType(type),
144	fDisplayAs(displayAs),
145	fAlignment(alignment),
146	fWidth(width),
147	fVisible(visible),
148	fEditable(editable)
149{
150}
151
152
153AttributeItem::AttributeItem()
154	:
155	BStringItem(""),
156	fType(B_STRING_TYPE),
157	fAlignment(B_ALIGN_LEFT),
158	fWidth(60),
159	fVisible(true),
160	fEditable(false)
161{
162}
163
164
165AttributeItem::AttributeItem(const AttributeItem& other)
166	:
167	BStringItem(other.PublicName())
168{
169	*this = other;
170}
171
172
173AttributeItem::~AttributeItem()
174{
175}
176
177
178void
179AttributeItem::DrawItem(BView* owner, BRect frame, bool drawEverything)
180{
181	BStringItem::DrawItem(owner, frame, drawEverything);
182
183	rgb_color highColor = owner->HighColor();
184	rgb_color lowColor = owner->LowColor();
185
186	if (IsSelected())
187		owner->SetLowColor(tint_color(lowColor, B_DARKEN_2_TINT));
188
189	rgb_color black = {0, 0, 0, 255};
190
191	if (!IsEnabled())
192		owner->SetHighColor(tint_color(black, B_LIGHTEN_2_TINT));
193	else
194		owner->SetHighColor(black);
195
196	owner->MovePenTo(frame.left + frame.Width() / 2.0f + 5.0f,
197		owner->PenLocation().y);
198
199	BString type;
200	name_for_type(type, fType, fDisplayAs.String());
201	owner->DrawString(type.String());
202
203	owner->SetHighColor(tint_color(owner->ViewColor(), B_DARKEN_1_TINT));
204
205	float middle = frame.left + frame.Width() / 2.0f;
206	owner->StrokeLine(BPoint(middle, 0.0f), BPoint(middle, frame.bottom));
207
208	owner->SetHighColor(highColor);
209	owner->SetLowColor(lowColor);
210}
211
212
213AttributeItem&
214AttributeItem::operator=(const AttributeItem& other)
215{
216	SetText(other.PublicName());
217	fName = other.Name();
218	fType = other.Type();
219	fDisplayAs = other.DisplayAs();
220	fAlignment = other.Alignment();
221	fWidth = other.Width();
222	fVisible = other.Visible();
223	fEditable = other.Editable();
224
225	return *this;
226}
227
228
229bool
230AttributeItem::operator==(const AttributeItem& other) const
231{
232	return !strcmp(Name(), other.Name())
233		&& !strcmp(PublicName(), other.PublicName())
234		&& !strcmp(DisplayAs(), other.DisplayAs())
235		&& Type() == other.Type()
236		&& Alignment() == other.Alignment()
237		&& Width() == other.Width()
238		&& Visible() == other.Visible()
239		&& Editable() == other.Editable();
240}
241
242
243bool
244AttributeItem::operator!=(const AttributeItem& other) const
245{
246	return !(*this == other);
247}
248
249
250//	#pragma mark -
251
252
253AttributeListView::AttributeListView(const char* name)
254	: BListView(name, B_SINGLE_SELECTION_LIST,
255		B_WILL_DRAW | B_NAVIGABLE | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS)
256{
257}
258
259
260AttributeListView::~AttributeListView()
261{
262	_DeleteItems();
263}
264
265
266void
267AttributeListView::_DeleteItems()
268{
269	for (int32 i = CountItems(); i-- > 0;) {
270		delete ItemAt(i);
271	}
272	MakeEmpty();
273}
274
275
276void
277AttributeListView::SetTo(BMimeType* type)
278{
279	AttributeItem selectedItem;
280	if (CurrentSelection(0) >= 0)
281		selectedItem = *(AttributeItem*)ItemAt(CurrentSelection(0));
282
283	// Remove the current items but remember them for now. Also remember
284	// the currently selected item.
285	BObjectList<AttributeItem> previousItems(CountItems(), true);
286	while (AttributeItem* item = (AttributeItem*)RemoveItem((int32)0))
287		previousItems.AddItem(item);
288
289	// fill it again
290
291	if (type == NULL)
292		return;
293
294	BMessage attributes;
295	if (type->GetAttrInfo(&attributes) != B_OK)
296		return;
297
298	AttributeItem* item;
299	int32 i = 0;
300	while ((item = create_attribute_item(attributes, i++)) != NULL)
301		AddItem(item);
302
303	// Maybe all the items are the same, except for one item. That
304	// attribute probably just got added. We should select it so the user
305	// can better follow what's going on. The problem we are solving by
306	// doing it this way is that updates to the MIME database are very
307	// asynchronous. Most likely we have created the new attribute ourselves,
308	// but the notification comes so late, we can't know for sure.
309	if (CountItems() == previousItems.CountItems() + 1) {
310		// First try to make sure that every previous item is there again.
311		bool allPreviousItemsFound = true;
312		for (i = previousItems.CountItems() - 1; i >= 0; i--) {
313			bool previousItemFound = false;
314			for (int32 j = CountItems() - 1; j >= 0; j--) {
315				item = (AttributeItem*)ItemAt(j);
316				if (*item == *previousItems.ItemAt(i)) {
317					previousItemFound = true;
318					break;
319				}
320			}
321			if (!previousItemFound) {
322				allPreviousItemsFound = false;
323				break;
324			}
325		}
326		if (allPreviousItemsFound) {
327			for (i = CountItems() - 1; i >= 0; i--) {
328				item = (AttributeItem*)ItemAt(i);
329				bool foundNewItem = false;
330				for (int32 j = previousItems.CountItems() - 1; j >= 0; j--) {
331					if (*item != *previousItems.ItemAt(j)) {
332						foundNewItem = true;
333						break;
334					}
335				}
336				if (foundNewItem) {
337					Select(i);
338					ScrollToSelection();
339					break;
340				}
341			}
342		}
343	} else {
344		// Try to re-selected a previously selected item, if it's the exact
345		// same attribute. This helps not loosing the selection, since changes
346		// to the model are followed by completely rebuilding the list all the
347		// time.
348		for (i = CountItems() - 1; i >= 0; i--) {
349			item = (AttributeItem*)ItemAt(i);
350			if (*item == selectedItem) {
351				Select(i);
352				ScrollToSelection();
353				break;
354			}
355		}
356	}
357}
358
359
360void
361AttributeListView::Draw(BRect updateRect)
362{
363	BListView::Draw(updateRect);
364
365	SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
366
367	float middle = Bounds().Width() / 2.0f;
368	StrokeLine(BPoint(middle, 0.0f), BPoint(middle, Bounds().bottom));
369}
370
371