xref: /haiku/src/preferences/locale/LanguageListView.cpp (revision a3e794ae459fec76826407f8ba8c94cd3535f128)
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 
36 LanguageListItem::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 
46 LanguageListItem::LanguageListItem(const LanguageListItem& other)
47 	:
48 	BStringItem(other.Text()),
49 	fID(other.fID),
50 	fCode(other.fCode)
51 {
52 }
53 
54 
55 void
56 LanguageListItem::DrawItem(BView* owner, BRect frame, bool complete)
57 {
58 	DrawItemWithTextOffset(owner, frame, complete, 0);
59 }
60 
61 
62 void
63 LanguageListItem::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 
111 LanguageListItemWithFlag::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 
121 LanguageListItemWithFlag::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 
131 LanguageListItemWithFlag::~LanguageListItemWithFlag()
132 {
133 	delete fIcon;
134 }
135 
136 
137 void
138 LanguageListItemWithFlag::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 
158 void
159 LanguageListItemWithFlag::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 
183 LanguageListView::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 
195 LanguageListView::~LanguageListView()
196 {
197 }
198 
199 
200 LanguageListItem*
201 LanguageListView::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 
218 LanguageListItem*
219 LanguageListView::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 
236 void
237 LanguageListView::SetDeleteMessage(BMessage* message)
238 {
239 	delete fDeleteMessage;
240 	fDeleteMessage = message;
241 }
242 
243 
244 void
245 LanguageListView::SetDragMessage(BMessage* message)
246 {
247 	delete fDragMessage;
248 	fDragMessage = message;
249 }
250 
251 
252 void
253 LanguageListView::SetGlobalDropTargetIndicator(bool isGlobal)
254 {
255 	fGlobalDropTargetIndicator = isGlobal;
256 }
257 
258 
259 void
260 LanguageListView::AttachedToWindow()
261 {
262 	BOutlineListView::AttachedToWindow();
263 	ScrollToSelection();
264 }
265 
266 
267 void
268 LanguageListView::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 
281 void
282 LanguageListView::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 
309 bool
310 LanguageListView::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 
424 void
425 LanguageListView::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 
482 void
483 LanguageListView::MouseUp(BPoint point)
484 {
485 	BOutlineListView::MouseUp(point);
486 	if (fDropTargetHighlightFrame.IsValid()) {
487 		Invalidate(fDropTargetHighlightFrame);
488 		fDropTargetHighlightFrame = BRect();
489 	}
490 }
491 
492 
493 void
494 LanguageListView::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 
505 bool
506 LanguageListView::_AcceptsDragMessage(const BMessage* message) const
507 {
508 	LanguageListView* sourceView = NULL;
509 	return message != NULL
510 		&& message->FindPointer("listview", (void**)&sourceView) == B_OK;
511 }
512