1/*
2 * Copyright 2006-2010, Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan A��mus <superstippi@gmx.de>
7 *		Adrien Destugues <pulkomandy@gmail.com>
8 *		Axel D��rfler, axeld@pinc-software.de
9 *		Oliver Tappe <zooey@hirschkaefer.de>
10 */
11
12
13#include "LanguageListView.h"
14
15#include <stdio.h>
16
17#include <new>
18
19#include <Bitmap.h>
20#include <Catalog.h>
21#include <FormattingConventions.h>
22#include <GradientLinear.h>
23#include <LocaleRoster.h>
24#include <Region.h>
25#include <Window.h>
26
27
28#define MAX_DRAG_HEIGHT		200.0
29#define ALPHA				170
30
31#undef B_TRANSLATION_CONTEXT
32#define B_TRANSLATION_CONTEXT "LanguageListView"
33
34
35static const float kLeftInset = 4;
36
37LanguageListItem::LanguageListItem(const char* text, const char* id,
38	const char* languageCode)
39	:
40	BStringItem(text),
41	fID(id),
42	fCode(languageCode)
43{
44}
45
46
47LanguageListItem::LanguageListItem(const LanguageListItem& other)
48	:
49	BStringItem(other.Text()),
50	fID(other.fID),
51	fCode(other.fCode)
52{
53}
54
55
56void
57LanguageListItem::DrawItem(BView* owner, BRect frame, bool complete)
58{
59	DrawItemWithTextOffset(owner, frame, complete, 0);
60}
61
62
63void
64LanguageListItem::DrawItemWithTextOffset(BView* owner, BRect frame,
65	bool complete, float textOffset)
66{
67	if (IsSelected() || complete) {
68		rgb_color color;
69		if (IsSelected())
70			color = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
71		else
72			color = owner->ViewColor();
73
74		owner->SetHighColor(color);
75		owner->SetLowColor(color);
76		owner->FillRect(frame);
77	} else
78		owner->SetLowColor(owner->ViewColor());
79
80	BString text = Text();
81	if (!IsEnabled()) {
82		rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
83		if (textColor.red + textColor.green + textColor.blue > 128 * 3)
84			owner->SetHighColor(tint_color(textColor, B_DARKEN_2_TINT));
85		else
86			owner->SetHighColor(tint_color(textColor, B_LIGHTEN_2_TINT));
87
88		text << "   [" << B_TRANSLATE("already chosen") << "]";
89	} else {
90		if (IsSelected())
91			owner->SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR));
92		else
93			owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
94	}
95
96	owner->MovePenTo(frame.left + kLeftInset + textOffset,
97		frame.top + BaselineOffset());
98	owner->DrawString(text.String());
99}
100
101
102// #pragma mark -
103
104
105LanguageListItemWithFlag::LanguageListItemWithFlag(const char* text,
106	const char* id, const char* languageCode, const char* countryCode)
107	:
108	LanguageListItem(text, id, languageCode),
109	fCountryCode(countryCode),
110	fIcon(NULL)
111{
112}
113
114
115LanguageListItemWithFlag::LanguageListItemWithFlag(
116	const LanguageListItemWithFlag& other)
117	:
118	LanguageListItem(other),
119	fCountryCode(other.fCountryCode),
120	fIcon(other.fIcon != NULL ? new BBitmap(*other.fIcon) : NULL)
121{
122}
123
124
125LanguageListItemWithFlag::~LanguageListItemWithFlag()
126{
127	delete fIcon;
128}
129
130
131void
132LanguageListItemWithFlag::Update(BView* owner, const BFont* font)
133{
134	LanguageListItem::Update(owner, font);
135
136	float iconSize = Height();
137	SetWidth(Width() + iconSize + 4);
138
139	if (fCountryCode.IsEmpty())
140		return;
141
142	fIcon = new(std::nothrow) BBitmap(BRect(0, 0, iconSize - 1, iconSize - 1),
143		B_RGBA32);
144	if (fIcon != NULL && BLocaleRoster::Default()->GetFlagIconForCountry(fIcon,
145			fCountryCode.String()) != B_OK) {
146		delete fIcon;
147		fIcon = NULL;
148	}
149}
150
151
152void
153LanguageListItemWithFlag::DrawItem(BView* owner, BRect frame, bool complete)
154{
155	if (fIcon == NULL || !fIcon->IsValid()) {
156		DrawItemWithTextOffset(owner, frame, complete, 0);
157		return;
158	}
159
160	float iconSize = fIcon->Bounds().Width();
161	DrawItemWithTextOffset(owner, frame, complete, iconSize + 4);
162
163	BRect iconFrame(frame.left + kLeftInset, frame.top,
164		frame.left + kLeftInset + iconSize - 1, frame.top + iconSize - 1);
165	owner->SetDrawingMode(B_OP_OVER);
166	owner->DrawBitmap(fIcon, iconFrame);
167	owner->SetDrawingMode(B_OP_COPY);
168}
169
170
171// #pragma mark -
172
173
174LanguageListView::LanguageListView(const char* name, list_view_type type)
175	:
176	BOutlineListView(name, type),
177	fDropIndex(-1),
178	fDropTargetHighlightFrame(),
179	fGlobalDropTargetIndicator(false),
180	fDeleteMessage(NULL),
181	fDragMessage(NULL)
182{
183}
184
185
186LanguageListView::~LanguageListView()
187{
188}
189
190
191LanguageListItem*
192LanguageListView::ItemForLanguageID(const char* id, int32* _index) const
193{
194	for (int32 index = 0; index < FullListCountItems(); index++) {
195		LanguageListItem* item
196			= static_cast<LanguageListItem*>(FullListItemAt(index));
197
198		if (item->ID() == id) {
199			if (_index != NULL)
200				*_index = index;
201			return item;
202		}
203	}
204
205	return NULL;
206}
207
208
209LanguageListItem*
210LanguageListView::ItemForLanguageCode(const char* code, int32* _index) const
211{
212	for (int32 index = 0; index < FullListCountItems(); index++) {
213		LanguageListItem* item
214			= static_cast<LanguageListItem*>(FullListItemAt(index));
215
216		if (item->Code() == code) {
217			if (_index != NULL)
218				*_index = index;
219			return item;
220		}
221	}
222
223	return NULL;
224}
225
226
227void
228LanguageListView::SetDeleteMessage(BMessage* message)
229{
230	delete fDeleteMessage;
231	fDeleteMessage = message;
232}
233
234
235void
236LanguageListView::SetDragMessage(BMessage* message)
237{
238	delete fDragMessage;
239	fDragMessage = message;
240}
241
242
243void
244LanguageListView::SetGlobalDropTargetIndicator(bool isGlobal)
245{
246	fGlobalDropTargetIndicator = isGlobal;
247}
248
249
250void
251LanguageListView::AttachedToWindow()
252{
253	BOutlineListView::AttachedToWindow();
254	ScrollToSelection();
255}
256
257
258void
259LanguageListView::MessageReceived(BMessage* message)
260{
261	if (message->WasDropped() && _AcceptsDragMessage(message)) {
262		// Someone just dropped something on us
263		BMessage dragMessage(*message);
264		dragMessage.AddInt32("drop_index", fDropIndex);
265		dragMessage.AddPointer("drop_target", this);
266		Messenger().SendMessage(&dragMessage);
267	} else
268		BOutlineListView::MessageReceived(message);
269}
270
271
272void
273LanguageListView::Draw(BRect updateRect)
274{
275	BOutlineListView::Draw(updateRect);
276
277	if (fDropIndex >= 0 && fDropTargetHighlightFrame.IsValid()) {
278		// TODO: decide if drawing of a drop target indicator should be moved
279		//       into ControlLook
280		BGradientLinear gradient;
281		int step = fGlobalDropTargetIndicator ? 64 : 128;
282		for (int i = 0; i < 256; i += step)
283			gradient.AddColor(i % (step * 2) == 0
284				? ViewColor() : ui_color(B_CONTROL_HIGHLIGHT_COLOR), i);
285		gradient.AddColor(ViewColor(), 255);
286		gradient.SetStart(fDropTargetHighlightFrame.LeftTop());
287		gradient.SetEnd(fDropTargetHighlightFrame.RightBottom());
288		if (fGlobalDropTargetIndicator) {
289			BRegion region(fDropTargetHighlightFrame);
290			region.Exclude(fDropTargetHighlightFrame.InsetByCopy(2.0, 2.0));
291			ConstrainClippingRegion(&region);
292			FillRect(fDropTargetHighlightFrame, gradient);
293			ConstrainClippingRegion(NULL);
294		} else
295			FillRect(fDropTargetHighlightFrame, gradient);
296	}
297}
298
299
300bool
301LanguageListView::InitiateDrag(BPoint point, int32 dragIndex,
302	bool /*wasSelected*/)
303{
304	if (fDragMessage == NULL)
305		return false;
306
307	BListItem* item = ItemAt(CurrentSelection(0));
308	if (item == NULL) {
309		// workaround for a timing problem
310		// TODO: this should support extending the selection
311		item = ItemAt(dragIndex);
312		Select(dragIndex);
313	}
314	if (item == NULL)
315		return false;
316
317	// create drag message
318	BMessage message = *fDragMessage;
319	message.AddPointer("listview", this);
320
321	for (int32 i = 0;; i++) {
322		int32 index = CurrentSelection(i);
323		if (index < 0)
324			break;
325
326		message.AddInt32("index", index);
327	}
328
329	// figure out drag rect
330
331	BRect dragRect(0.0, 0.0, Bounds().Width(), -1.0);
332	int32 numItems = 0;
333	bool fade = false;
334
335	// figure out, how many items fit into our bitmap
336	for (int32 i = 0, index; message.FindInt32("index", i, &index) == B_OK;
337			i++) {
338		BListItem* item = ItemAt(index);
339		if (item == NULL)
340			break;
341
342		dragRect.bottom += ceilf(item->Height()) + 1.0;
343		numItems++;
344
345		if (dragRect.Height() > MAX_DRAG_HEIGHT) {
346			dragRect.bottom = MAX_DRAG_HEIGHT;
347			fade = true;
348			break;
349		}
350	}
351
352	BBitmap* dragBitmap = new BBitmap(dragRect, B_RGB32, true);
353	if (dragBitmap->IsValid()) {
354		BView* view = new BView(dragBitmap->Bounds(), "helper", B_FOLLOW_NONE,
355			B_WILL_DRAW);
356		dragBitmap->AddChild(view);
357		dragBitmap->Lock();
358		BRect itemBounds(dragRect) ;
359		itemBounds.bottom = 0.0;
360		// let all selected items, that fit into our drag_bitmap, draw
361		for (int32 i = 0; i < numItems; i++) {
362			int32 index = message.FindInt32("index", i);
363			LanguageListItem* item
364				= static_cast<LanguageListItem*>(ItemAt(index));
365			itemBounds.bottom = itemBounds.top + ceilf(item->Height());
366			if (itemBounds.bottom > dragRect.bottom)
367				itemBounds.bottom = dragRect.bottom;
368			item->DrawItem(view, itemBounds);
369			itemBounds.top = itemBounds.bottom + 1.0;
370		}
371		// make a black frame around the edge
372		view->SetHighColor(0, 0, 0, 255);
373		view->StrokeRect(view->Bounds());
374		view->Sync();
375
376		uint8* bits = (uint8*)dragBitmap->Bits();
377		int32 height = (int32)dragBitmap->Bounds().Height() + 1;
378		int32 width = (int32)dragBitmap->Bounds().Width() + 1;
379		int32 bpr = dragBitmap->BytesPerRow();
380
381		if (fade) {
382			for (int32 y = 0; y < height - ALPHA / 2; y++, bits += bpr) {
383				uint8* line = bits + 3;
384				for (uint8* end = line + 4 * width; line < end; line += 4)
385					*line = ALPHA;
386			}
387			for (int32 y = height - ALPHA / 2; y < height;
388				y++, bits += bpr) {
389				uint8* line = bits + 3;
390				for (uint8* end = line + 4 * width; line < end; line += 4)
391					*line = (height - y) << 1;
392			}
393		} else {
394			for (int32 y = 0; y < height; y++, bits += bpr) {
395				uint8* line = bits + 3;
396				for (uint8* end = line + 4 * width; line < end; line += 4)
397					*line = ALPHA;
398			}
399		}
400		dragBitmap->Unlock();
401	} else {
402		delete dragBitmap;
403		dragBitmap = NULL;
404	}
405
406	if (dragBitmap != NULL)
407		DragMessage(&message, dragBitmap, B_OP_ALPHA, BPoint(0.0, 0.0));
408	else
409		DragMessage(&message, dragRect.OffsetToCopy(point), this);
410
411	return true;
412}
413
414
415void
416LanguageListView::MouseMoved(BPoint where, uint32 transit,
417	const BMessage* dragMessage)
418{
419	if (dragMessage != NULL && _AcceptsDragMessage(dragMessage)) {
420		switch (transit) {
421			case B_ENTERED_VIEW:
422			case B_INSIDE_VIEW:
423			{
424				BRect highlightFrame;
425
426				if (fGlobalDropTargetIndicator) {
427					highlightFrame = Bounds();
428					fDropIndex = 0;
429				} else {
430					// offset where by half of item height
431					BRect r = ItemFrame(0);
432					where.y += r.Height() / 2.0;
433
434					int32 index = IndexOf(where);
435					if (index < 0)
436						index = CountItems();
437					highlightFrame = ItemFrame(index);
438					if (highlightFrame.IsValid())
439						highlightFrame.bottom = highlightFrame.top;
440					else {
441						highlightFrame = ItemFrame(index - 1);
442						if (highlightFrame.IsValid())
443							highlightFrame.top = highlightFrame.bottom;
444						else {
445							// empty view, show indicator at top
446							highlightFrame = Bounds();
447							highlightFrame.bottom = highlightFrame.top;
448						}
449					}
450					fDropIndex = index;
451				}
452
453				if (fDropTargetHighlightFrame != highlightFrame) {
454					Invalidate(fDropTargetHighlightFrame);
455					fDropTargetHighlightFrame = highlightFrame;
456					Invalidate(fDropTargetHighlightFrame);
457				}
458
459				BOutlineListView::MouseMoved(where, transit, dragMessage);
460				return;
461			}
462		}
463	}
464
465	if (fDropTargetHighlightFrame.IsValid()) {
466		Invalidate(fDropTargetHighlightFrame);
467		fDropTargetHighlightFrame = BRect();
468	}
469	BOutlineListView::MouseMoved(where, transit, dragMessage);
470}
471
472
473void
474LanguageListView::MouseUp(BPoint point)
475{
476	BOutlineListView::MouseUp(point);
477	if (fDropTargetHighlightFrame.IsValid()) {
478		Invalidate(fDropTargetHighlightFrame);
479		fDropTargetHighlightFrame = BRect();
480	}
481}
482
483
484void
485LanguageListView::KeyDown(const char* bytes, int32 numBytes)
486{
487	if (bytes[0] == B_DELETE && fDeleteMessage != NULL) {
488		Invoke(fDeleteMessage);
489		return;
490	}
491
492	BOutlineListView::KeyDown(bytes, numBytes);
493}
494
495
496bool
497LanguageListView::_AcceptsDragMessage(const BMessage* message) const
498{
499	LanguageListView* sourceView = NULL;
500	return message != NULL
501		&& message->FindPointer("listview", (void**)&sourceView) == B_OK;
502}
503