1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // generic user interface
17 // container for a dynamic number of vertically stacked controls
18 
19 #include "C4Include.h"
20 #include "gui/C4Gui.h"
21 
22 #include "graphics/C4Draw.h"
23 #include "gui/C4MouseControl.h"
24 
25 namespace C4GUI
26 {
27 
28 
29 // ----------------------------------------------------
30 // ListBox
31 
ListBox(const C4Rect & rtBounds,int32_t iMultiColItemWidth)32 	ListBox::ListBox(const C4Rect &rtBounds, int32_t iMultiColItemWidth) : Control(rtBounds), iMultiColItemWidth(iMultiColItemWidth), iColCount(1)
33 			, pSelectedItem(nullptr), pSelectionChangeHandler(nullptr), pSelectionDblClickHandler(nullptr), fDrawBackground(true), fDrawBorder(false), fSelectionDisabled(false)
34 	{
35 		// calc client rect
36 		UpdateOwnPos();
37 		// create content scroll window
38 		pClientWindow = new ScrollWindow(this);
39 		// calc column count
40 		UpdateColumnCount();
41 		// create key bindings
42 		pKeyContext = new C4KeyBinding(C4KeyCodeEx(K_MENU), "GUIListBoxContext", KEYSCOPE_Gui,
43 		                               new ControlKeyCB<ListBox>(*this, &ListBox::KeyContext), C4CustomKey::PRIO_Ctrl);
44 		C4CustomKey::CodeList keys;
45 		keys.emplace_back(K_UP);
46 		if (Config.Controls.GamepadGuiControl) ControllerKeys::Up(keys);
47 		pKeyUp = new C4KeyBinding(keys, "GUIListBoxUp", KEYSCOPE_Gui,
48 		                          new ControlKeyCB<ListBox>(*this, &ListBox::KeyUp), C4CustomKey::PRIO_Ctrl);
49 		keys.clear();
50 		keys.emplace_back(K_DOWN);
51 		if (Config.Controls.GamepadGuiControl) ControllerKeys::Down(keys);
52 		pKeyDown = new C4KeyBinding(keys, "GUIListBoxDown", KEYSCOPE_Gui,
53 		                            new ControlKeyCB<ListBox>(*this, &ListBox::KeyDown), C4CustomKey::PRIO_Ctrl);
54 		keys.clear();
55 		keys.emplace_back(K_LEFT);
56 		if (Config.Controls.GamepadGuiControl) ControllerKeys::Left(keys);
57 		pKeyLeft = new C4KeyBinding(keys, "GUIListBoxLeft", KEYSCOPE_Gui,
58 		                            new ControlKeyCB<ListBox>(*this, &ListBox::KeyLeft), C4CustomKey::PRIO_Ctrl);
59 		keys.clear();
60 		keys.emplace_back(K_RIGHT);
61 		if (Config.Controls.GamepadGuiControl) ControllerKeys::Right(keys);
62 		pKeyRight = new C4KeyBinding(keys, "GUIListBoxRight", KEYSCOPE_Gui,
63 		                             new ControlKeyCB<ListBox>(*this, &ListBox::KeyRight), C4CustomKey::PRIO_Ctrl);
64 		pKeyPageUp = new C4KeyBinding(C4KeyCodeEx(K_PAGEUP), "GUIListBoxPageUp", KEYSCOPE_Gui,
65 		                              new ControlKeyCB<ListBox>(*this, &ListBox::KeyPageUp), C4CustomKey::PRIO_Ctrl);
66 		pKeyPageDown = new C4KeyBinding(C4KeyCodeEx(K_PAGEDOWN), "GUIListBoxPageDown", KEYSCOPE_Gui,
67 		                                new ControlKeyCB<ListBox>(*this, &ListBox::KeyPageDown), C4CustomKey::PRIO_Ctrl);
68 		pKeyHome = new C4KeyBinding(C4KeyCodeEx(K_HOME), "GUIListBoxHome", KEYSCOPE_Gui,
69 		                            new ControlKeyCB<ListBox>(*this, &ListBox::KeyHome), C4CustomKey::PRIO_Ctrl);
70 		pKeyEnd = new C4KeyBinding(C4KeyCodeEx(K_END), "GUIListBoxEnd", KEYSCOPE_Gui,
71 		                           new ControlKeyCB<ListBox>(*this, &ListBox::KeyEnd), C4CustomKey::PRIO_Ctrl);
72 		// "activate" current item
73 		keys.clear();
74 		keys.emplace_back(K_RETURN);
75 		keys.emplace_back(K_RETURN, KEYS_Alt);
76 		if (Config.Controls.GamepadGuiControl)
77 		{
78 			ControllerKeys::Ok(keys);
79 		}
80 		pKeyActivate = new C4KeyBinding(keys, "GUIListActivate", KEYSCOPE_Gui,
81 		                                new ControlKeyCB<ListBox>(*this, &ListBox::KeyActivate), C4CustomKey::PRIO_Ctrl);
82 	}
83 
~ListBox()84 	ListBox::~ListBox()
85 	{
86 		delete pKeyActivate;
87 		delete pKeyEnd;
88 		delete pKeyHome;
89 		delete pKeyPageDown;
90 		delete pKeyPageUp;
91 		delete pKeyRight;
92 		delete pKeyLeft;
93 		delete pKeyDown;
94 		delete pKeyUp;
95 		delete pKeyContext;
96 		if (pSelectionDblClickHandler) pSelectionDblClickHandler->DeRef();
97 		if (pSelectionChangeHandler) pSelectionChangeHandler->DeRef();
98 	}
99 
DrawElement(C4TargetFacet & cgo)100 	void ListBox::DrawElement(C4TargetFacet &cgo)
101 	{
102 		if (fDrawBackground)
103 			pDraw->DrawBoxDw(cgo.Surface, cgo.TargetX+rcBounds.x, cgo.TargetY+rcBounds.y, cgo.TargetX+rcBounds.x+rcBounds.Wdt-1, cgo.TargetY+rcBounds.y+rcBounds.Hgt-1, 0x7f000000);
104 		if (fDrawBorder) Draw3DFrame(cgo);
105 		// listbox bg: mark selected item
106 		if (!pClientWindow) return;
107 		if (pSelectedItem)
108 		{
109 			C4Rect rcSelArea = pSelectedItem->GetBounds();
110 			rcSelArea.x += GetClientRect().x;
111 			rcSelArea.y += GetClientRect().y + pClientWindow->GetClientRect().y;
112 			// clip
113 			if (rcSelArea.y < GetClientRect().y)
114 			{
115 				rcSelArea.Hgt -= GetClientRect().y - rcSelArea.y;
116 				rcSelArea.y = GetClientRect().y;
117 			}
118 			rcSelArea.Hgt = std::min(rcSelArea.Hgt, GetClientRect().y + GetClientRect().Hgt - rcSelArea.y);
119 			// draw
120 			if (rcSelArea.Hgt>=0)
121 				pDraw->DrawBoxDw(cgo.Surface, rcSelArea.x+cgo.TargetX, rcSelArea.y+cgo.TargetY,
122 				                   rcSelArea.x+rcSelArea.Wdt+cgo.TargetX-1, rcSelArea.y+rcSelArea.Hgt+cgo.TargetY-1,
123 				                   HasDrawFocus() ? C4GUI_ListBoxSelColor : C4GUI_ListBoxInactSelColor);
124 		}
125 		// draw delimeter bars
126 		Element *pCurr = pClientWindow->GetFirst();
127 		if (!pCurr) return;
128 		while ((pCurr = pCurr->GetNext()))
129 			if (pCurr->GetListItemTopSpacingBar())
130 			{
131 				int32_t iYSpace = pCurr->GetListItemTopSpacing();
132 				int32_t iY = pCurr->GetBounds().y + GetClientRect().y + pClientWindow->GetClientRect().y - iYSpace/2;
133 				int32_t iX0 = pCurr->GetBounds().x + GetClientRect().x + C4GUI_ListBoxBarIndent;
134 				int32_t iX1 = iX0 + pClientWindow->GetClientRect().Wdt - 2*C4GUI_ListBoxBarIndent;
135 				// clip
136 				if (iY < GetClientRect().y || iY >= GetClientRect().y+GetClientRect().Hgt) continue;
137 				// draw
138 				pDraw->DrawLineDw(cgo.Surface, (float)(iX0+cgo.TargetX), (float)(iY+cgo.TargetY), (float)(iX1+cgo.TargetX), (float)(iY+cgo.TargetY), C4GUI_ListBoxBarColor);
139 			}
140 	}
141 
MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)142 	void ListBox::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
143 	{
144 		// inherited
145 		Control::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
146 		// safety
147 		if (pClientWindow)
148 		{
149 			// check list area bounds
150 			if (pClientWindow->GetBounds().Contains(iX, iY))
151 				// left btn down: select item (regardless of key states)
152 				if (iButton == C4MC_Button_LeftDown || iButton == C4MC_Button_LeftDouble)
153 				{
154 					// reset selection
155 					Element *pPrevSelectedItem = pSelectedItem;
156 					pSelectedItem = nullptr;
157 					// get client component the mouse is over
158 					iX -= GetMarginLeft(); iY -= GetMarginTop();
159 					iY += pClientWindow->GetScrollY();
160 					for (Element *pCurr = pClientWindow->GetFirst(); pCurr; pCurr = pCurr->GetNext())
161 						if (pCurr->GetBounds().Contains(iX, iY))
162 							pSelectedItem = pCurr;
163 					// selection change sound
164 					if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true);
165 					// item double-clicked? Callback
166 					if (iButton == C4MC_Button_LeftDouble && pSelectedItem)
167 						if (pSelectionDblClickHandler) pSelectionDblClickHandler->DoCall(pSelectedItem);
168 				}
169 		}
170 	}
171 
UpdateColumnCount()172 	void ListBox::UpdateColumnCount()
173 	{
174 		if (iMultiColItemWidth && pClientWindow)
175 		{
176 			// multicoloumn-listbox
177 			iColCount = std::max<int32_t>(pClientWindow->GetClientRect().Wdt / iMultiColItemWidth, 1);
178 		}
179 		else
180 		{
181 			// regular 1-col-listbox
182 			iColCount = 1;
183 		}
184 	}
185 
ContractToElementHeight()186 	int32_t ListBox::ContractToElementHeight()
187 	{
188 		if (!pClientWindow) return 0;
189 		// calc superfluous bottom space
190 		int32_t iExtraSpace = pClientWindow->GetBounds().Hgt - pClientWindow->GetClientRect().Hgt;
191 		if (iExtraSpace <= 0) return 0;
192 		// contract by it
193 		C4Rect rcNewBounds = GetBounds();
194 		rcNewBounds.Hgt -= iExtraSpace;
195 		SetBounds(rcNewBounds);
196 		return iExtraSpace;
197 	}
198 
OnGetFocus(bool fByMouse)199 	void ListBox::OnGetFocus(bool fByMouse)
200 	{
201 		// inherited (tooltip)
202 		Control::OnGetFocus(fByMouse);
203 		// select list item if none is selected (only for keyboard; mouse will select with left-click anyway)
204 		if (!pSelectedItem && pClientWindow && !fByMouse)
205 		{
206 			pSelectedItem = pClientWindow->GetFirstContained();
207 			SelectionChanged(false);
208 		}
209 	}
210 
KeyContext()211 	bool ListBox::KeyContext()
212 	{
213 		// key: context menu
214 		if (pSelectedItem && pSelectedItem->DoContext()) return true;
215 		return false;
216 	}
217 
KeyUp()218 	bool ListBox::KeyUp()
219 	{
220 		// key: selection up
221 		Element *pPrevSelectedItem = pSelectedItem;
222 		if (!pSelectedItem)
223 			// select last
224 			pSelectedItem = pClientWindow->GetLastContained();
225 		else
226 		{
227 			// select prev row
228 			int32_t cnt = iColCount;
229 			while (pSelectedItem && cnt--) pSelectedItem = pSelectedItem->GetPrev();
230 			if (!pSelectedItem) pSelectedItem = pPrevSelectedItem; // was in start row
231 		}
232 		// selection might have changed
233 		if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true);
234 		return true;
235 	}
236 
KeyDown()237 	bool ListBox::KeyDown()
238 	{
239 		// key: selection down
240 		Element *pPrevSelectedItem = pSelectedItem;
241 		if (!pSelectedItem)
242 			// select first
243 			pSelectedItem = pClientWindow->GetFirstContained();
244 		else
245 		{
246 			// select next row
247 			int32_t cnt = iColCount;
248 			while (pSelectedItem && cnt--) pSelectedItem = pSelectedItem->GetNext();
249 			if (!pSelectedItem) pSelectedItem = pPrevSelectedItem; // was in end row
250 		}
251 		// selection might have changed
252 		if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true);
253 		return true;
254 	}
255 
KeyLeft()256 	bool ListBox::KeyLeft()
257 	{
258 		// key: Selection left
259 		// only in multi-col-listboxes
260 		if (!IsMultiColumn()) return false;
261 		Element *pPrevSelectedItem = pSelectedItem;
262 		if (!pSelectedItem)
263 			// select last
264 			pSelectedItem = pClientWindow->GetLastContained();
265 		else
266 		{
267 			// select prev
268 			if (pSelectedItem->GetPrev()) pSelectedItem = pSelectedItem->GetPrev();
269 		}
270 		// selection might have changed
271 		if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true);
272 		return true;
273 	}
274 
KeyRight()275 	bool ListBox::KeyRight()
276 	{
277 		// key: Selection right
278 		// only in multi-col-listboxes
279 		if (!IsMultiColumn()) return false;
280 		Element *pPrevSelectedItem = pSelectedItem;
281 		if (!pSelectedItem)
282 			// select first
283 			pSelectedItem = pClientWindow->GetFirstContained();
284 		else
285 		{
286 			// select next
287 			if (pSelectedItem->GetNext()) pSelectedItem = pSelectedItem->GetNext();
288 		}
289 		// selection might have changed
290 		if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true);
291 		return true;
292 	}
293 
KeyPageDown()294 	bool ListBox::KeyPageDown()
295 	{
296 		// key: selection one page down
297 		// start from first item or selected
298 		Element *pNextSelectedItem = pSelectedItem ? pSelectedItem : pClientWindow->GetFirstContained(), *pNext;
299 		if (!pNextSelectedItem) return false;
300 		if ((pNext = pNextSelectedItem->GetNext()))
301 		{
302 			pNextSelectedItem = pNext;
303 			// if this is not the last, visible item in the list: go down until item is no longer fully in view
304 			if (pClientWindow->IsRangeInView(pNextSelectedItem->GetBounds().y, pNextSelectedItem->GetBounds().Hgt))
305 			{
306 				while ((pNext = pNextSelectedItem->GetNext()))
307 					if (pClientWindow->IsRangeInView(pNext->GetBounds().y, pNext->GetBounds().Hgt))
308 						pNextSelectedItem = pNext;
309 					else
310 						break;
311 			}
312 			else
313 			{
314 				// selected item was last visible: Just scroll one page down and select last visible
315 				pClientWindow->ScrollPages(+1);
316 				pNextSelectedItem = pClientWindow->GetLastContained();
317 				while (!pClientWindow->IsRangeInView(pNextSelectedItem->GetBounds().y, pNextSelectedItem->GetBounds().Hgt))
318 						if ((pNext = pNextSelectedItem->GetPrev())) pNextSelectedItem = pNext; else break;
319 			}
320 		}
321 		// selection might have changed
322 		if (pSelectedItem != pNextSelectedItem)
323 		{
324 			pSelectedItem = pNextSelectedItem;
325 			SelectionChanged(true);
326 		}
327 		return true;
328 	}
329 
KeyPageUp()330 	bool ListBox::KeyPageUp()
331 	{
332 		// key: selection one page up
333 		// start from last item or selected
334 		Element *pNextSelectedItem = pSelectedItem ? pSelectedItem : pClientWindow->GetLastContained(), *pNext;
335 		if (!pNextSelectedItem) return false;
336 		if ((pNext = pNextSelectedItem->GetPrev()))
337 		{
338 			pNextSelectedItem = pNext;
339 			// if this is not the first, visible item in the list: go up until item is no longer fully in view
340 			if (pClientWindow->IsRangeInView(pNextSelectedItem->GetBounds().y, pNextSelectedItem->GetBounds().Hgt))
341 			{
342 				while ((pNext = pNextSelectedItem->GetPrev()))
343 					if (pClientWindow->IsRangeInView(pNext->GetBounds().y, pNext->GetBounds().Hgt))
344 						pNextSelectedItem = pNext;
345 					else
346 						break;
347 			}
348 			else
349 			{
350 				// selected item was last visible: Just scroll one page up and select first visible
351 				pClientWindow->ScrollPages(-1);
352 				pNextSelectedItem = pClientWindow->GetFirstContained();
353 				while (!pClientWindow->IsRangeInView(pNextSelectedItem->GetBounds().y, pNextSelectedItem->GetBounds().Hgt))
354 						if ((pNext = pNextSelectedItem->GetNext())) pNextSelectedItem = pNext; else break;
355 			}
356 		}
357 		// selection might have changed
358 		if (pSelectedItem != pNextSelectedItem)
359 		{
360 			pSelectedItem = pNextSelectedItem;
361 			SelectionChanged(true);
362 		}
363 		return true;
364 	}
365 
KeyHome()366 	bool ListBox::KeyHome()
367 	{
368 		// key: selection to first item
369 		Element *pPrevSelectedItem = pSelectedItem;
370 		pSelectedItem = pClientWindow->GetFirstContained();
371 		// selection might have changed
372 		if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true);
373 		return true;
374 	}
375 
KeyEnd()376 	bool ListBox::KeyEnd()
377 	{
378 		// key: selection to last item
379 		Element *pPrevSelectedItem = pSelectedItem;
380 		pSelectedItem = pClientWindow->GetLastContained();
381 		// selection might have changed
382 		if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true);
383 		return true;
384 	}
385 
KeyActivate()386 	bool ListBox::KeyActivate()
387 	{
388 		// process as doubleclick
389 		if (pSelectedItem && pSelectionDblClickHandler)
390 		{
391 			pSelectionDblClickHandler->DoCall(pSelectedItem);
392 			return true;
393 		}
394 		return false;
395 	}
396 
ScrollItemInView(Element * pItem)397 	void ListBox::ScrollItemInView(Element *pItem)
398 	{
399 		// safety
400 		if (!pItem) return;
401 		// scroll covered range into view
402 		pClientWindow->ScrollRangeInView(pItem->GetBounds().y, pItem->GetBounds().Hgt);
403 	}
404 
UpdateElementPositions()405 	void ListBox::UpdateElementPositions()
406 	{
407 		// safety
408 		if (!pClientWindow) return;
409 		// first item at zero offset
410 		Element *pCurr = pClientWindow->GetFirst();
411 		int iOverallHgt;
412 		if (pCurr)
413 		{
414 			if (!iMultiColItemWidth)
415 			{
416 				// Single column box: All stacked vertically
417 				if (pCurr->GetBounds().y)
418 				{
419 					pCurr->GetBounds().y = 0;
420 					pCurr->UpdateOwnPos();
421 				}
422 				if(pCurr->fVisible) iOverallHgt = pCurr->GetBounds().Hgt;
423 				else iOverallHgt = 0;
424 				// others stacked under it
425 				while ((pCurr = pCurr->GetNext()))
426 				{
427 					if(!pCurr->fVisible) continue; //Do not reserve space for hidden elements
428 					int32_t iYSpace = pCurr->GetListItemTopSpacing();
429 					int32_t iNewY = iOverallHgt + iYSpace;
430 					iOverallHgt += pCurr->GetBounds().Hgt + iYSpace;
431 					if (iNewY != pCurr->GetBounds().y)
432 					{
433 						pCurr->GetBounds().y = iNewY;
434 						pCurr->UpdateOwnPos();
435 					}
436 				}
437 			}
438 			else
439 			{
440 				// Multi column box: Keep element size; reposition horizontally+vertically
441 				int32_t y=0, iLineHgt=0, col=0;
442 				for (; pCurr; pCurr=pCurr->GetNext())
443 				{
444 					const C4Rect &rcCurrBounds = pCurr->GetBounds();
445 					iLineHgt = std::max<int32_t>(rcCurrBounds.Hgt, iLineHgt);
446 					int32_t x = col * iMultiColItemWidth;
447 					if (rcCurrBounds.x != x || rcCurrBounds.y != y || rcCurrBounds.Wdt != iMultiColItemWidth)
448 						pCurr->SetBounds(C4Rect(x,y,iMultiColItemWidth,rcCurrBounds.Hgt));
449 					if (++col >= iColCount)
450 					{
451 						col = 0;
452 						y += iLineHgt;
453 					}
454 				}
455 				iOverallHgt = y + iLineHgt;
456 			}
457 		}
458 		else
459 			iOverallHgt = 0;
460 		// update scrolling
461 		pClientWindow->SetClientHeight(iOverallHgt);
462 	}
463 
UpdateElementPosition(Element * pOfElement,int32_t iIndent)464 	void ListBox::UpdateElementPosition(Element *pOfElement, int32_t iIndent)
465 	{
466 		// resize it
467 		C4Rect &rcChildBounds = pOfElement->GetBounds();
468 		rcChildBounds.x = iIndent;
469 		rcChildBounds.Wdt = GetItemWidth() - iIndent ;
470 		pOfElement->UpdateOwnPos();
471 		// re-stack elements
472 		UpdateElementPositions();
473 	}
474 
RemoveElement(Element * pChild)475 	void ListBox::RemoveElement(Element *pChild)
476 	{
477 		// inherited
478 		Control::RemoveElement(pChild);
479 		// clear selection var
480 		if (pChild == pSelectedItem)
481 		{
482 			pSelectedItem = nullptr;
483 			SelectionChanged(false);
484 		}
485 		// position update in AfterElementRemoval
486 	}
487 
AddElement(Element * pChild,int32_t iIndent)488 	bool ListBox::AddElement(Element *pChild, int32_t iIndent)
489 	{
490 		// fail if no client window is present
491 		if (!pClientWindow) return false;
492 		// add to scroll window
493 		pClientWindow->AddElement(pChild);
494 		// resize to horizontal list extents
495 		C4Rect &rcChildBounds = pChild->GetBounds();
496 		rcChildBounds.x = iIndent;
497 		rcChildBounds.Wdt = GetItemWidth() - iIndent ;
498 		// reposition to end of list
499 		if (pChild->GetPrev())
500 		{
501 			if (iMultiColItemWidth)
502 			{
503 				rcChildBounds.y = pChild->GetPrev()->GetBounds().y;
504 				int32_t col = pChild->GetPrev()->GetBounds().x / iMultiColItemWidth + 1;
505 				if (col >= iColCount)
506 				{
507 					col = 0;
508 					int32_t cnt = iColCount;
509 					int32_t iPrevLineHgt = 0;
510 					Element *pPrevChild = pChild->GetPrev();
511 					while (cnt-- && pPrevChild)
512 					{
513 						iPrevLineHgt = std::max<int32_t>(iPrevLineHgt, pPrevChild->GetBounds().Hgt);
514 						pPrevChild = pPrevChild->GetPrev();
515 					}
516 					rcChildBounds.y += iPrevLineHgt;
517 				}
518 				rcChildBounds.x = col * iMultiColItemWidth;
519 			}
520 			else
521 			{
522 				rcChildBounds.y = pChild->GetPrev()->GetBounds().y + pChild->GetPrev()->GetBounds().Hgt + pChild->GetListItemTopSpacing();
523 			}
524 		}
525 		else
526 			rcChildBounds.y = 0;
527 		pChild->UpdateOwnPos();
528 		// update scrolling
529 		pClientWindow->SetClientHeight(rcChildBounds.y+rcChildBounds.Hgt);
530 		// success
531 		return true;
532 	}
533 
InsertElement(Element * pChild,Element * pInsertBefore,int32_t iIndent)534 	bool ListBox::InsertElement(Element *pChild, Element *pInsertBefore, int32_t iIndent)
535 	{
536 		// fail if no client window is present
537 		if (!pClientWindow) return false;
538 		// add to scroll window
539 		pClientWindow->InsertElement(pChild, pInsertBefore);
540 		// resize to horizontal list extents
541 		C4Rect &rcChildBounds = pChild->GetBounds();
542 		rcChildBounds.x = iIndent;
543 		rcChildBounds.Wdt = GetItemWidth() - iIndent ;
544 		pChild->UpdateOwnPos();
545 		// update all element positions (and scrolling)
546 		UpdateElementPositions();
547 		// done, success
548 		return true;
549 	}
550 
ElementSizeChanged(Element * pOfElement)551 	void ListBox::ElementSizeChanged(Element *pOfElement)
552 	{
553 		// inherited
554 		if (pOfElement->GetParent() == this)
555 		{
556 			Control::ElementSizeChanged(pOfElement);
557 			// update col count if list element container was resized
558 			UpdateColumnCount();
559 		}
560 		// update positions of all list items
561 		UpdateElementPositions();
562 	}
563 
ElementPosChanged(Element * pOfElement)564 	void ListBox::ElementPosChanged(Element *pOfElement)
565 	{
566 		// inherited
567 		if (pOfElement->GetParent() == this)
568 			Control::ElementSizeChanged(pOfElement);
569 		// update positions of all list items
570 		UpdateElementPositions();
571 	}
572 
SelectionChanged(bool fByUser)573 	void ListBox::SelectionChanged(bool fByUser)
574 	{
575 		// selections disabled?
576 		if (fSelectionDisabled) { pSelectedItem = nullptr; return; }
577 		// any selection?
578 		if (pSelectedItem)
579 		{
580 			// effect
581 			if (fByUser) GUISound("UI::Select");
582 		}
583 		// callback (caution: May do periluous things...)
584 		if (pSelectionChangeHandler) pSelectionChangeHandler->DoCall(pSelectedItem);
585 		// let's hope it wasn't perilous enough to delete this,
586 		// because scrolling the item into view must be done AFTER the callback, as the callback might resize
587 		if (pSelectedItem) ScrollItemInView(pSelectedItem);
588 	}
589 
SelectEntry(Element * pNewSel,bool fByUser)590 	void ListBox::SelectEntry(Element *pNewSel, bool fByUser)
591 	{
592 		assert(!pNewSel || pNewSel->GetParent() == pClientWindow);
593 		if (pSelectedItem == pNewSel) return;
594 		pSelectedItem = pNewSel;
595 		SelectionChanged(fByUser);
596 	}
597 
CharIn(const char * c)598 	bool ListBox::CharIn(const char * c)
599 	{
600 		// Jump to first/next entry beginning with typed letter
601 		Element *pSel = GetSelectedItem();
602 		Element *pStartCheck = pSel;
603 		if (pSel) pSel = pSel->GetNext();
604 		if (!pSel)
605 		{
606 			pSel = GetFirst();
607 			if (!pSel) return false;
608 		}
609 		while (pSel != pStartCheck && !pSel->CheckNameHotkey(c))
610 			if (!(pSel = pSel->GetNext()))
611 				if (pStartCheck)
612 					// list end reached while another entry had been selected before: Re-check start of list
613 					pSel = GetFirst();
614 		// ok, change selection - might do nothing if list was cycled, which is OK
615 		if (pSel)
616 		{
617 			SelectEntry(pSel, true);
618 			return true;
619 		}
620 		return Control::CharIn(c);
621 	}
622 
623 	class SortCompareElements
624 	{
625 		void *par;
626 		ListBox::SortFunction SortFunc;
627 
628 	public:
SortCompareElements(ListBox::SortFunction SortFunc,void * par)629 		SortCompareElements(ListBox::SortFunction SortFunc, void *par) : par(par), SortFunc(SortFunc) {}
630 
operator ()(const Element * pEl1,const Element * pEl2)631 		int operator()(const Element *pEl1, const Element *pEl2)
632 		{ return (*SortFunc)(pEl1, pEl2, par)>0; }
633 	};
634 
SortElements(SortFunction SortFunc,void * par)635 	void ListBox::SortElements(SortFunction SortFunc, void *par)
636 	{
637 		// sort list items:
638 		// create an array of all list items, sort it, and reorder them afterwards
639 		if (!pClientWindow) return;
640 		int32_t iElemCount = pClientWindow->GetElementCount();
641 		if (iElemCount <= 1) return;
642 		Element **ppElements = new Element *[iElemCount];
643 		try
644 		{
645 			int32_t i=0;
646 			for (Element *pEl = pClientWindow->GetFirst(); pEl; pEl = pEl->GetNext())
647 				ppElements[i++] = pEl;
648 			std::sort(ppElements, ppElements+iElemCount, SortCompareElements(SortFunc, par));
649 			for (i=0; i<iElemCount; ++i)
650 				pClientWindow->ReaddElement(ppElements[i]);
651 		}
652 		catch (...)
653 		{
654 			delete [] ppElements;
655 			throw;
656 		}
657 		delete [] ppElements;
658 		UpdateElementPositions();
659 	}
660 
661 } // end of namespace
662 
663