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