1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2001, Sven2
6 * Copyright (c) 2017-2020, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// generic user interface
19// container for a dynamic number of vertically stacked controls
20
21#include <C4Include.h>
22#include <C4GuiListBox.h>
23#include <C4FullScreen.h>
24#include <C4LoaderScreen.h>
25#include <C4Application.h>
26
27namespace C4GUI
28{
29
30// ListBox
31
32ListBox::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.push_back(x: C4KeyCodeEx(K_UP));
46 if (Config.Controls.GamepadGuiControl) keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_Up)));
47 pKeyUp = new C4KeyBinding(keys, "GUIListBoxUp", KEYSCOPE_Gui,
48 new ControlKeyCB<ListBox>(*this, &ListBox::KeyUp), C4CustomKey::PRIO_Ctrl);
49 keys.clear();
50 keys.push_back(x: C4KeyCodeEx(K_DOWN));
51 if (Config.Controls.GamepadGuiControl) keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_Down)));
52 pKeyDown = new C4KeyBinding(keys, "GUIListBoxDown", KEYSCOPE_Gui,
53 new ControlKeyCB<ListBox>(*this, &ListBox::KeyDown), C4CustomKey::PRIO_Ctrl);
54 keys.clear();
55 keys.push_back(x: C4KeyCodeEx(K_LEFT));
56 if (Config.Controls.GamepadGuiControl) keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_Left)));
57 pKeyLeft = new C4KeyBinding(keys, "GUIListBoxLeft", KEYSCOPE_Gui,
58 new ControlKeyCB<ListBox>(*this, &ListBox::KeyLeft), C4CustomKey::PRIO_Ctrl);
59 keys.clear();
60 keys.push_back(x: C4KeyCodeEx(K_RIGHT));
61 if (Config.Controls.GamepadGuiControl) keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_Right)));
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.push_back(x: C4KeyCodeEx(K_RETURN));
75 keys.push_back(x: C4KeyCodeEx(K_RETURN, KEYS_Alt));
76 if (Config.Controls.GamepadGuiControl)
77 {
78 keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_AnyLowButton)));
79 }
80 pKeyActivate = new C4KeyBinding(keys, "GUIListActivate", KEYSCOPE_Gui,
81 new ControlKeyCB<ListBox>(*this, &ListBox::KeyActivate), C4CustomKey::PRIO_Ctrl);
82}
83
84ListBox::~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
100void ListBox::DrawElement(C4FacetEx &cgo)
101{
102 if (fDrawBackground)
103 lpDDraw->DrawBoxDw(sfcDest: cgo.Surface, iX1: cgo.TargetX + rcBounds.x, iY1: cgo.TargetY + rcBounds.y, iX2: cgo.TargetX + rcBounds.x + rcBounds.Wdt - 1, iY2: cgo.TargetY + rcBounds.y + rcBounds.Hgt - 1, dwClr: 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)(a: rcSelArea.Hgt, b: GetClientRect().y + GetClientRect().Hgt - rcSelArea.y);
119 // draw
120 if (rcSelArea.Hgt >= 0)
121 lpDDraw->DrawBoxDw(sfcDest: cgo.Surface, iX1: rcSelArea.x + cgo.TargetX, iY1: rcSelArea.y + cgo.TargetY,
122 iX2: rcSelArea.x + rcSelArea.Wdt + cgo.TargetX - 1, iY2: rcSelArea.y + rcSelArea.Hgt + cgo.TargetY - 1,
123 dwClr: 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 lpDDraw->DrawLineDw(sfcTarget: cgo.Surface, x1: static_cast<float>(iX0 + cgo.TargetX), y1: static_cast<float>(iY + cgo.TargetY), x2: static_cast<float>(iX1 + cgo.TargetX), y2: static_cast<float>(iY + cgo.TargetY), C4GUI_ListBoxBarColor);
139 }
140}
141
142void ListBox::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, uint32_t 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(fByUser: true);
165 // item double-clicked? Callback
166 if (iButton == C4MC_Button_LeftDouble && pSelectedItem)
167 if (pSelectionDblClickHandler) pSelectionDblClickHandler->DoCall(pElement: pSelectedItem);
168 }
169 }
170}
171
172void ListBox::UpdateColumnCount()
173{
174 if (iMultiColItemWidth && pClientWindow)
175 {
176 // multicoloumn-listbox
177 iColCount = std::max<int32_t>(a: pClientWindow->GetClientRect().Wdt / iMultiColItemWidth, b: 1);
178 }
179 else
180 {
181 // regular 1-col-listbox
182 iColCount = 1;
183 }
184}
185
186int32_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
199void 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(fByUser: false);
208 }
209}
210
211bool ListBox::KeyContext()
212{
213 // key: context menu
214 if (pSelectedItem && pSelectedItem->DoContext()) return true;
215 return false;
216}
217
218bool 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(fByUser: true);
234 return true;
235}
236
237bool 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(fByUser: true);
253 return true;
254}
255
256bool 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(fByUser: true);
272 return true;
273}
274
275bool 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(fByUser: true);
291 return true;
292}
293
294bool 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(iY: pNextSelectedItem->GetBounds().y, iHgt: pNextSelectedItem->GetBounds().Hgt))
305 {
306 while (pNext = pNextSelectedItem->GetNext())
307 if (pClientWindow->IsRangeInView(iY: pNext->GetBounds().y, iHgt: 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(iPageCount: +1);
316 pNextSelectedItem = pClientWindow->GetLastContained();
317 while (!pClientWindow->IsRangeInView(iY: pNextSelectedItem->GetBounds().y, iHgt: 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(fByUser: true);
326 }
327 return true;
328}
329
330bool 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(iY: pNextSelectedItem->GetBounds().y, iHgt: pNextSelectedItem->GetBounds().Hgt))
341 {
342 while (pNext = pNextSelectedItem->GetPrev())
343 if (pClientWindow->IsRangeInView(iY: pNext->GetBounds().y, iHgt: 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(iPageCount: -1);
352 pNextSelectedItem = pClientWindow->GetFirstContained();
353 while (!pClientWindow->IsRangeInView(iY: pNextSelectedItem->GetBounds().y, iHgt: 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(fByUser: true);
362 }
363 return true;
364}
365
366bool 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(fByUser: true);
373 return true;
374}
375
376bool 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(fByUser: true);
383 return true;
384}
385
386bool ListBox::KeyActivate()
387{
388 // process as doubleclick
389 if (pSelectedItem && pSelectionDblClickHandler)
390 {
391 pSelectionDblClickHandler->DoCall(pElement: pSelectedItem);
392 return true;
393 }
394 return false;
395}
396
397void ListBox::ScrollItemInView(Element *pItem)
398{
399 // safety
400 if (!pItem) return;
401 // scroll covered range into view
402 pClientWindow->ScrollRangeInView(iY: pItem->GetBounds().y, iHgt: pItem->GetBounds().Hgt);
403}
404
405void 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 iOverallHgt = pCurr->GetBounds().Hgt;
423 // others stacked under it
424 while (pCurr = pCurr->GetNext())
425 {
426 int32_t iYSpace = pCurr->GetListItemTopSpacing();
427 int32_t iNewY = iOverallHgt + iYSpace;
428 iOverallHgt += pCurr->GetBounds().Hgt + iYSpace;
429 if (iNewY != pCurr->GetBounds().y)
430 {
431 pCurr->GetBounds().y = iNewY;
432 pCurr->UpdateOwnPos();
433 }
434 }
435 }
436 else
437 {
438 // Multi column box: Keep element size; reposition horizontally+vertically
439 int32_t y = 0, iLineHgt = 0, col = 0;
440 for (; pCurr; pCurr = pCurr->GetNext())
441 {
442 const C4Rect &rcCurrBounds = pCurr->GetBounds();
443 iLineHgt = std::max<int32_t>(a: rcCurrBounds.Hgt, b: iLineHgt);
444 int32_t x = col * iMultiColItemWidth;
445 if (rcCurrBounds.x != x || rcCurrBounds.y != y || rcCurrBounds.Wdt != iMultiColItemWidth)
446 pCurr->SetBounds(C4Rect(x, y, iMultiColItemWidth, rcCurrBounds.Hgt));
447 if (++col >= iColCount)
448 {
449 col = 0;
450 y += iLineHgt;
451 }
452 }
453 iOverallHgt = y + iLineHgt;
454 }
455 }
456 else
457 iOverallHgt = 0;
458 // update scrolling
459 pClientWindow->SetClientHeight(iOverallHgt);
460}
461
462void ListBox::UpdateElementPosition(Element *pOfElement, int32_t iIndent)
463{
464 // resize it
465 C4Rect &rcChildBounds = pOfElement->GetBounds();
466 rcChildBounds.x = iIndent;
467 rcChildBounds.Wdt = GetItemWidth() - iIndent;
468 pOfElement->UpdateOwnPos();
469 // re-stack elements
470 UpdateElementPositions();
471}
472
473void ListBox::RemoveElement(Element *pChild)
474{
475 // inherited
476 Control::RemoveElement(pChild);
477 // clear selection var
478 if (pChild == pSelectedItem)
479 {
480 pSelectedItem = nullptr;
481 SelectionChanged(fByUser: false);
482 }
483 // position update in AfterElementRemoval
484}
485
486bool ListBox::AddElement(Element *pChild, int32_t iIndent)
487{
488 // fail if no client window is present
489 if (!pClientWindow) return false;
490 // add to scroll window
491 pClientWindow->AddElement(pChild);
492 // resize to horizontal list extents
493 C4Rect &rcChildBounds = pChild->GetBounds();
494 rcChildBounds.x = iIndent;
495 rcChildBounds.Wdt = GetItemWidth() - iIndent;
496 // reposition to end of list
497 if (pChild->GetPrev())
498 {
499 if (iMultiColItemWidth)
500 {
501 rcChildBounds.y = pChild->GetPrev()->GetBounds().y;
502 int32_t col = pChild->GetPrev()->GetBounds().x / iMultiColItemWidth + 1;
503 if (col >= iColCount)
504 {
505 col = 0;
506 int32_t cnt = iColCount;
507 int32_t iPrevLineHgt = 0;
508 Element *pPrevChild = pChild->GetPrev();
509 while (cnt-- && pPrevChild)
510 {
511 iPrevLineHgt = std::max<int32_t>(a: iPrevLineHgt, b: pPrevChild->GetBounds().Hgt);
512 pPrevChild = pPrevChild->GetPrev();
513 }
514 rcChildBounds.y += iPrevLineHgt;
515 }
516 rcChildBounds.x = col * iMultiColItemWidth;
517 }
518 else
519 {
520 rcChildBounds.y = pChild->GetPrev()->GetBounds().y + pChild->GetPrev()->GetBounds().Hgt + pChild->GetListItemTopSpacing();
521 }
522 }
523 else
524 rcChildBounds.y = 0;
525 pChild->UpdateOwnPos();
526 // update scrolling
527 pClientWindow->SetClientHeight(rcChildBounds.y + rcChildBounds.Hgt);
528 // success
529 return true;
530}
531
532bool ListBox::InsertElement(Element *pChild, Element *pInsertBefore, int32_t iIndent)
533{
534 // fail if no client window is present
535 if (!pClientWindow) return false;
536 // add to scroll window
537 pClientWindow->InsertElement(pChild, pInsertBefore);
538 // resize to horizontal list extents
539 C4Rect &rcChildBounds = pChild->GetBounds();
540 rcChildBounds.x = iIndent;
541 rcChildBounds.Wdt = GetItemWidth() - iIndent;
542 pChild->UpdateOwnPos();
543 // update all element positions (and scrolling)
544 UpdateElementPositions();
545 // done, success
546 return true;
547}
548
549void ListBox::ElementSizeChanged(Element *pOfElement)
550{
551 // inherited
552 if (pOfElement->GetParent() == this)
553 {
554 Control::ElementSizeChanged(pOfElement);
555 // update col count if list element container was resized
556 UpdateColumnCount();
557 }
558 // update positions of all list items
559 UpdateElementPositions();
560}
561
562void ListBox::ElementPosChanged(Element *pOfElement)
563{
564 // inherited
565 if (pOfElement->GetParent() == this)
566 Control::ElementSizeChanged(pOfElement);
567 // update positions of all list items
568 UpdateElementPositions();
569}
570
571void ListBox::SelectionChanged(bool fByUser)
572{
573 // selections disabled?
574 if (fSelectionDisabled) { pSelectedItem = nullptr; return; }
575 // any selection?
576 if (pSelectedItem)
577 {
578 // effect
579 if (fByUser) GUISound(szSound: "Command");
580 }
581 // callback (caution: May do periluous things...)
582 if (pSelectionChangeHandler) pSelectionChangeHandler->DoCall(pElement: pSelectedItem);
583 // let's hope it wasn't perilous enough to delete this,
584 // because scrolling the item into view must be done AFTER the callback, as the callback might resize
585 if (pSelectedItem) ScrollItemInView(pItem: pSelectedItem);
586}
587
588void ListBox::SelectEntry(Element *pNewSel, bool fByUser)
589{
590 assert(!pNewSel || pNewSel->GetParent() == pClientWindow);
591 if (pSelectedItem == pNewSel) return;
592 pSelectedItem = pNewSel;
593 SelectionChanged(fByUser);
594}
595
596bool ListBox::CharIn(const char *c)
597{
598 // Jump to first/next entry beginning with typed letter
599 Element *pSel = GetSelectedItem();
600 Element *pStartCheck = pSel;
601 if (pSel) pSel = pSel->GetNext();
602 if (!pSel)
603 {
604 pSel = GetFirst();
605 if (!pSel) return false;
606 }
607 while (pSel != pStartCheck && !pSel->CheckNameHotkey(c))
608 if (!(pSel = pSel->GetNext()))
609 if (pStartCheck)
610 // list end reached while another entry had been selected before: Re-check start of list
611 pSel = GetFirst();
612 // ok, change selection - might do nothing if list was cycled, which is OK
613 if (pSel)
614 {
615 SelectEntry(pNewSel: pSel, fByUser: true);
616 return true;
617 }
618 return Control::CharIn(c);
619}
620
621class SortCompareElements
622{
623 void *par;
624 ListBox::SortFunction SortFunc;
625
626public:
627 SortCompareElements(ListBox::SortFunction SortFunc, void *par) : SortFunc(SortFunc), par(par) {}
628
629 int operator()(const Element *pEl1, const Element *pEl2)
630 {
631 return (*SortFunc)(pEl1, pEl2, par) > 0;
632 }
633};
634
635void 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(first: ppElements, last: ppElements + iElemCount, comp: SortCompareElements(SortFunc, par));
649 for (i = 0; i < iElemCount; ++i)
650 pClientWindow->ReaddElement(pChild: ppElements[i]);
651 }
652 catch (...)
653 {
654 delete[] ppElements;
655 throw;
656 }
657 delete[] ppElements;
658 UpdateElementPositions();
659}
660
661void ListBox::UpdateSize()
662{
663 Control::UpdateSize();
664 if (pClientWindow)
665 {
666 pClientWindow->UpdateSize();
667 UpdateElementPositions();
668
669 for (Element *pEl = pClientWindow->GetFirst(); pEl; pEl = pEl->GetNext())
670 {
671 C4Rect &rcChildBounds = pEl->GetBounds();
672 rcChildBounds.Wdt = GetItemWidth() - rcChildBounds.x;
673 pEl->SetBounds(rcChildBounds);
674 }
675 }
676}
677
678} // end of namespace
679