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