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