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