xref: /haiku/src/preferences/locale/LanguageListView.cpp (revision 8d2bf6953e851d431fc67de1bc970c40afa79e9f)
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 
35 static const float kLeftInset = 4;
36 
37 LanguageListItem::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 
47 LanguageListItem::LanguageListItem(const LanguageListItem& other)
48 	:
49 	BStringItem(other.Text()),
50 	fID(other.fID),
51 	fCode(other.fCode)
52 {
53 }
54 
55 
56 void
57 LanguageListItem::DrawItem(BView* owner, BRect frame, bool complete)
58 {
59 	DrawItemWithTextOffset(owner, frame, complete, 0);
60 }
61 
62 
63 void
64 LanguageListItem::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 
105 LanguageListItemWithFlag::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 
115 LanguageListItemWithFlag::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 
125 LanguageListItemWithFlag::~LanguageListItemWithFlag()
126 {
127 	delete fIcon;
128 }
129 
130 
131 void
132 LanguageListItemWithFlag::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 
152 void
153 LanguageListItemWithFlag::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 
174 LanguageListView::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 
186 LanguageListView::~LanguageListView()
187 {
188 }
189 
190 
191 LanguageListItem*
192 LanguageListView::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 
209 LanguageListItem*
210 LanguageListView::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 
227 void
228 LanguageListView::SetDeleteMessage(BMessage* message)
229 {
230 	delete fDeleteMessage;
231 	fDeleteMessage = message;
232 }
233 
234 
235 void
236 LanguageListView::SetDragMessage(BMessage* message)
237 {
238 	delete fDragMessage;
239 	fDragMessage = message;
240 }
241 
242 
243 void
244 LanguageListView::SetGlobalDropTargetIndicator(bool isGlobal)
245 {
246 	fGlobalDropTargetIndicator = isGlobal;
247 }
248 
249 
250 void
251 LanguageListView::AttachedToWindow()
252 {
253 	BOutlineListView::AttachedToWindow();
254 	ScrollToSelection();
255 }
256 
257 
258 void
259 LanguageListView::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 
272 void
273 LanguageListView::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 
300 bool
301 LanguageListView::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 
415 void
416 LanguageListView::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 
473 void
474 LanguageListView::MouseUp(BPoint point)
475 {
476 	BOutlineListView::MouseUp(point);
477 	if (fDropTargetHighlightFrame.IsValid()) {
478 		Invalidate(fDropTargetHighlightFrame);
479 		fDropTargetHighlightFrame = BRect();
480 	}
481 }
482 
483 
484 void
485 LanguageListView::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 
496 bool
497 LanguageListView::_AcceptsDragMessage(const BMessage* message) const
498 {
499 	LanguageListView* sourceView = NULL;
500 	return message != NULL
501 		&& message->FindPointer("listview", (void**)&sourceView) == B_OK;
502 }
503