1 /* GG is a GUI for OpenGL.
2    Copyright (C) 2003-2008 T. Zachary Laine
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public License
6    as published by the Free Software Foundation; either version 2.1
7    of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with this library; if not, write to the Free
16    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307 USA
18 
19    If you do not wish to comply with the terms of the LGPL please
20    contact the author as other terms are available for a fee.
21 
22    Zach Laine
23    whatwasthataddress@gmail.com */
24 
25 #include <GG/ListBox.h>
26 
27 #include <GG/DeferredLayout.h>
28 #include <GG/DrawUtil.h>
29 #include <GG/GUI.h>
30 #include <GG/Scroll.h>
31 #include <GG/StyleFactory.h>
32 #include <GG/TextControl.h>
33 
34 #include <boost/cast.hpp>
35 
36 #include <iterator>
37 #include <numeric>
38 
39 
40 using namespace GG;
41 
42 // static(s)
43 const int ListBox::DEFAULT_MARGIN(2);
44 const X ListBox::DEFAULT_ROW_WIDTH(50);
45 const Y ListBox::DEFAULT_ROW_HEIGHT(22);
46 const unsigned int ListBox::BORDER_THICK = 2;
47 
48 namespace {
49     struct ListSignalEcho
50     {
ListSignalEcho__anon270f500e0111::ListSignalEcho51         ListSignalEcho(const ListBox& lb, const std::string& name) :
52             m_LB(lb),
53             m_name(name)
54         {}
55 
operator ()__anon270f500e0111::ListSignalEcho56         void operator()()
57         { std::cerr << "GG SIGNAL : " << m_name << "()" << std::endl; }
58 
operator ()__anon270f500e0111::ListSignalEcho59         void operator()(const ListBox::SelectionSet& sels)
60         {
61             std::cerr << "GG SIGNAL : " << m_name << "(sels=[ ";
62 
63             for (const auto& sel : sels)
64             { std::cerr << RowIndex(sel) << ' '; }
65 
66             std::cerr << "])" << std::endl;
67         }
68 
operator ()__anon270f500e0111::ListSignalEcho69         void operator()(ListBox::const_iterator it)
70         { std::cerr << "GG SIGNAL : " << m_name << "(row=" << RowIndex(it) << ")" << std::endl; }
71 
operator ()__anon270f500e0111::ListSignalEcho72         void operator()(ListBox::const_iterator it, const Pt& pt, const Flags<ModKey>& mod_keys)
73         { std::cerr << "GG SIGNAL : " << m_name << "(row=" << RowIndex(it) << " pt=" << pt << ")" << std::endl; }
74 
RowIndex__anon270f500e0111::ListSignalEcho75         std::size_t RowIndex(ListBox::const_iterator it)
76         { return std::distance(m_LB.begin(), it); }
77 
78         const ListBox& m_LB;
79         std::string m_name;
80     };
81 
82     const int SCROLL_WIDTH = 14;
83 
84     class RowSorter // used to sort rows by a certain column (which may contain some empty cells)
85     {
86     public:
RowSorter(const std::function<bool (const ListBox::Row &,const ListBox::Row &,std::size_t)> & cmp,std::size_t col,bool invert)87         RowSorter(const std::function<bool (const ListBox::Row&, const ListBox::Row&, std::size_t)>& cmp,
88                   std::size_t col, bool invert) :
89             m_cmp(cmp),
90             m_sort_col(col),
91             m_invert(invert)
92         {}
93 
operator ()(const std::shared_ptr<ListBox::Row> & l,const std::shared_ptr<ListBox::Row> & r)94         bool operator()(const std::shared_ptr<ListBox::Row>& l, const std::shared_ptr<ListBox::Row>& r)
95         { return m_invert ? m_cmp(*r, *l, m_sort_col) : m_cmp(*l, *r, m_sort_col); }
operator ()(const ListBox::Row * l,const ListBox::Row * r)96         bool operator()(const ListBox::Row* l, const ListBox::Row* r)
97         { return m_invert ? m_cmp(*r, *l, m_sort_col) : m_cmp(*l, *r, m_sort_col); }
98 
99     private:
100         std::function<bool (const ListBox::Row&, const ListBox::Row&, std::size_t)> m_cmp;
101         std::size_t m_sort_col;
102         bool m_invert;
103     };
104 
SafeDeref(const ListBox::iterator & it,const ListBox::iterator & end)105     ListBox::Row* SafeDeref(const ListBox::iterator& it, const ListBox::iterator& end)
106     { return it == end ? nullptr : it->get(); }
107 
IteratorToShared(const ListBox::iterator & it,const ListBox::iterator & end)108     std::shared_ptr<ListBox::Row> IteratorToShared(const ListBox::iterator& it, const ListBox::iterator& end)
109     { return it == end ? std::shared_ptr<ListBox::Row>() : std::shared_ptr<ListBox::Row>(*it); }
110 
RowAboveOrIsRow(ListBox::iterator lhs,ListBox::iterator rhs,ListBox::iterator end)111     bool RowAboveOrIsRow(ListBox::iterator lhs, ListBox::iterator rhs, ListBox::iterator end)
112     {
113         if (rhs == end)
114             return true;
115         if (lhs == end)
116             return false;
117         if (lhs == rhs)
118             return true;
119         const ListBox::Row* lhs_row = SafeDeref(lhs, end);
120         const ListBox::Row* rhs_row = SafeDeref(rhs, end);
121         if (!rhs_row)
122             return true;
123         if (!lhs_row)
124             return false;
125         return lhs_row->Top() < rhs_row->Top();
126     }
127 
ResetIfEqual(ListBox::iterator & val,ListBox::iterator other,ListBox::iterator end)128     void ResetIfEqual(ListBox::iterator& val, ListBox::iterator other, ListBox::iterator end)
129     {
130         if (val == other)
131             val = end;
132     }
133 
AlignmentFromStyle(Flags<ListBoxStyle> style)134     Alignment AlignmentFromStyle(Flags<ListBoxStyle> style)
135     {
136         Alignment retval = ALIGN_NONE;
137         if (style & LIST_LEFT)
138             retval = ALIGN_LEFT;
139         if (style & LIST_CENTER)
140             retval = ALIGN_CENTER;
141         if (style & LIST_RIGHT)
142             retval = ALIGN_RIGHT;
143         return retval;
144     }
145 }
146 
147 ///////////////////////////////////////
148 // ListBoxStyle
149 ///////////////////////////////////////
150 const ListBoxStyle GG::LIST_NONE            (0);
151 const ListBoxStyle GG::LIST_VCENTER         (1 << 0);
152 const ListBoxStyle GG::LIST_TOP             (1 << 1);
153 const ListBoxStyle GG::LIST_BOTTOM          (1 << 2);
154 const ListBoxStyle GG::LIST_CENTER          (1 << 3);
155 const ListBoxStyle GG::LIST_LEFT            (1 << 4);
156 const ListBoxStyle GG::LIST_RIGHT           (1 << 5);
157 const ListBoxStyle GG::LIST_NOSORT          (1 << 6);
158 const ListBoxStyle GG::LIST_SORTDESCENDING  (1 << 7);
159 const ListBoxStyle GG::LIST_NOSEL           (1 << 8);
160 const ListBoxStyle GG::LIST_SINGLESEL       (1 << 9);
161 const ListBoxStyle GG::LIST_QUICKSEL        (1 << 10);
162 const ListBoxStyle GG::LIST_USERDELETE      (1 << 11);
163 const ListBoxStyle GG::LIST_BROWSEUPDATES   (1 << 12);
164 
165 GG_FLAGSPEC_IMPL(ListBoxStyle);
166 
167 namespace {
RegisterListBoxStyles()168     bool RegisterListBoxStyles()
169     {
170         FlagSpec<ListBoxStyle>& spec = FlagSpec<ListBoxStyle>::instance();
171         spec.insert(LIST_NONE,          "LIST_NONE",            true);
172         spec.insert(LIST_VCENTER,       "LIST_VCENTER",         true);
173         spec.insert(LIST_TOP,           "LIST_TOP",             true);
174         spec.insert(LIST_BOTTOM,        "LIST_BOTTOM",          true);
175         spec.insert(LIST_CENTER,        "LIST_CENTER",          true);
176         spec.insert(LIST_LEFT,          "LIST_LEFT",            true);
177         spec.insert(LIST_RIGHT,         "LIST_RIGHT",           true);
178         spec.insert(LIST_NOSORT,        "LIST_NOSORT",          true);
179         spec.insert(LIST_SORTDESCENDING,"LIST_SORTDESCENDING",  true);
180         spec.insert(LIST_NOSEL,         "LIST_NOSEL",           true);
181         spec.insert(LIST_SINGLESEL,     "LIST_SINGLESEL",       true);
182         spec.insert(LIST_QUICKSEL,      "LIST_QUICKSEL",        true);
183         spec.insert(LIST_USERDELETE,    "LIST_USERDELETE",      true);
184         spec.insert(LIST_BROWSEUPDATES, "LIST_BROWSEUPDATES",   true);
185         return true;
186     }
187     bool dummy = RegisterListBoxStyles();
188 }
189 
190 
191 namespace {
192     /** Make \p layout at least \p size large*/
ValidateLayoutSize(GG::Layout * layout,std::size_t size)193     void ValidateLayoutSize(GG::Layout* layout, std::size_t size)
194     {
195         if (layout->Columns() < size)
196             layout->ResizeLayout(1, size);
197     }
198 }
199 
200 ////////////////////////////////////////////////
201 // GG::ListBox::Row
202 ////////////////////////////////////////////////
Row()203 ListBox::Row::Row() :
204     Row(ListBox::DEFAULT_ROW_WIDTH, ListBox::DEFAULT_ROW_HEIGHT)
205 {}
206 
Row(X w,Y h)207 ListBox::Row::Row(X w, Y h) :
208     Control(X0, Y0, w, h),
209     m_row_alignment(ALIGN_VCENTER),
210     m_margin(ListBox::DEFAULT_MARGIN)
211 {}
212 
CompleteConstruction()213 void ListBox::Row::CompleteConstruction()
214 { SetLayout(Wnd::Create<DeferredLayout>(X0, Y0, Width(), Height(), 1, 1, m_margin, m_margin)); }
215 
~Row()216 ListBox::Row::~Row()
217 {}
218 
SortKey(std::size_t col) const219 std::string ListBox::Row::SortKey(std::size_t col) const
220 {
221     if (col >= m_cells.size()) {
222         std::cout << "ListBox::Row::SortKey out of range column = " << col << " > num cols = " << m_cells.size();
223         return "";
224     }
225 
226     const TextControl* text_control = dynamic_cast<const TextControl*>(at(col));
227     return text_control ? text_control->Text() : "";
228 }
229 
size() const230 std::size_t ListBox::Row::size() const
231 { return m_cells.size(); }
232 
empty() const233 bool ListBox::Row::empty() const
234 { return m_cells.empty(); }
235 
at(std::size_t n) const236 Control* ListBox::Row::at(std::size_t n) const
237 { return m_cells.at(n).get(); }
238 
RowAlignment() const239 Alignment ListBox::Row::RowAlignment() const
240 { return m_row_alignment; }
241 
ColAlignment(std::size_t n) const242 Alignment ListBox::Row::ColAlignment(std::size_t n) const
243 { return m_col_alignments[n]; }
244 
ColWidth(std::size_t n) const245 X ListBox::Row::ColWidth(std::size_t n) const
246 { return m_col_widths[n]; }
247 
Margin() const248 unsigned int ListBox::Row::Margin() const
249 { return m_margin; }
250 
IsNormalized() const251 bool ListBox::Row::IsNormalized() const
252 { return m_is_normalized; }
253 
Render()254 void ListBox::Row::Render()
255 {}
256 
GrowWidthsStretchesAlignmentsTo(std::size_t nn)257 void ListBox::Row::GrowWidthsStretchesAlignmentsTo(std::size_t nn) {
258     if (m_col_widths.size() < nn) {
259         m_col_widths.resize(nn, X(5));
260         m_col_alignments.resize(nn, ALIGN_NONE);
261         m_col_stretches.resize(nn, 0.0);
262     }
263 }
264 
push_back(std::shared_ptr<Control> c)265 void ListBox::Row::push_back(std::shared_ptr<Control> c)
266 {
267     m_cells.push_back(c);
268     GrowWidthsStretchesAlignmentsTo(m_cells.size());
269     auto ii = m_cells.size() - 1;
270     auto&& layout = GetLayout();
271     if (c) {
272         layout->Add(std::forward<std::shared_ptr<Control>>(c), 0, ii, m_row_alignment | m_col_alignments[ii]);
273         layout->SetMinimumColumnWidth(ii, m_col_widths.back());
274         layout->SetColumnStretch(ii, m_col_stretches.back());
275     }
276 }
277 
clear()278 void ListBox::Row::clear()
279 {
280     m_cells.clear();
281     RemoveLayout();
282     DetachChildren();
283     SetLayout(Wnd::Create<DeferredLayout>(X0, Y0, Width(), Height(), 1, 1, m_margin, m_margin));
284 }
285 
resize(std::size_t n)286 void ListBox::Row::resize(std::size_t n)
287 {
288     if (n == m_cells.size())
289         return;
290 
291     auto&& layout = GetLayout();
292     for (auto& cell : m_cells) {
293         layout->Remove(cell.get());
294     }
295 
296     std::size_t old_size = m_cells.size();
297 
298     for (std::size_t ii = n; ii < old_size; ++ii) {
299         m_cells[ii].reset();
300     }
301     m_cells.resize(n, nullptr);
302     m_col_widths.resize(n);
303     m_col_alignments.resize(n);
304     m_col_stretches.resize(n);
305     for (std::size_t ii = old_size; ii < n; ++ii) {
306         m_col_widths[ii] = old_size ? m_col_widths[old_size - 1] : X(5); // assign new cells reasonable default widths
307         m_col_alignments[ii] = ALIGN_NONE;
308         m_col_stretches[ii] = 0.0;
309     }
310 
311     DetachChildren();
312     SetLayout(layout);
313 
314     bool nonempty_cell_found = false;
315     for (auto& control : m_cells) {
316         if (control) {
317             nonempty_cell_found = true;
318             break;
319         }
320     }
321 
322     if (!nonempty_cell_found)
323         return;
324 
325     layout->ResizeLayout(1, m_cells.size());
326     for (std::size_t ii = 0; ii < m_cells.size(); ++ii) {
327         if (!m_col_widths.empty())
328             layout->SetMinimumColumnWidth(ii, m_col_widths[ii]);
329         if (!m_col_stretches.empty())
330             layout->SetColumnStretch(ii, m_col_stretches[ii]);
331         if (m_cells[ii]) {
332             if (m_col_alignments.empty())
333                 layout->Add(m_cells[ii], 0, ii, m_row_alignment);
334             else
335                 layout->Add(m_cells[ii], 0, ii, m_row_alignment | m_col_alignments[ii]);
336         }
337     }
338 }
339 
SetCell(std::size_t n,const std::shared_ptr<Control> & c)340 void ListBox::Row::SetCell(std::size_t n, const std::shared_ptr<Control>& c)
341 {
342     if (c == m_cells[n])
343         return;
344 
345     auto&& layout = GetLayout();
346 
347     if (m_cells.size() > n && m_cells[n]) {
348         layout->Remove(m_cells[n].get());
349         m_cells[n].reset();
350     }
351 
352     m_cells[n] = c;
353 
354     if (!c)
355         return;
356     if (layout->Columns() <= n)
357         layout->ResizeLayout(1, n + 1);
358     layout->Add(c, 0, n, m_row_alignment | m_col_alignments[n]);
359 }
360 
RemoveCell(std::size_t n)361 Control* ListBox::Row::RemoveCell(std::size_t n)
362 {
363     if (m_cells.size() <= n)
364         return nullptr;
365     auto&& layout = GetLayout();
366     auto& retval = m_cells[n];
367     layout->Remove(retval.get());
368     m_cells[n].reset();
369     return retval.get();
370 }
371 
SetRowAlignment(Alignment align)372 void ListBox::Row::SetRowAlignment(Alignment align)
373 {
374     if (align == m_row_alignment)
375         return;
376 
377     m_row_alignment = align;
378 
379     auto&& layout = GetLayout();
380     for (std::size_t ii = 0; ii < m_cells.size(); ++ii) {
381         if (m_cells[ii])
382             layout->Add(m_cells[ii], 0, ii,
383                         (m_row_alignment
384                          | (m_col_alignments.empty() ? ALIGN_NONE : m_col_alignments[ii])));
385     }
386 }
387 
SetColAlignment(std::size_t n,Alignment align)388 void ListBox::Row::SetColAlignment(std::size_t n, Alignment align)
389 {
390     GrowWidthsStretchesAlignmentsTo(n + 1);
391     if (align == m_col_alignments[n])
392         return;
393 
394     m_col_alignments[n] = align;
395     auto&& layout = GetLayout();
396     ValidateLayoutSize(layout.get(), n + 1);
397     if (m_cells[n])
398         layout->SetChildAlignment(m_cells[n].get(), m_row_alignment | align);
399 }
400 
SetColWidth(std::size_t n,X width)401 void ListBox::Row::SetColWidth(std::size_t n, X width)
402 {
403     GrowWidthsStretchesAlignmentsTo(n + 1);
404     if (width == m_col_widths[n])
405         return;
406 
407     m_col_widths[n] = width;
408 
409     auto&& layout = GetLayout();
410     ValidateLayoutSize(layout.get(), n + 1);
411     layout->SetMinimumColumnWidth(n, width);
412 }
413 
SetColAlignments(const std::vector<Alignment> & aligns)414 void ListBox::Row::SetColAlignments(const std::vector<Alignment>& aligns)
415 {
416     if (aligns == m_col_alignments)
417         return;
418 
419     m_col_alignments = aligns;
420     m_col_alignments.resize(m_cells.size(), ALIGN_NONE);
421     auto&& layout = GetLayout();
422     ValidateLayoutSize(layout.get(), aligns.size());
423     for (std::size_t ii = 0; ii < m_cells.size(); ++ii) {
424         if (m_cells[ii])
425             layout->SetChildAlignment(m_cells[ii].get(), m_row_alignment | m_col_alignments[ii]);
426     }
427 }
428 
ClearColAlignments()429 void ListBox::Row::ClearColAlignments()
430 {
431     if (m_col_alignments.empty())
432         return;
433 
434     m_col_alignments.clear();
435     auto&& layout = GetLayout();
436     for (auto& control : m_cells) {
437         if (control)
438             layout->SetChildAlignment(control.get(), m_row_alignment);
439     }
440 }
441 
SetColWidths(const std::vector<X> & widths)442 void ListBox::Row::SetColWidths(const std::vector<X>& widths)
443 {
444     if (widths == m_col_widths)
445         return;
446 
447     m_col_widths = widths;
448     m_col_widths.resize(m_cells.size(), GG::X(5));
449     auto&& layout = GetLayout();
450     ValidateLayoutSize(layout.get(), widths.size());
451     for (std::size_t ii = 0; ii < m_cells.size(); ++ii) {
452         layout->SetMinimumColumnWidth(ii, m_col_widths[ii]);
453     }
454 }
455 
ClearColWidths()456 void ListBox::Row::ClearColWidths()
457 {
458     if (m_col_widths.empty())
459         return;
460 
461     m_col_widths.clear();
462     auto&& layout = GetLayout();
463     ValidateLayoutSize(layout.get(), m_cells.size());
464     for (std::size_t ii = 0; ii < m_cells.size(); ++ii) {
465         layout->SetMinimumColumnWidth(ii, GG::X0);
466     }
467 }
468 
SetColStretches(const std::vector<double> & stretches)469 void ListBox::Row::SetColStretches(const std::vector<double>& stretches)
470 {
471     if (stretches == m_col_stretches)
472         return;
473 
474     m_col_stretches = stretches;
475     m_col_stretches.resize(m_cells.size(), 0.0);
476     auto&& layout = GetLayout();
477     ValidateLayoutSize(layout.get(), m_col_stretches.size());
478     for (std::size_t ii = 0; ii < m_cells.size(); ++ii) {
479         layout->SetColumnStretch(ii, m_col_stretches[ii]);
480     }
481 }
482 
SetMargin(unsigned int margin)483 void ListBox::Row::SetMargin(unsigned int margin)
484 {
485     if (margin == m_margin)
486         return;
487 
488     m_margin = margin;
489     auto layout = GetLayout();
490     if (layout)
491     {
492         layout->SetBorderMargin(margin);
493         layout->SetCellMargin(margin);
494     }
495 }
496 
SetNormalized(bool normalized)497 void ListBox::Row::SetNormalized(bool normalized)
498 { m_is_normalized = normalized; }
499 
RClick(const Pt & pt,GG::Flags<GG::ModKey> mod)500 void ListBox::Row::RClick(const Pt& pt, GG::Flags<GG::ModKey> mod) {
501      RightClickedSignal(pt, mod);
502 }
503 
504 ////////////////////////////////////////////////
505 // GG::ListBox::RowPtrIteratorLess
506 ////////////////////////////////////////////////
operator ()(const ListBox::iterator & lhs,const ListBox::iterator & rhs) const507 bool ListBox::RowPtrIteratorLess::operator()(const ListBox::iterator& lhs, const ListBox::iterator& rhs) const
508 { return (*lhs)->Top() < (*rhs)->Top(); }
509 
510 
511 ////////////////////////////////////////////////
512 // GG::ListBox
513 ////////////////////////////////////////////////
ListBox(Clr color,Clr interior)514 ListBox::ListBox(Clr color, Clr interior/* = CLR_ZERO*/) :
515     Control(X0, Y0, X1, Y1, INTERACTIVE),
516     m_caret(m_rows.end()),
517     m_old_sel_row(m_rows.end()),
518     m_old_rdown_row(m_rows.end()),
519     m_lclick_row(m_rows.end()),
520     m_rclick_row(m_rows.end()),
521     m_last_row_browsed(m_rows.end()),
522     m_first_row_shown(m_rows.end()),
523     m_cell_margin(DEFAULT_MARGIN),
524     m_int_color(interior),
525     m_header_row(Wnd::Create<Row>()),
526     m_sort_cmp(DefaultRowCmp<Row>())
527 {
528     Control::SetColor(color);
529 }
530 
CompleteConstruction()531 void ListBox::CompleteConstruction()
532 {
533     ValidateStyle();
534     SetChildClippingMode(ClipToClient);
535     m_auto_scroll_timer.Stop();
536     m_auto_scroll_timer.Connect(this);
537 
538     InstallEventFilter(shared_from_this());
539 
540     if (INSTRUMENT_ALL_SIGNALS) {
541         ClearedRowsSignal.connect(ListSignalEcho(*this, "ListBox::ClearedRowsSignal"));
542         BeforeInsertRowSignal.connect(ListSignalEcho(*this, "ListBox::BeforeInsertRowSignal"));
543         AfterInsertRowSignal.connect(ListSignalEcho(*this, "ListBox::AfterInsertRowSignal"));
544         SelRowsChangedSignal.connect(ListSignalEcho(*this, "ListBox::SelRowsChangedSignal"));
545         DroppedRowSignal.connect(ListSignalEcho(*this, "ListBox::DroppedRowSignal"));
546         LeftClickedRowSignal.connect(ListSignalEcho(*this, "ListBox::LeftClickedRowSignal"));
547         RightClickedRowSignal.connect(ListSignalEcho(*this, "ListBox::RightClickedRowSignal"));
548         DoubleClickedRowSignal.connect(ListSignalEcho(*this, "ListBox::DoubleClickedRowSignal"));
549         BeforeEraseRowSignal.connect(ListSignalEcho(*this, "ListBox::BeforeEraseRowSignal"));
550         AfterEraseRowSignal.connect(ListSignalEcho(*this, "ListBox::AfterEraseRowSignal"));
551         BrowsedRowSignal.connect(ListSignalEcho(*this, "ListBox::BrowsedRowSignal"));
552     }
553 }
554 
~ListBox()555 ListBox::~ListBox()
556 {}
557 
AllowDrops(bool allow)558 void ListBox::AllowDrops(bool allow)
559 { m_allow_drops = allow; }
560 
AllowingDrops()561 bool ListBox::AllowingDrops()
562 { return m_allow_drops; }
563 
AllowAllDropTypes(bool allow)564 void ListBox::AllowAllDropTypes(bool allow) {
565     // If all types are allow use boost::none as a sentinel
566     if (allow)
567         m_allowed_drop_types = boost::none;
568 
569     // Otherwise hold each allowed type in a set.
570     else if (!m_allowed_drop_types)
571         m_allowed_drop_types = std::unordered_set<std::string>();
572 }
573 
DropsAcceptable(DropsAcceptableIter first,DropsAcceptableIter last,const Pt & pt,Flags<ModKey> mod_keys) const574 void ListBox::DropsAcceptable(DropsAcceptableIter first, DropsAcceptableIter last,
575                               const Pt& pt, Flags<ModKey> mod_keys) const
576 {
577     for (auto& it = first; it != last; ++it) {
578         const auto& row = dynamic_cast<const Row* const>(it->first);
579 
580         bool allowed = (m_allow_drops
581                         && row
582                         && AllowedDropType(row->DragDropDataType()));
583 
584         it->second = allowed;
585     }
586 }
587 
HandleRowRightClicked(const Pt & pt,GG::Flags<GG::ModKey> mod)588 void ListBox::HandleRowRightClicked(const Pt& pt, GG::Flags<GG::ModKey> mod) {
589     iterator row_it = RowUnderPt(pt);
590     if (row_it != m_rows.end()) {
591         m_rclick_row = row_it;
592         RightClickedRowSignal(row_it, pt, mod);
593     }
594 }
595 
MinUsableSize() const596 Pt ListBox::MinUsableSize() const
597 {
598     return Pt(X(5 * SCROLL_WIDTH + 2 * BORDER_THICK),
599               Y(5 * SCROLL_WIDTH + 2 * BORDER_THICK));
600 }
601 
ClientUpperLeft() const602 Pt ListBox::ClientUpperLeft() const
603 {
604     return UpperLeft() +
605         Pt(X(BORDER_THICK), static_cast<int>(BORDER_THICK) + (m_header_row->empty() ? Y0 : m_header_row->Height()));
606 }
607 
ClientLowerRight() const608 Pt ListBox::ClientLowerRight() const
609 { return LowerRight() - Pt(static_cast<int>(BORDER_THICK) + RightMargin(), static_cast<int>(BORDER_THICK) + BottomMargin()); }
610 
Empty() const611 bool ListBox::Empty() const
612 { return m_rows.empty(); }
613 
begin() const614 ListBox::const_iterator ListBox::begin() const
615 { return m_rows.begin(); }
616 
end() const617 ListBox::const_iterator ListBox::end() const
618 { return m_rows.end(); }
619 
GetRow(std::size_t n) const620 const ListBox::Row& ListBox::GetRow(std::size_t n) const
621 {
622     assert(n < m_rows.size());
623     return **std::next(m_rows.begin(), n);
624 }
625 
Caret() const626 ListBox::iterator ListBox::Caret() const
627 { return m_caret; }
628 
Selections() const629 const ListBox::SelectionSet& ListBox::Selections() const
630 { return m_selections; }
631 
Selected(iterator it) const632 bool ListBox::Selected(iterator it) const
633 { return it != m_rows.end() && m_selections.count(it); }
634 
InteriorColor() const635 Clr ListBox::InteriorColor() const
636 { return m_int_color; }
637 
HiliteColor() const638 Clr ListBox::HiliteColor() const
639 { return m_hilite_color; }
640 
Style() const641 Flags<ListBoxStyle> ListBox::Style() const
642 { return m_style; }
643 
ColHeaders() const644 const ListBox::Row& ListBox::ColHeaders() const
645 { return *m_header_row; }
646 
FirstRowShown() const647 ListBox::iterator ListBox::FirstRowShown() const
648 { return m_first_row_shown; }
649 
FirstColShown() const650 std::size_t ListBox::FirstColShown() const
651 { return m_first_col_shown; }
652 
LastVisibleRow() const653 ListBox::iterator ListBox::LastVisibleRow() const
654 {
655     Y visible_pixels = ClientSize().y;
656     Y acc(0);
657     iterator it = m_first_row_shown;
658     for (; it != m_rows.end(); ) {
659         acc += (*it)->Height();
660         iterator next_it = it;
661         ++next_it;
662         if (visible_pixels <= acc || next_it == m_rows.end())
663             break;
664         it = next_it;
665     }
666     return it;
667 }
668 
LastVisibleCol() const669 std::size_t ListBox::LastVisibleCol() const
670 {
671     if (m_first_row_shown == m_rows.end())
672         return 0;
673 
674     // Find the last column that is entirely left of the rightmost pixel.
675     X rightmost_pixel = ClientLowerRight().x;
676     std::size_t ii_last_visible(0);
677     for (auto& cell : (*m_first_row_shown)->GetLayout()->Children()) {
678         if (cell->UpperLeft().x >= rightmost_pixel)
679             break;
680         if ((cell->UpperLeft().x < rightmost_pixel) && (cell->LowerRight().x >= rightmost_pixel))
681             return ii_last_visible;
682         ++ii_last_visible;
683     }
684 
685     return (ii_last_visible ? (ii_last_visible - 1) : 0);
686 }
687 
NumRows() const688 std::size_t ListBox::NumRows() const
689 { return m_rows.size(); }
690 
NumCols() const691 std::size_t ListBox::NumCols() const
692 { return m_num_cols; }
693 
KeepColWidths() const694 bool ListBox::KeepColWidths() const
695 { return m_keep_col_widths; }
696 
ManuallyManagingColProps() const697 bool ListBox::ManuallyManagingColProps() const
698 { return !m_manage_column_props; }
699 
SortCol() const700 std::size_t ListBox::SortCol() const
701 { return m_sort_col; }
702 
ColWidth(std::size_t n) const703 X ListBox::ColWidth(std::size_t n) const
704 { return m_col_widths[n]; }
705 
ColAlignment(std::size_t n) const706 Alignment ListBox::ColAlignment(std::size_t n) const
707 { return m_col_alignments[n]; }
708 
RowAlignment(iterator it) const709 Alignment ListBox::RowAlignment(iterator it) const
710 { return (*it)->RowAlignment(); }
711 
ColStretch(std::size_t n) const712 double ListBox::ColStretch(std::size_t n) const
713 { return m_col_stretches[n]; }
714 
AllowedDropType(const std::string & type) const715 bool ListBox::AllowedDropType(const std::string& type) const
716 {
717     return (!m_allowed_drop_types                 // all types allowed
718             || m_allowed_drop_types->count(type)); //this type allowed;
719 }
720 
AutoScrollDuringDragDrops() const721 bool ListBox::AutoScrollDuringDragDrops() const
722 { return m_auto_scroll_during_drag_drops; }
723 
AutoScrollMargin() const724 unsigned int ListBox::AutoScrollMargin() const
725 { return m_auto_scroll_margin; }
726 
AutoScrollInterval() const727 unsigned int ListBox::AutoScrollInterval() const
728 { return m_auto_scroll_timer.Interval(); }
729 
StartingChildDragDrop(const Wnd * wnd,const Pt & offset)730 void ListBox::StartingChildDragDrop(const Wnd* wnd, const Pt& offset)
731 {
732     if (m_selections.empty())
733         return;
734     if (m_rows.empty())
735         return;
736 
737     iterator wnd_it = std::find_if(m_rows.begin(), m_rows.end(),
738                                    [&wnd](const std::shared_ptr<Row>& x){ return x.get() == wnd; });
739     if (wnd_it == m_rows.end())
740         return;
741 
742     if (!m_selections.count(wnd_it))
743         return;
744 
745     // Preserve the displayed row order in the dragged selections by finding the y offset of wnd
746     // and adjusting all the dragged rows relative to wnd.
747     std::map<GG::Y, SelectionSet::iterator> selections_Y_sorted;
748     for (SelectionSet::iterator sel_it = m_selections.begin(); sel_it != m_selections.end(); ++sel_it) {
749         selections_Y_sorted.insert({(**sel_it)->Top(), sel_it});
750     }
751 
752     Y vertical_offset = offset.y;
753     for (const auto& sorted_sel : selections_Y_sorted) {
754         auto row_wnd = **(sorted_sel.second);
755         if (row_wnd.get() == wnd)
756             break;
757         vertical_offset += row_wnd->Height();
758     }
759 
760     for (const auto& sorted_sel : selections_Y_sorted) {
761         auto row_wnd = **(sorted_sel.second);
762         if (row_wnd.get() != wnd) {
763             GUI::GetGUI()->RegisterDragDropWnd(row_wnd, Pt(offset.x, vertical_offset), shared_from_this());
764             vertical_offset -= row_wnd->Height();
765         } else {
766             vertical_offset -= wnd->Height();
767         }
768     }
769 }
770 
AcceptDrops(const Pt & pt,std::vector<std::shared_ptr<Wnd>> wnds,Flags<ModKey> mod_keys)771 void ListBox::AcceptDrops(const Pt& pt, std::vector<std::shared_ptr<Wnd>> wnds, Flags<ModKey> mod_keys)
772 {
773     iterator insertion_it = RowUnderPt(pt);
774     bool inserting_at_first_row = insertion_it == m_first_row_shown;
775     for (auto& wnd : wnds) {
776         if (const auto& row = std::dynamic_pointer_cast<Row>(wnd))
777             Insert(row, insertion_it, true);
778     }
779 
780     // Adjust to show rows inserted before the first row.
781     if (inserting_at_first_row)
782         SetFirstRowShown(std::prev(m_first_row_shown, wnds.size()));
783 }
784 
ChildrenDraggedAway(const std::vector<Wnd * > & wnds,const Wnd * destination)785 void ListBox::ChildrenDraggedAway(const std::vector<Wnd*>& wnds, const Wnd* destination)
786 {
787     if (MatchesOrContains(this, destination))
788         return;
789 
790     std::vector<std::shared_ptr<Row>> initially_selected_rows;
791     if (!(m_style & LIST_NOSEL) && !m_selections.empty()) {
792         // save selections...
793         for (const auto& sel : m_selections)
794             initially_selected_rows.push_back(*sel);
795         m_selections.clear();
796     }
797 
798     // remove dragged-away row from this ListBox
799     for (auto& wnd : wnds) {
800         auto row = boost::polymorphic_downcast<Row*>(wnd);
801         iterator row_it = std::find_if(m_rows.begin(), m_rows.end(),
802                                        [&row](const std::shared_ptr<Row>& x){ return x.get() == row; });
803 
804 
805         if (row_it == m_rows.end())
806             continue;
807 
808         Erase(row_it, false, true);
809     }
810 
811     if (!(m_style & LIST_NOSEL) && !initially_selected_rows.empty()) {
812         // reselect any remaining from old selections
813         SelectionSet new_selections;
814         for (auto& row : initially_selected_rows) {
815             iterator sel_it = std::find(m_rows.begin(), m_rows.end(), row);
816             if (sel_it != m_rows.end())
817                 new_selections.insert(sel_it);
818         }
819 
820         m_selections = new_selections;
821 
822         if (m_selections.size() != initially_selected_rows.size()) {
823             SelRowsChangedSignal(m_selections);
824         }
825     }
826 }
827 
PreRender()828 void ListBox::PreRender()
829 {
830     // Use the first row to define the column properties
831     if (!m_rows.empty()
832         && m_manage_column_props
833         && (m_col_widths.empty() || !m_keep_col_widths))
834     {
835         DefineColWidths(*(*m_rows.begin()));
836         DefineColAlignments(*(*m_rows.begin()));
837         DefineColStretches(*(*m_rows.begin()));
838     }
839 
840     if (m_normalize_rows_on_insert) {
841         if (!m_header_row->empty() && !m_header_row->IsNormalized())
842             NormalizeRow(m_header_row.get());
843         for (auto& row : m_rows)
844             if (!row->IsNormalized())
845                 NormalizeRow(row.get());
846     }
847 
848     // Adding/removing scrolls and prerendering rows may change the row sizes and require a change
849     // in added/removed scrolls. This may not be stable. Perform two cycles and if it is not
850     // stable then force the scrollbar to be added if either cycle had a scroll bar.
851 
852     // Perform a cycle of adjust scrolls and prerendering rows and return if sizes changed.
853     auto check_adjust_scroll_size_change = [this](std::pair<bool, bool> force_scrolls = {false, false}) {
854         // This adjust scrolls may add or remove scrolls
855         AdjustScrolls(true);
856 
857         bool visible_row_size_change = ShowVisibleRows(true);
858 
859         bool header_size_change = false;
860         if (!m_header_row->empty()) {
861             auto old_size = m_header_row->Size();
862             GUI::PreRenderWindow(m_header_row.get());
863             header_size_change |= (old_size != m_header_row->Size());
864         }
865         return visible_row_size_change | header_size_change;
866     };
867 
868     // Try adjusting scroll twice and then force the scrolls on.
869     if (check_adjust_scroll_size_change()) {
870         bool any_vscroll = (m_vscroll != nullptr);
871         bool any_hscroll = (m_hscroll != nullptr);
872 
873         if (check_adjust_scroll_size_change()) {
874             any_vscroll |= (m_vscroll != nullptr);
875             any_hscroll |= (m_hscroll != nullptr);
876             check_adjust_scroll_size_change({any_hscroll, any_vscroll});
877         }
878     }
879 
880     // Reset require prerender after call to adjust scrolls
881     Control::PreRender();
882 
883     // Position rows
884     Pt pt(m_first_row_offset);
885     for (auto& row : m_rows) {
886         row->MoveTo(pt);
887         pt.y += row->Height();
888     }
889 
890 }
Render()891 void ListBox::Render()
892 {
893     // draw beveled rectangle around client area
894     Pt ul = UpperLeft(), lr = LowerRight();
895     Pt cl_ul = ClientUpperLeft(), cl_lr = ClientLowerRight();
896     Clr color_to_use = Disabled() ? DisabledColor(Color()) : Color();
897     Clr int_color_to_use = Disabled() ? DisabledColor(m_int_color) : m_int_color;
898     Clr hilite_color_to_use = Disabled() ? DisabledColor(m_hilite_color) : m_hilite_color;
899 
900     BeveledRectangle(ul, lr, int_color_to_use, color_to_use, false, BORDER_THICK);
901 
902     // HACK! This gets around the issue of how to render headers and scrolls,
903     // which do not fall within the client area.
904     if (!m_header_row->empty()) {
905         Rect header_area(Pt(ul.x + static_cast<int>(BORDER_THICK), m_header_row->Top()),
906                          Pt(lr.x - static_cast<int>(BORDER_THICK), m_header_row->Bottom()));
907         BeginScissorClipping(header_area.ul, header_area.lr);
908         GUI::GetGUI()->RenderWindow(m_header_row.get());
909         EndScissorClipping();
910     }
911 
912     if (m_first_row_shown == m_rows.end())
913         return;
914 
915     iterator last_visible_row = LastVisibleRow();
916 
917     BeginClipping();
918 
919     // draw selection hiliting
920     Y top(0);
921     Y bottom = (*m_first_row_shown)->Height();
922     for (iterator curr_sel : m_selections) {
923         if (RowAboveOrIsRow(m_first_row_shown, curr_sel, m_rows.end()) &&
924             RowAboveOrIsRow(curr_sel, last_visible_row, m_rows.end()))
925         {
926             top = std::max((*curr_sel)->Top(), cl_ul.y);
927             bottom = std::min((*curr_sel)->Top() + (*curr_sel)->Height(), cl_lr.y);
928             FlatRectangle(Pt(cl_ul.x, top), Pt(cl_lr.x, bottom),
929                           hilite_color_to_use, CLR_ZERO, 0);
930         }
931     }
932 
933     // draw caret
934     if (m_caret != m_rows.end() &&
935         RowAboveOrIsRow(m_first_row_shown, m_caret, m_rows.end()) &&
936         RowAboveOrIsRow(m_caret, last_visible_row, m_rows.end()) &&
937         MatchesOrContains(this, GUI::GetGUI()->FocusWnd().get()))
938     {
939         Pt row_ul = (*m_caret)->UpperLeft();
940         Pt row_lr = (*m_caret)->LowerRight();
941         row_lr.x = ClientLowerRight().x;
942         FlatRectangle(row_ul, row_lr, CLR_ZERO, CLR_SHADOW, 2);
943     }
944 
945     EndClipping();
946 
947     if (m_vscroll)
948         GUI::GetGUI()->RenderWindow(m_vscroll.get());
949     if (m_hscroll)
950         GUI::GetGUI()->RenderWindow(m_hscroll.get());
951 }
952 
SizeMove(const Pt & ul,const Pt & lr)953 void ListBox::SizeMove(const Pt& ul, const Pt& lr)
954 {
955     const GG::Pt old_size = Size();
956     Wnd::SizeMove(ul, lr);
957     AdjustScrolls(old_size != Size());
958     if (old_size != Size())
959         RequirePreRender();
960 }
961 
ShowVisibleRows(bool do_prerender)962 bool ListBox::ShowVisibleRows(bool do_prerender)
963 {
964     bool a_row_size_changed = false;
965     // Ensure that data in occluded cells is not rendered
966     // and that any re-layout during prerender is immediate.
967     Y visible_height(BORDER_THICK);
968     Y max_visible_height = ClientSize().y;
969     bool hide = true;
970     for (iterator it = m_rows.begin(); it != m_rows.end(); ++it) {
971         if (it == m_first_row_shown)
972             hide = false;
973 
974         if (hide) {
975             (*it)->Hide();
976         } else {
977             (*it)->Show();
978             if (do_prerender) {
979                 auto old_size = (*it)->Size();
980                 GUI::PreRenderWindow(it->get());
981                 a_row_size_changed |= (old_size != (*it)->Size());
982             }
983 
984             visible_height += (*it)->Height();
985             if (visible_height >= max_visible_height)
986                 hide = true;
987         }
988     }
989 
990     return a_row_size_changed;
991 }
992 
Show()993 void ListBox::Show()
994 {
995     Control::Show();
996 
997     // Deal with the header row and non row children normally
998     for (auto& wnd : Children()) {
999         const Row* row(dynamic_cast<Row*>(wnd.get()));
1000         bool is_regular_row(row && row != m_header_row.get());
1001         if (!is_regular_row)
1002             wnd->Show();
1003     }
1004 
1005     // Show rows that will be visible when rendered but don't prerender them.
1006     ShowVisibleRows(false);
1007 }
1008 
Disable(bool b)1009 void ListBox::Disable(bool b/* = true*/)
1010 {
1011     Control::Disable(b);
1012     if (m_vscroll)
1013         m_vscroll->Disable(b);
1014     if (m_hscroll)
1015         m_hscroll->Disable(b);
1016 }
1017 
SetColor(Clr c)1018 void ListBox::SetColor(Clr c)
1019 {
1020     Control::SetColor(c);
1021     if (m_vscroll)
1022         m_vscroll->SetColor(c);
1023     if (m_hscroll)
1024         m_hscroll->SetColor(c);
1025 }
1026 
Insert(std::shared_ptr<Row> row,iterator it)1027 ListBox::iterator ListBox::Insert(std::shared_ptr<Row> row, iterator it)
1028 { return Insert(std::forward<std::shared_ptr<Row>>(row), it, false); }
1029 
Insert(std::shared_ptr<Row> row)1030 ListBox::iterator ListBox::Insert(std::shared_ptr<Row> row)
1031 { return Insert(std::forward<std::shared_ptr<Row>>(row), m_rows.end(), false); }
1032 
Insert(const std::vector<std::shared_ptr<Row>> & rows,iterator it)1033 void ListBox::Insert(const std::vector<std::shared_ptr<Row>>& rows, iterator it)
1034 { Insert(rows, it, false); }
1035 
Insert(const std::vector<std::shared_ptr<Row>> & rows)1036 void ListBox::Insert(const std::vector<std::shared_ptr<Row>>& rows)
1037 { Insert(rows, m_rows.end(), false); }
1038 
Erase(iterator it,bool signal)1039 std::shared_ptr<ListBox::Row> ListBox::Erase(iterator it, bool signal/* = false*/)
1040 { return Erase(it, false, signal); }
1041 
Clear()1042 void ListBox::Clear()
1043 {
1044     m_rows.clear();
1045     m_caret = m_rows.end();
1046     DetachChild(m_header_row.get());
1047     DetachChildren();
1048     AttachChild(m_header_row);
1049     m_first_row_offset = Pt(X(BORDER_THICK), Y(BORDER_THICK));
1050     m_first_row_shown = m_rows.end();
1051     m_first_col_shown = 0;
1052     m_selections.clear();
1053     m_old_sel_row = m_rows.end();
1054     m_old_rdown_row = m_rows.end();
1055     m_lclick_row = m_rows.end();
1056     m_rclick_row = m_rows.end();
1057     m_last_row_browsed = m_rows.end();
1058 
1059     if (!m_keep_col_widths) { // remove column widths and alignments, if needed
1060         m_col_widths.clear();
1061         m_col_alignments.clear();
1062         m_col_stretches.clear();
1063 
1064         if (m_manage_column_props)
1065             m_num_cols = 1;
1066     }
1067 
1068     DetachChildAndReset(m_vscroll);
1069     DetachChildAndReset(m_hscroll);
1070 
1071     RequirePreRender();
1072     ClearedRowsSignal();
1073 }
1074 
SelectRow(iterator it,bool signal)1075 void ListBox::SelectRow(iterator it, bool signal/* = false*/)
1076 {
1077     if (m_style & LIST_NOSEL)
1078         return;
1079     if (it == m_rows.end())
1080         return;
1081     if (m_selections.count(it))
1082         return;
1083 
1084     SelectionSet previous_selections = m_selections;
1085 
1086     if (m_style & LIST_SINGLESEL)
1087         m_selections.clear();
1088 
1089     m_selections.insert(it);
1090 
1091     if (signal && previous_selections != m_selections)
1092         SelRowsChangedSignal(m_selections);
1093 }
1094 
DeselectRow(iterator it,bool signal)1095 void ListBox::DeselectRow(iterator it, bool signal/* = false*/)
1096 {
1097     SelectionSet previous_selections = m_selections;
1098 
1099     if (it == m_rows.end())  // always check that an iterator is valid before attempting a search for it
1100         return;
1101     if (m_selections.count(it))
1102         m_selections.erase(it);
1103 
1104     if (signal && previous_selections != m_selections)
1105         SelRowsChangedSignal(m_selections);
1106 }
1107 
SelectAll(bool signal)1108 void ListBox::SelectAll(bool signal/* = false*/)
1109 {
1110     if (m_style & LIST_NOSEL)
1111         return;
1112 
1113     SelectionSet previous_selections = m_selections;
1114 
1115     if (m_style & LIST_SINGLESEL) {
1116         if (m_selections.empty() && !m_rows.empty()) {
1117             m_selections.insert(m_rows.begin());
1118         }
1119     } else {
1120         if (m_selections.size() != m_rows.size()) {
1121             m_selections.clear();
1122             for (iterator it = m_rows.begin(); it != m_rows.end(); ++it)
1123                 m_selections.insert(it);
1124         }
1125     }
1126 
1127     if (signal && previous_selections != m_selections)
1128         SelRowsChangedSignal(m_selections);
1129 }
1130 
DeselectAll(bool signal)1131 void ListBox::DeselectAll(bool signal/* = false*/)
1132 {
1133     SelectionSet previous_selections = m_selections;
1134 
1135     if (!m_selections.empty()) {
1136         m_selections.clear();
1137         m_caret = m_rows.end();
1138     }
1139 
1140     if (signal && previous_selections != m_selections)
1141         SelRowsChangedSignal(m_selections);
1142 }
1143 
begin()1144 ListBox::iterator ListBox::begin()
1145 { return m_rows.begin(); }
1146 
end()1147 ListBox::iterator ListBox::end()
1148 { return m_rows.end(); }
1149 
GetRow(std::size_t n)1150 ListBox::Row& ListBox::GetRow(std::size_t n)
1151 {
1152     assert(n < m_rows.size());
1153     return **std::next(m_rows.begin(), n);
1154 }
1155 
SetSelections(const SelectionSet & s,bool signal)1156 void ListBox::SetSelections(const SelectionSet& s, bool signal/* = false*/)
1157 {
1158     if (m_style & LIST_NOSEL)
1159         return;
1160 
1161     SelectionSet previous_selections = m_selections;
1162 
1163     m_selections = s;
1164 
1165     if (signal && previous_selections != m_selections)
1166         SelRowsChangedSignal(m_selections);
1167 }
1168 
SetCaret(iterator it)1169 void ListBox::SetCaret(iterator it)
1170 { m_caret = it; }
1171 
BringRowIntoView(iterator target)1172 void ListBox::BringRowIntoView(iterator target)
1173 {
1174     if (target == m_rows.end())
1175         return;
1176 
1177     // m_first_row_shown only equals end() if the list is empty, hence 'it' is invalid.
1178     if (m_first_row_shown == m_rows.end())
1179         return;
1180 
1181     // Find the y offsets of the first and last shown rows and target.
1182     auto first_row_found(false);
1183     auto last_row_found(false);
1184     auto target_found(false);
1185 
1186     auto y_offset_top(Y0);
1187     auto y_offset_bot(Y0);
1188     auto target_y_offset(Y0);
1189 
1190     auto first_row_y_offset(Y0);
1191     auto last_row_y_offset(Y0);
1192 
1193     auto final_row = --m_rows.end();
1194     auto it = m_rows.begin();
1195 
1196     while ((it != m_rows.end()) && (!first_row_found || !last_row_found || !target_found)) {
1197         y_offset_bot += (*it)->Height();
1198 
1199         if (it == m_first_row_shown) {
1200             first_row_y_offset = y_offset_top;
1201             first_row_found = true;
1202         }
1203 
1204         if (it == target) {
1205             target_y_offset = y_offset_top;
1206             target_found = true;
1207         }
1208 
1209         if (first_row_found && !last_row_found
1210             && (((y_offset_bot - first_row_y_offset) >= ClientHeight())
1211                 || it == final_row))
1212         {
1213             last_row_found = true;
1214             last_row_y_offset = y_offset_top;
1215         }
1216 
1217         y_offset_top = y_offset_bot;
1218         ++it;
1219     }
1220 
1221     if (!target_found)
1222         return;
1223 
1224     if (y_offset_bot <= ClientHeight())
1225         SetFirstRowShown(begin());
1226 
1227     // Shift the view if target is outside of [first_row .. last_row]
1228     if (target_y_offset < first_row_y_offset)
1229         SetFirstRowShown(target);
1230     else if (target_y_offset >= last_row_y_offset)
1231         SetFirstRowShown(FirstRowShownWhenBottomIs(target));
1232 }
1233 
SetFirstRowShown(iterator it)1234 void ListBox::SetFirstRowShown(iterator it)
1235 {
1236     if (it == m_rows.end() && !m_rows.empty())
1237         return;
1238 
1239     RequirePreRender();
1240 
1241     m_first_row_shown = it;
1242 
1243     AdjustScrolls(false);
1244 }
1245 
SetVScrollWheelIncrement(unsigned int increment)1246 void ListBox::SetVScrollWheelIncrement(unsigned int increment)
1247 {
1248     m_vscroll_wheel_scroll_increment = increment;
1249     AdjustScrolls(false);
1250 }
1251 
SetHScrollWheelIncrement(unsigned int increment)1252 void ListBox::SetHScrollWheelIncrement(unsigned int increment)
1253 {
1254     m_hscroll_wheel_scroll_increment = increment;
1255     AdjustScrolls(false);
1256 }
1257 
SetInteriorColor(Clr c)1258 void ListBox::SetInteriorColor(Clr c)
1259 { m_int_color = c; }
1260 
SetHiliteColor(Clr c)1261 void ListBox::SetHiliteColor(Clr c)
1262 { m_hilite_color = c; }
1263 
SetStyle(Flags<ListBoxStyle> s)1264 void ListBox::SetStyle(Flags<ListBoxStyle> s)
1265 {
1266     Flags<ListBoxStyle> old_style = m_style;
1267     m_style = s;
1268     ValidateStyle();
1269 
1270     // if we're going from an unsorted style to a sorted one, do the sorting now
1271     if (old_style & LIST_NOSORT) {
1272         if (!(m_style & LIST_NOSORT))
1273             Resort();
1274     // if we're changing the sorting order of a sorted list, reverse the contents
1275     } else if (static_cast<bool>(old_style & LIST_SORTDESCENDING) !=
1276                static_cast<bool>(m_style & LIST_SORTDESCENDING)) {
1277         Resort();
1278     }
1279 }
1280 
SetColHeaders(const std::shared_ptr<Row> & r)1281 void ListBox::SetColHeaders(const std::shared_ptr<Row>& r)
1282 {
1283     Y client_height = ClientHeight();
1284     DetachChildAndReset(m_header_row);
1285     if (r) {
1286         m_header_row = r;
1287         // if this column header is being added to an empty listbox, the listbox takes on some of the
1288         // attributes of the header, similar to the insertion of a row into an empty listbox; see Insert()
1289         if (m_manage_column_props && m_rows.empty() && m_col_widths.empty()) {
1290             m_num_cols = m_header_row->size();
1291             m_col_widths.resize(m_header_row->size(),
1292                                 ClientWidth() / static_cast<int>(m_header_row->size()));
1293             // put the remainder in the last column, so the total width == ClientWidth()
1294             m_col_widths.back() += ClientWidth() % static_cast<int>(m_header_row->size());
1295             m_col_alignments.resize(m_header_row->size(), AlignmentFromStyle(m_style));
1296             m_col_stretches.resize(m_header_row->size(), 0.0);
1297         }
1298         m_header_row->MoveTo(Pt(X0, -m_header_row->Height()));
1299         AttachChild(m_header_row);
1300     } else {
1301         m_header_row = Wnd::Create<Row>();
1302     }
1303     if (client_height != ClientHeight())
1304         AdjustScrolls(true);
1305 }
1306 
RemoveColHeaders()1307 void ListBox::RemoveColHeaders()
1308 { SetColHeaders(nullptr); }
1309 
SetNumCols(std::size_t n)1310 void ListBox::SetNumCols(std::size_t n)
1311 {
1312     assert(n);
1313     m_num_cols = n;
1314     if (m_manage_column_props) {
1315         if (m_col_widths.size()) {
1316             m_col_widths.resize(n);
1317             m_col_alignments.resize(n, ALIGN_NONE);
1318             m_col_stretches.resize(n, 0.0);
1319         } else {
1320             m_col_widths.resize(n, ClientSize().x / static_cast<int>(n));
1321             m_col_widths.back() += ClientSize().x % static_cast<int>(n);
1322             Alignment alignment = ALIGN_NONE;
1323             if (m_style & LIST_LEFT)
1324                 alignment = ALIGN_LEFT;
1325             if (m_style & LIST_CENTER)
1326                 alignment = ALIGN_CENTER;
1327             if (m_style & LIST_RIGHT)
1328                 alignment = ALIGN_RIGHT;
1329             m_col_alignments.resize(n, alignment);
1330             m_col_stretches.resize(n, 0.0);
1331         }
1332     }
1333 
1334     if (n <= m_sort_col)
1335         m_sort_col = 0;
1336 
1337     RequirePreRender();
1338 }
1339 
SetColWidth(std::size_t n,X w)1340 void ListBox::SetColWidth(std::size_t n, X w)
1341 {
1342     if (m_num_cols < n + 1)
1343         m_num_cols = n + 1;
1344     if (m_col_widths.size() < n + 1)
1345         m_col_widths.resize(n + 1);
1346 
1347     m_col_widths[n] = w;
1348     for (auto& row : m_rows) {
1349         row->SetColWidth(n, w);
1350     }
1351     AdjustScrolls(false);
1352 }
1353 
SetSortCol(std::size_t n)1354 void ListBox::SetSortCol(std::size_t n)
1355 {
1356     bool needs_resort = m_sort_col != n && !(m_style & LIST_NOSORT);
1357     if (m_num_cols < n + 1)
1358         m_num_cols = n + 1;
1359 
1360     m_sort_col = n;
1361     if (needs_resort)
1362         Resort();
1363 }
1364 
SetSortCmp(const std::function<bool (const Row &,const Row &,std::size_t)> & sort_cmp)1365 void ListBox::SetSortCmp(const std::function<bool (const Row&, const Row&, std::size_t)>& sort_cmp)
1366 {
1367     m_sort_cmp = sort_cmp;
1368     if (!(m_style & LIST_NOSORT))
1369         Resort();
1370 }
1371 
LockColWidths()1372 void ListBox::LockColWidths()
1373 {
1374     m_manage_column_props = true;
1375     m_keep_col_widths = true;
1376 }
1377 
UnLockColWidths()1378 void ListBox::UnLockColWidths()
1379 {
1380     m_manage_column_props = true;
1381     m_keep_col_widths = false;
1382 }
1383 
ManuallyManageColProps()1384 void ListBox::ManuallyManageColProps()
1385 { m_manage_column_props = false; }
1386 
SetColAlignment(std::size_t n,Alignment align)1387 void ListBox::SetColAlignment(std::size_t n, Alignment align)
1388 {
1389     if (m_num_cols < n + 1)
1390         m_num_cols = n + 1;
1391     if (m_col_alignments.size() < n + 1)
1392         m_col_alignments.resize(n + 1);
1393 
1394     m_col_alignments[n] = align;
1395     for (auto& row : m_rows) {
1396         row->SetColAlignment(n, align);
1397     }
1398 }
1399 
SetColStretch(std::size_t n,double x)1400 void ListBox::SetColStretch(std::size_t n, double x)
1401 {
1402     if (m_num_cols < n + 1)
1403         m_num_cols = n + 1;
1404     if (m_col_stretches.size() < n + 1)
1405         m_col_stretches.resize(n + 1);
1406 
1407     m_col_stretches[n] = x;
1408     for (auto& row : m_rows) {
1409         auto&& layout = row->GetLayout();
1410         if (!layout)
1411             return;
1412         layout->SetColumnStretch(n, x);
1413     }
1414 }
1415 
SetRowAlignment(iterator it,Alignment align)1416 void ListBox::SetRowAlignment(iterator it, Alignment align)
1417 { (*it)->SetRowAlignment(align); }
1418 
NormalizeRowsOnInsert(bool enable)1419 void ListBox::NormalizeRowsOnInsert(bool enable)
1420 { m_normalize_rows_on_insert = enable; }
1421 
AddPaddingAtEnd(bool enable)1422 void ListBox::AddPaddingAtEnd(bool enable)
1423 { m_add_padding_at_end = enable; }
1424 
AllowDropType(const std::string & str)1425 void ListBox::AllowDropType(const std::string& str)
1426 {
1427     // Create the set if necessary
1428     if (!m_allowed_drop_types)
1429         m_allowed_drop_types = std::unordered_set<std::string>();
1430     m_allowed_drop_types->insert(str);
1431 }
1432 
AutoScrollDuringDragDrops(bool auto_scroll)1433 void ListBox::AutoScrollDuringDragDrops(bool auto_scroll)
1434 { m_auto_scroll_during_drag_drops = auto_scroll; }
1435 
SetAutoScrollMargin(unsigned int margin)1436 void ListBox::SetAutoScrollMargin(unsigned int margin)
1437 { m_auto_scroll_margin = margin; }
1438 
SetAutoScrollInterval(unsigned int interval)1439 void ListBox::SetAutoScrollInterval(unsigned int interval)
1440 { m_auto_scroll_timer.SetInterval(interval); }
1441 
RightMargin() const1442 X ListBox::RightMargin() const
1443 { return X(m_vscroll ? SCROLL_WIDTH : 0); }
1444 
BottomMargin() const1445 Y ListBox::BottomMargin() const
1446 { return Y(m_hscroll ? SCROLL_WIDTH : 0); }
1447 
CellMargin() const1448 unsigned int ListBox::CellMargin() const
1449 { return m_cell_margin; }
1450 
RowUnderPt(const Pt & pt) const1451 ListBox::iterator ListBox::RowUnderPt(const Pt& pt) const
1452 {
1453     if (!InClient(pt))
1454         return m_rows.end();
1455 
1456     iterator retval = m_first_row_shown;
1457     Y acc = ClientUpperLeft().y;
1458     for ( ; retval != m_rows.end(); ++retval) {
1459         acc += (*retval)->Height();
1460         if (pt.y <= acc)
1461             break;
1462     }
1463     return retval;
1464 }
1465 
OldSelRow() const1466 ListBox::iterator ListBox::OldSelRow() const
1467 { return m_old_sel_row; }
1468 
OldRDownRow() const1469 ListBox::iterator ListBox::OldRDownRow() const
1470 { return m_old_rdown_row; }
1471 
LClickRow() const1472 ListBox::iterator ListBox::LClickRow() const
1473 { return m_lclick_row; }
1474 
RClickRow() const1475 ListBox::iterator ListBox::RClickRow() const
1476 { return m_rclick_row; }
1477 
AutoScrollingUp() const1478 bool ListBox::AutoScrollingUp() const
1479 { return m_auto_scrolling_up; }
1480 
AutoScrollingDown() const1481 bool ListBox::AutoScrollingDown() const
1482 { return m_auto_scrolling_down; }
1483 
AutoScrollingLeft() const1484 bool ListBox::AutoScrollingLeft() const
1485 { return m_auto_scrolling_left; }
1486 
AutoScrollingRight() const1487 bool ListBox::AutoScrollingRight() const
1488 { return m_auto_scrolling_right; }
1489 
KeyPress(Key key,std::uint32_t key_code_point,Flags<ModKey> mod_keys)1490 void ListBox::KeyPress(Key key, std::uint32_t key_code_point, Flags<ModKey> mod_keys)
1491 {
1492     if (!Disabled()) {
1493         bool bring_caret_into_view = true;
1494         switch (key) {
1495         case GGK_SPACE: // space bar (selects item under caret like a mouse click)
1496             if (m_caret != m_rows.end()) {
1497                 m_old_sel_row_selected = m_selections.count(m_caret);
1498                 ClickAtRow(m_caret, mod_keys);
1499             }
1500             break;
1501         case GGK_DELETE: // delete key
1502             if (m_style & LIST_USERDELETE) {
1503                 if (m_style & LIST_NOSEL) {
1504                     if (m_caret != m_rows.end())
1505                         Erase(m_caret, false, true);
1506                 } else {
1507                     std::vector<iterator> prev_selections(m_selections.size());
1508                     std::copy(m_selections.begin(), m_selections.end(), prev_selections.begin());
1509                     m_selections.clear();
1510                     for (iterator it : prev_selections) {
1511                         Erase(it, false, true);
1512                     }
1513                 }
1514             } else {
1515                 // Pass delete on if we ignored it
1516                 Control::KeyPress(key, key_code_point, mod_keys);
1517             }
1518             break;
1519 
1520         // vertical scrolling keys
1521         case GGK_UP: // arrow-up (not numpad arrow)
1522             if (m_caret != m_rows.end() && m_caret != m_rows.begin())
1523                 --m_caret;
1524             break;
1525         case GGK_DOWN: // arrow-down (not numpad arrow)
1526             if (m_caret != m_rows.end() && m_caret != --m_rows.end())
1527                 ++m_caret;
1528             break;
1529         case GGK_PAGEUP: // page up key (not numpad key)
1530             if (m_caret != m_rows.end()) {
1531                 Y space = ClientSize().y;
1532                 while (m_caret != m_rows.begin() && 0 < (space -= (*std::prev(m_caret))->Height())) {
1533                     --m_caret;
1534                 }
1535             }
1536             break;
1537         case GGK_PAGEDOWN: // page down key (not numpad key)
1538             if (m_caret != m_rows.end()) {
1539                 Y space = ClientSize().y;
1540                 while (m_caret != --m_rows.end() && 0 < (space -= (*m_caret)->Height())) {
1541                     ++m_caret;
1542                 }
1543             }
1544             break;
1545         case GGK_HOME: // home key (not numpad)
1546             if (m_caret != m_rows.end())
1547                 m_caret = m_rows.begin();
1548             break;
1549         case GGK_END: // end key (not numpad)
1550             if (m_caret != m_rows.end())
1551                 m_caret = --m_rows.end();
1552             break;
1553 
1554         // horizontal scrolling keys
1555         case GGK_LEFT:{ // left key (not numpad key)
1556             if (m_first_col_shown == 0)
1557                 break;
1558 
1559             --m_first_col_shown;
1560             auto&& first_row_first_child((*m_first_row_shown)->GetLayout()->Children().begin());
1561             auto first_shown_cell = *std::next(first_row_first_child, m_first_col_shown);
1562             GG::X new_scroll_offset(first_shown_cell->UpperLeft().x - UpperLeft().x - GG::X(BORDER_THICK));
1563             m_hscroll->ScrollTo(Value(new_scroll_offset));
1564             SignalScroll(*m_hscroll, true);
1565             break;}
1566         case GGK_RIGHT:{ // right key (not numpad)
1567             std::size_t num_cols((*m_first_row_shown)->GetLayout()->Children().size());
1568             if (num_cols <= 1)
1569                 break;
1570             if (LastVisibleCol() >= (num_cols - 1))
1571                 break;
1572 
1573             ++m_first_col_shown;
1574             auto&& first_row_first_child((*m_first_row_shown)->GetLayout()->Children().begin());
1575             auto first_shown_cell = *std::next(first_row_first_child, m_first_col_shown);
1576             GG::X new_scroll_offset(first_shown_cell->UpperLeft().x - UpperLeft().x - GG::X(BORDER_THICK));
1577             m_hscroll->ScrollTo(Value(new_scroll_offset));
1578             SignalScroll(*m_hscroll, true);
1579             break;}
1580 
1581         // any other key gets passed along to the parent
1582         default:
1583             Control::KeyPress(key, key_code_point, mod_keys);
1584             bring_caret_into_view = false;
1585         }
1586 
1587         if (bring_caret_into_view &&
1588             key != GGK_SPACE && key != GGK_DELETE && key != GGK_LEFT && key != GGK_RIGHT) {
1589             BringCaretIntoView();
1590         }
1591     } else {
1592         Control::KeyPress(key, key_code_point, mod_keys);
1593     }
1594 }
1595 
MouseWheel(const Pt & pt,int move,Flags<ModKey> mod_keys)1596 void ListBox::MouseWheel(const Pt& pt, int move, Flags<ModKey> mod_keys)
1597 {
1598     if (Disabled() || !m_vscroll)
1599         return;
1600     m_vscroll->ScrollLineIncr(-move);
1601     SignalScroll(*m_vscroll, true);
1602 }
1603 
DragDropEnter(const Pt & pt,std::map<const Wnd *,bool> & drop_wnds_acceptable,Flags<ModKey> mod_keys)1604 void ListBox::DragDropEnter(const Pt& pt, std::map<const Wnd*, bool>& drop_wnds_acceptable, Flags<ModKey> mod_keys)
1605 {
1606     ResetAutoScrollVars();
1607     DragDropHere(pt, drop_wnds_acceptable, mod_keys);
1608 }
1609 
DragDropHere(const Pt & pt,std::map<const Wnd *,bool> & drop_wnds_acceptable,Flags<ModKey> mod_keys)1610 void ListBox::DragDropHere(const Pt& pt, std::map<const Wnd*, bool>& drop_wnds_acceptable, Flags<ModKey> mod_keys)
1611 {
1612     this->DropsAcceptable(drop_wnds_acceptable.begin(), drop_wnds_acceptable.end(), pt, mod_keys);
1613 
1614     if (m_rows.empty() || !m_auto_scroll_during_drag_drops || !InClient(pt))
1615         return;
1616 
1617     const Pt MARGIN_OFFSET = Pt(X(m_auto_scroll_margin), Y(m_auto_scroll_margin));
1618     Rect client_no_scroll_hole(ClientUpperLeft() + MARGIN_OFFSET, ClientLowerRight() - MARGIN_OFFSET);
1619     m_auto_scrolling_up = pt.y < client_no_scroll_hole.ul.y;
1620     m_auto_scrolling_down = client_no_scroll_hole.lr.y < pt.y;
1621     m_auto_scrolling_left = pt.x < client_no_scroll_hole.ul.x;
1622     m_auto_scrolling_right = client_no_scroll_hole.lr.x < pt.x;
1623     if (m_auto_scrolling_up || m_auto_scrolling_down || m_auto_scrolling_left || m_auto_scrolling_right) {
1624         bool acceptable_drop = false;
1625         for (auto& acceptable_wnd : drop_wnds_acceptable) {
1626             if (AllowedDropType(acceptable_wnd.first->DragDropDataType())) {
1627                 acceptable_drop = true;
1628                 break;
1629             }
1630         }
1631         if (acceptable_drop) {
1632             if (!m_auto_scroll_timer.Running()) {
1633                 m_auto_scroll_timer.Reset(GUI::GetGUI()->Ticks());
1634                 m_auto_scroll_timer.Start();
1635             }
1636         } else {
1637             DragDropLeave();
1638         }
1639     }
1640 }
1641 
DragDropLeave()1642 void ListBox::DragDropLeave()
1643 { ResetAutoScrollVars(); }
1644 
CancellingChildDragDrop(const std::vector<const Wnd * > & wnds)1645 void ListBox::CancellingChildDragDrop(const std::vector<const Wnd*>& wnds)
1646 { ResetAutoScrollVars(); }
1647 
TimerFiring(unsigned int ticks,Timer * timer)1648 void ListBox::TimerFiring(unsigned int ticks, Timer* timer)
1649 {
1650     if (timer == &m_auto_scroll_timer && !m_rows.empty()) {
1651         if (m_vscroll) {
1652             if (m_auto_scrolling_up &&
1653                 m_first_row_shown != m_rows.end() &&
1654                 m_first_row_shown != m_rows.begin()) {
1655                 m_vscroll->ScrollTo(m_vscroll->PosnRange().first -
1656                                     Value((*std::prev(m_first_row_shown))->Height()));
1657                 SignalScroll(*m_vscroll, true);
1658             }
1659             if (m_auto_scrolling_down) {
1660                 iterator last_visible_row = LastVisibleRow();
1661                 if (last_visible_row != m_rows.end() &&
1662                     (last_visible_row != --m_rows.end() ||
1663                      ClientLowerRight().y < (*last_visible_row)->Bottom()))
1664                 {
1665                     m_vscroll->ScrollTo(m_vscroll->PosnRange().first +
1666                                         Value((*m_first_row_shown)->Height()));
1667                     SignalScroll(*m_vscroll, true);
1668                 }
1669             }
1670         }
1671         if (m_hscroll) {
1672             if (m_auto_scrolling_left && 0 < m_first_col_shown) {
1673                 m_hscroll->ScrollTo(m_hscroll->PosnRange().first -
1674                                     Value(m_col_widths[m_first_col_shown - 1]));
1675                 SignalScroll(*m_hscroll, true);
1676             }
1677             if (m_auto_scrolling_right) {
1678                 std::size_t last_visible_col = LastVisibleCol();
1679                 if (last_visible_col < m_col_widths.size() - 1 ||
1680                     ClientLowerRight().x < m_rows.front()->Right()) {
1681                     m_hscroll->ScrollTo(m_hscroll->PosnRange().first +
1682                                         Value(m_col_widths[m_first_col_shown]));
1683                     SignalScroll(*m_hscroll, true);
1684                 }
1685             }
1686         }
1687     }
1688 }
1689 
EventFilter(Wnd * w,const WndEvent & event)1690 bool ListBox::EventFilter(Wnd* w, const WndEvent& event)
1691 {
1692     assert(w == this || dynamic_cast<Row*>(w));
1693 
1694     if (Disabled())
1695         return true;
1696 
1697     Pt pt = event.Point();
1698     Flags<ModKey> mod_keys = event.ModKeys();
1699 
1700     switch (event.Type()) {
1701     case WndEvent::LButtonDown: {
1702         m_old_sel_row = RowUnderPt(pt);
1703         if (m_old_sel_row != m_rows.end()) {
1704             m_old_sel_row_selected = m_selections.count(m_old_sel_row);
1705             if (!(m_style & LIST_NOSEL) && !m_old_sel_row_selected)
1706                 ClickAtRow(m_old_sel_row, mod_keys);
1707         }
1708         break;
1709     }
1710 
1711     case WndEvent::LButtonUp: {
1712         m_old_sel_row = m_rows.end();
1713         break;
1714     }
1715 
1716     case WndEvent::LClick: {
1717         if (m_old_sel_row != m_rows.end()) {
1718             iterator sel_row = RowUnderPt(pt);
1719             if (sel_row == m_old_sel_row) {
1720                 if (m_style & LIST_NOSEL)
1721                     m_caret = sel_row;
1722                 else
1723                     ClickAtRow(sel_row, mod_keys);
1724                 m_lclick_row = sel_row;
1725                 LeftClickedRowSignal(sel_row, pt, mod_keys);
1726             }
1727         }
1728         break;
1729     }
1730 
1731     case WndEvent::LDoubleClick: {
1732         iterator row = RowUnderPt(pt);
1733         if (row != m_rows.end() && row == m_lclick_row) {
1734             DoubleClickedRowSignal(row, pt, mod_keys);
1735             m_old_sel_row = m_rows.end();
1736         } else {
1737             LClick(pt, mod_keys);
1738         }
1739         break;
1740     }
1741 
1742     case WndEvent::RButtonDown: {
1743         iterator row = RowUnderPt(pt);
1744         if (row != m_rows.end())
1745             m_old_rdown_row = row;
1746         else
1747             m_old_rdown_row = m_rows.end();
1748         break;
1749     }
1750 
1751     case WndEvent::RClick: {
1752         iterator row = RowUnderPt(pt);
1753         if (row != m_rows.end() && row == m_old_rdown_row) {
1754             m_rclick_row = row;
1755             RightClickedRowSignal(row, pt, mod_keys);
1756         }
1757         m_old_rdown_row = m_rows.end();
1758         break;
1759     }
1760 
1761     case WndEvent::MouseEnter: {
1762         if (m_style & LIST_BROWSEUPDATES) {
1763             iterator sel_row = RowUnderPt(pt);
1764             if (sel_row != m_rows.end() && m_last_row_browsed != sel_row)
1765                 BrowsedRowSignal(m_last_row_browsed = sel_row);
1766         }
1767         break;
1768     }
1769 
1770     case WndEvent::MouseHere:
1771         break;
1772 
1773     case WndEvent::MouseLeave: {
1774         if (m_style & LIST_BROWSEUPDATES) {
1775             if (m_last_row_browsed != m_rows.end())
1776                 BrowsedRowSignal(m_last_row_browsed = m_rows.end());
1777         }
1778         break;
1779     }
1780 
1781     case WndEvent::GainingFocus: {
1782         if (w == this)
1783             return false;
1784         GUI::GetGUI()->SetFocusWnd(shared_from_this());
1785         break;
1786     }
1787 
1788     case WndEvent::MouseWheel:
1789         return false;
1790 
1791     case WndEvent::DragDropEnter:
1792     case WndEvent::DragDropHere:
1793     case WndEvent::CheckDrops:
1794     case WndEvent::DragDropLeave:
1795     case WndEvent::DragDroppedOn:
1796         if (w == this)
1797             return false;
1798         //std::cout << "ListBox::EventFilter of type: " << EventTypeName(event) << std::endl;
1799         HandleEvent(event);
1800         break;
1801 
1802     case WndEvent::KeyPress:
1803     case WndEvent::KeyRelease:
1804     case WndEvent::TimerFiring:
1805         return false;
1806 
1807     default:
1808         break;
1809     }
1810 
1811     return true;
1812 }
1813 
DefineColWidths(const Row & row)1814 void ListBox::DefineColWidths(const Row& row)
1815 {
1816     const GG::X WIDTH = ClientSize().x - SCROLL_WIDTH;
1817 
1818     m_col_widths.resize(row.size());
1819     GG::X total_width = GG::X0;
1820     for (std::size_t i = 0; i < row.size(); ++i) {
1821         // use the column width from the Row
1822         total_width += row.ColWidth(i);
1823     }
1824 
1825     const GG::X_d SCALE_FACTOR = 1.0 * WIDTH / total_width;
1826 
1827     GG::X total_scaled_width = GG::X0;
1828     for (std::size_t i = 0; i < row.size(); ++i) {
1829         total_scaled_width += (m_col_widths[i] = row.ColWidth(i) * SCALE_FACTOR);
1830     }
1831     m_col_widths.back() += total_scaled_width - WIDTH;
1832 }
1833 
DefineColAlignments(const Row & row)1834 void ListBox::DefineColAlignments(const Row& row)
1835 {
1836     m_col_alignments.resize(row.size());
1837     for (std::size_t i = 0; i < row.size(); ++i) {
1838         // use the column alignment from the Row, if it has been set;
1839         // otherwise, use the one dictated by the ListBoxStyle flags
1840         Alignment a = row.ColAlignment(i);
1841         if (a == ALIGN_NONE)
1842             a = AlignmentFromStyle(m_style);
1843         m_col_alignments[i] = a;
1844     }
1845 }
1846 
DefineColStretches(const Row & row)1847 void ListBox::DefineColStretches(const Row& row)
1848 {
1849     auto&& layout = GetLayout();
1850     if (!layout)
1851         return;
1852 
1853     m_col_stretches.resize(row.size());
1854     for (std::size_t i = 0; i < row.size(); ++i) {
1855         m_col_stretches[i] = layout->ColumnStretch (i);
1856     }
1857 }
1858 
Insert(std::shared_ptr<Row> row,iterator it,bool dropped)1859 ListBox::iterator ListBox::Insert(std::shared_ptr<Row> row, iterator it, bool dropped)
1860 {
1861     if(!row)
1862         return m_rows.end();
1863 
1864     // Track the originating row in case this is an intra-ListBox
1865     // drag-and-drop.
1866     iterator original_dropped_position = m_rows.end();
1867     if (dropped)
1868         original_dropped_position = std::find(m_rows.begin(), m_rows.end(), row);
1869 
1870     bool moved = (original_dropped_position != m_rows.end());
1871     std::size_t original_dropped_offset = moved ? std::distance(begin(), original_dropped_position) :0;
1872 
1873     iterator retval = it;
1874 
1875     row->InstallEventFilter(shared_from_this());
1876 
1877     BeforeInsertRowSignal(it);
1878 
1879     if (m_rows.empty()) {
1880         m_rows.push_back(row);
1881         retval = m_rows.begin();
1882     } else {
1883         if (!(m_style & LIST_NOSORT)) {
1884             retval = m_rows.begin();
1885             RowSorter cmp(m_sort_cmp, m_sort_col, m_style & LIST_SORTDESCENDING);
1886             while (retval != m_rows.end() && !cmp(row.get(), retval->get())) {
1887                 ++retval;
1888             }
1889         }
1890         retval = m_rows.insert(retval, row);
1891     }
1892 
1893     AttachChild(row);
1894 
1895     if (m_first_row_shown == m_rows.end())
1896         m_first_row_shown = m_rows.begin();
1897 
1898     if (dropped && moved) {
1899         Erase(original_dropped_position, true, false);
1900         // keep pointing at the same offset as original location after the erase
1901         original_dropped_position = begin();
1902         std::advance(original_dropped_position, original_dropped_offset);
1903     }
1904 
1905 #if BOOST_VERSION >= 106000
1906     using boost::placeholders::_1;
1907     using boost::placeholders::_2;
1908 #endif
1909 
1910     row->Hide();
1911     row->Resize(Pt(std::max(ClientWidth(), X(1)), row->Height()));
1912     row->RightClickedSignal.connect(boost::bind(&ListBox::HandleRowRightClicked, this, _1, _2));
1913 
1914     AfterInsertRowSignal(it);
1915     if (dropped)
1916         DroppedRowSignal(retval);
1917     if (moved)
1918         MovedRowSignal(retval, original_dropped_position);
1919 
1920     RequirePreRender();
1921     return retval;
1922 }
1923 
Insert(const std::vector<std::shared_ptr<Row>> & rows,iterator it,bool dropped)1924 void ListBox::Insert(const std::vector<std::shared_ptr<Row>>& rows, iterator it, bool dropped)
1925 {
1926     for (auto& row : rows)
1927     { Insert(row, it, dropped); }
1928 }
1929 
Erase(iterator it,bool removing_duplicate,bool signal)1930 std::shared_ptr<ListBox::Row> ListBox::Erase(iterator it, bool removing_duplicate, bool signal)
1931 {
1932     if (it == m_rows.end())
1933         return nullptr;
1934 
1935     RequirePreRender();
1936 
1937     auto row = *it;
1938     if (!removing_duplicate) {
1939         DetachChild(row.get());
1940         row->RemoveEventFilter(shared_from_this());
1941     }
1942 
1943     ResetIfEqual(m_old_sel_row,     it, m_rows.end());
1944     ResetIfEqual(m_old_rdown_row,   it, m_rows.end());
1945     ResetIfEqual(m_lclick_row,      it, m_rows.end());
1946     ResetIfEqual(m_rclick_row,      it, m_rows.end());
1947     ResetIfEqual(m_last_row_browsed,it, m_rows.end());
1948 
1949     bool check_first_row_and_caret_for_end = false;
1950     if (m_first_row_shown == it) {
1951         ++m_first_row_shown;
1952         check_first_row_and_caret_for_end = true;
1953     }
1954     if (m_caret == it) {
1955         ++m_caret;
1956         check_first_row_and_caret_for_end = true;
1957     }
1958 
1959     // remove row from selections and contained rows.
1960     m_selections.erase(it);
1961     m_rows.erase(it);
1962 
1963     if (check_first_row_and_caret_for_end && m_first_row_shown == m_rows.end() && !m_rows.empty())
1964         --m_first_row_shown;
1965     if (check_first_row_and_caret_for_end && m_caret == m_rows.end() && !m_rows.empty())
1966         --m_caret;
1967 
1968     return row;
1969 }
1970 
BringCaretIntoView()1971 void ListBox::BringCaretIntoView()
1972 { BringRowIntoView(m_caret); }
1973 
ResetAutoScrollVars()1974 void ListBox::ResetAutoScrollVars()
1975 {
1976     m_auto_scrolling_up = false;
1977     m_auto_scrolling_down = false;
1978     m_auto_scrolling_left = false;
1979     m_auto_scrolling_right = false;
1980     m_auto_scroll_timer.Stop();
1981 }
1982 
1983 struct ListBox::SelectionCache
1984 {
1985     std::set<std::shared_ptr<Row>> selections;
1986     std::shared_ptr<Row> caret;
1987     std::shared_ptr<Row> old_sel_row;
1988     std::shared_ptr<Row> old_rdown_row;
1989     std::shared_ptr<Row> lclick_row;
1990     std::shared_ptr<Row> rclick_row;
1991     std::shared_ptr<Row> last_row_browsed;
1992 };
1993 
1994 // TODO: change to unique_ptr with move mechanics or more the entire definition into the cpp file.
CacheSelections()1995 std::shared_ptr<ListBox::SelectionCache> ListBox::CacheSelections()
1996 {
1997     auto cache = std::make_shared<ListBox::SelectionCache>();
1998     cache->caret = IteratorToShared(m_caret, m_rows.end());
1999     for (const auto& sel : m_selections) {
2000         cache->selections.insert(*sel);
2001     }
2002     cache->old_sel_row =      IteratorToShared(m_old_sel_row, m_rows.end());
2003     cache->old_rdown_row =    IteratorToShared(m_old_rdown_row, m_rows.end());
2004     cache->lclick_row =       IteratorToShared(m_lclick_row, m_rows.end());
2005     cache->rclick_row =       IteratorToShared(m_rclick_row, m_rows.end());
2006     cache->last_row_browsed = IteratorToShared(m_last_row_browsed, m_rows.end());
2007 
2008     m_selections.clear();
2009 
2010     return cache;
2011 }
2012 
RestoreCachedSelections(const ListBox::SelectionCache & cache)2013 void ListBox::RestoreCachedSelections(const ListBox::SelectionCache& cache)
2014 {
2015     m_selections.clear();
2016 
2017     for (iterator it = m_rows.begin(); it != m_rows.end(); ++it) {
2018         auto row = *it;
2019         if (cache.caret == row)
2020             m_caret = it;
2021         if (cache.selections.count(row))
2022             m_selections.insert(it);
2023         if (cache.old_sel_row == row)
2024             m_old_sel_row = it;
2025         if (cache.old_rdown_row == row)
2026             m_old_rdown_row = it;
2027         if (cache.lclick_row == row)
2028             m_lclick_row = it;
2029         if (cache.rclick_row == row)
2030             m_rclick_row = it;
2031         if (cache.last_row_browsed == row)
2032             m_last_row_browsed = it;
2033     }
2034 }
2035 
Resort()2036 void ListBox::Resort()
2037 {
2038     std::shared_ptr<ListBox::SelectionCache> cached_selections = CacheSelections();
2039 
2040     std::vector<std::shared_ptr<Row>> rows_vec(m_rows.size());
2041     std::copy(m_rows.begin(), m_rows.end(), rows_vec.begin());
2042     std::stable_sort(rows_vec.begin(), rows_vec.end(),
2043                      RowSorter(m_sort_cmp, m_sort_col, m_style & LIST_SORTDESCENDING));
2044     m_rows.clear();
2045     m_rows.insert(m_rows.begin(), rows_vec.begin(), rows_vec.end());
2046 
2047     RequirePreRender();
2048 
2049     RestoreCachedSelections(*cached_selections);
2050 
2051     m_first_row_shown = m_rows.empty() ? m_rows.end() : m_rows.begin();
2052     SetFirstRowShown(m_first_row_shown);
2053 }
2054 
ColHeaders()2055 ListBox::Row& ListBox::ColHeaders()
2056 { return *m_header_row; }
2057 
ConnectSignals()2058 void ListBox::ConnectSignals()
2059 {
2060 #if BOOST_VERSION >= 106000
2061     using boost::placeholders::_1;
2062     using boost::placeholders::_2;
2063     using boost::placeholders::_3;
2064     using boost::placeholders::_4;
2065 #endif
2066 
2067     if (m_vscroll)
2068         m_vscroll->ScrolledSignal.connect(boost::bind(&ListBox::VScrolled, this, _1, _2, _3, _4));
2069     if (m_hscroll)
2070         m_hscroll->ScrolledSignal.connect(boost::bind(&ListBox::HScrolled, this, _1, _2, _3, _4));
2071 }
2072 
ValidateStyle()2073 void ListBox::ValidateStyle()
2074 {
2075     int dup_ct = 0;   // duplication count
2076     if (m_style & LIST_LEFT) ++dup_ct;
2077     if (m_style & LIST_RIGHT) ++dup_ct;
2078     if (m_style & LIST_CENTER) ++dup_ct;
2079     if (dup_ct != 1) {  // exactly one must be picked; when none or multiples are picked, use LIST_LEFT by default
2080         m_style &= ~(LIST_RIGHT | LIST_CENTER);
2081         m_style |= LIST_LEFT;
2082     }
2083     dup_ct = 0;
2084     if (m_style & LIST_TOP) ++dup_ct;
2085     if (m_style & LIST_BOTTOM) ++dup_ct;
2086     if (m_style & LIST_VCENTER) ++dup_ct;
2087     if (dup_ct != 1) {  // exactly one must be picked; when none or multiples are picked, use LIST_VCENTER by default
2088         m_style &= ~(LIST_TOP | LIST_BOTTOM);
2089         m_style |= LIST_VCENTER;
2090     }
2091     dup_ct = 0;
2092     if (m_style & LIST_NOSEL) ++dup_ct;
2093     if (m_style & LIST_SINGLESEL) ++dup_ct;
2094     if (m_style & LIST_QUICKSEL) ++dup_ct;
2095     if (1 < dup_ct)  // at most one of these may be picked; when multiples are picked, disable all of them
2096         m_style &= ~(LIST_NOSEL | LIST_SINGLESEL | LIST_QUICKSEL);
2097 }
2098 
ClientSizeExcludingScrolls() const2099 Pt ListBox::ClientSizeExcludingScrolls() const
2100 {
2101     // This client area calculation is used to determine if scroll should/should not be added, so
2102     // it does not include the thickness of scrolls.
2103     Pt cl_sz = (LowerRight()
2104                 - Pt(X(BORDER_THICK), Y(BORDER_THICK))
2105                 - UpperLeft()
2106                 - Pt(X(BORDER_THICK),
2107                      static_cast<int>(BORDER_THICK)
2108                      + (m_header_row->empty() ? Y0 : m_header_row->Height())));
2109     return cl_sz;
2110 }
2111 
CheckIfScrollsRequired(const std::pair<bool,bool> & force_scrolls,const boost::optional<Pt> & maybe_client_size) const2112 std::pair<boost::optional<X>, boost::optional<Y>> ListBox::CheckIfScrollsRequired(
2113     const std::pair<bool, bool>& force_scrolls,
2114     const boost::optional<Pt>& maybe_client_size) const
2115 {
2116     // Use the precalculated client size if possible.
2117     auto cl_sz = maybe_client_size ? *maybe_client_size : ClientSizeExcludingScrolls();
2118 
2119     X total_x_extent = std::accumulate(m_col_widths.begin(), m_col_widths.end(), X0);
2120     Y total_y_extent(0);
2121     for (auto& row : m_rows)
2122         total_y_extent += row->Height();
2123 
2124     bool vertical_needed =
2125         force_scrolls.second ||
2126         m_first_row_shown != m_rows.begin() ||
2127         (m_rows.size() && (cl_sz.y < total_y_extent ||
2128                            (cl_sz.y < total_y_extent - SCROLL_WIDTH &&
2129                             cl_sz.x < total_x_extent - SCROLL_WIDTH)));
2130     bool horizontal_needed =
2131         force_scrolls.first ||
2132         m_first_col_shown ||
2133         (m_rows.size() && (cl_sz.x < total_x_extent ||
2134                            (cl_sz.x < total_x_extent - SCROLL_WIDTH &&
2135                             cl_sz.y < total_y_extent - SCROLL_WIDTH)));
2136 
2137     if (m_add_padding_at_end) {
2138         // This probably looks a little odd. We only want to show scrolls if they
2139         // are needed, that is if the data shown exceed the bounds of the client
2140         // area. However, if we are going to show scrolls, we want to allow them
2141         // to range such that the first row/column shown can be any of the N
2142         // rows/columns. This is necessary since otherwise the bottom row may get
2143         // cut off. Dead space after the last row/column is the result, even if it
2144         // may look slightly ugly.
2145         if (!m_col_widths.empty() && m_col_widths.back() < cl_sz.x)
2146             total_x_extent += cl_sz.x - m_col_widths.back();
2147         if (!m_rows.empty() && m_rows.back()->Height() < cl_sz.y)
2148             total_y_extent += cl_sz.y - m_rows.back()->Height();
2149     }
2150 
2151     boost::optional<X> x_retval = horizontal_needed ? boost::optional<X>(total_x_extent) : boost::none;
2152     boost::optional<Y> y_retval = vertical_needed   ? boost::optional<Y>(total_y_extent) : boost::none;
2153 
2154     return {x_retval, y_retval};
2155 }
2156 
AddOrRemoveScrolls(const std::pair<boost::optional<X>,boost::optional<Y>> & required_total_extents,const boost::optional<Pt> & maybe_client_size)2157 std::pair<bool, bool> ListBox::AddOrRemoveScrolls(
2158     const std::pair<boost::optional<X>, boost::optional<Y>>& required_total_extents,
2159     const boost::optional<Pt>& maybe_client_size)
2160 {
2161     // Use the precalculated client size if possible.
2162     auto cl_sz = maybe_client_size ? *maybe_client_size : ClientSizeExcludingScrolls();
2163 
2164     const auto& style = GetStyleFactory();
2165 
2166     bool horizontal_needed = (required_total_extents.first ? true : false);
2167     bool vertical_needed = (required_total_extents.second ? true : false);
2168 
2169     bool vscroll_added_or_removed = false;
2170 
2171     // Remove unecessary vscroll
2172     if (m_vscroll && !vertical_needed) {
2173         vscroll_added_or_removed = true;
2174 
2175         //Scroll back to zero
2176         m_vscroll->ScrollTo(0);
2177         SignalScroll(*m_vscroll, true);
2178 
2179         DetachChildAndReset(m_vscroll);
2180     }
2181 
2182     // Add necessary vscroll
2183     if (!m_vscroll && vertical_needed) {
2184         vscroll_added_or_removed = true;
2185         m_vscroll = style->NewListBoxVScroll(m_color, CLR_SHADOW);
2186         m_vscroll->NonClientChild(true);
2187         m_vscroll->MoveTo(Pt(cl_sz.x - SCROLL_WIDTH, Y0));
2188         m_vscroll->Resize(Pt(X(SCROLL_WIDTH), cl_sz.y - (horizontal_needed ? SCROLL_WIDTH : 0)));
2189 
2190 #if BOOST_VERSION >= 106000
2191         using boost::placeholders::_1;
2192         using boost::placeholders::_2;
2193         using boost::placeholders::_3;
2194         using boost::placeholders::_4;
2195 #endif
2196 
2197         AttachChild(m_vscroll);
2198         m_vscroll->ScrolledSignal.connect(boost::bind(&ListBox::VScrolled, this, _1, _2, _3, _4));
2199     }
2200 
2201     if (vertical_needed) {
2202         unsigned int line_size = m_vscroll_wheel_scroll_increment;
2203         if (line_size == 0 && !this->Empty()) {
2204             const auto& row = *begin();
2205             line_size = Value(row->Height());
2206         }
2207 
2208         unsigned int page_size = std::abs(Value(cl_sz.y - (horizontal_needed ? SCROLL_WIDTH : 0)));
2209 
2210         m_vscroll->SizeScroll(0, Value(*required_total_extents.second - 1),
2211                               line_size, std::max(line_size, page_size));
2212 
2213         MoveChildUp(m_vscroll.get());
2214 
2215         // Scroll to the correct location
2216         Y acc(0);
2217         for (iterator it2 = m_rows.begin(); it2 != m_first_row_shown; ++it2)
2218             acc += (*it2)->Height();
2219         m_vscroll->ScrollTo(Value(acc));
2220         SignalScroll(*m_vscroll, true);
2221     }
2222 
2223     bool hscroll_added_or_removed = false;
2224 
2225     // Remove unecessary hscroll
2226     if (m_hscroll && !horizontal_needed) {
2227         hscroll_added_or_removed = true;
2228 
2229         //Scroll back to zero
2230         m_hscroll->ScrollTo(0);
2231         SignalScroll(*m_hscroll, true);
2232 
2233         DetachChild(m_hscroll.get());
2234         m_hscroll = nullptr;
2235     }
2236 
2237     // Add necessary hscroll
2238     if (!m_hscroll && horizontal_needed) {
2239         hscroll_added_or_removed = true;
2240         m_hscroll = style->NewListBoxHScroll(m_color, CLR_SHADOW);
2241         m_hscroll->NonClientChild(true);
2242         m_hscroll->MoveTo(Pt(X0, cl_sz.y - SCROLL_WIDTH));
2243         m_hscroll->Resize(Pt(cl_sz.x - (vertical_needed ? SCROLL_WIDTH : 0), Y(SCROLL_WIDTH)));
2244 
2245 #if BOOST_VERSION >= 106000
2246         using boost::placeholders::_1;
2247         using boost::placeholders::_2;
2248         using boost::placeholders::_3;
2249         using boost::placeholders::_4;
2250 #endif
2251 
2252         AttachChild(m_hscroll);
2253         m_hscroll->ScrolledSignal.connect(boost::bind(&ListBox::HScrolled, this, _1, _2, _3, _4));
2254     }
2255 
2256     if (horizontal_needed) {
2257         unsigned int line_size = m_hscroll_wheel_scroll_increment;
2258         if (line_size == 0 && !this->Empty()) {
2259             const auto& row = *begin();
2260             line_size = Value(row->Height());
2261         }
2262 
2263         unsigned int page_size = std::abs(Value(cl_sz.x - (vertical_needed ? SCROLL_WIDTH : 0)));
2264 
2265         m_hscroll->SizeScroll(0, Value(*required_total_extents.first - 1),
2266                               line_size, std::max(line_size, page_size));
2267         MoveChildUp(m_hscroll.get());
2268     }
2269 
2270     return {hscroll_added_or_removed, vscroll_added_or_removed};
2271 }
2272 
AdjustScrolls(bool adjust_for_resize,const std::pair<bool,bool> & force_scrolls)2273 void ListBox::AdjustScrolls(bool adjust_for_resize, const std::pair<bool, bool>& force_scrolls)
2274 {
2275     // The client size before scrolls are/are not added.
2276     const Pt cl_sz = ClientSizeExcludingScrolls();
2277 
2278     // The size of the underlying list box, indicating if scrolls are required.
2279     const auto required_total_extents = CheckIfScrollsRequired(force_scrolls, cl_sz);
2280 
2281     bool vscroll_added_or_removed;
2282     std::tie(std::ignore,  vscroll_added_or_removed) = AddOrRemoveScrolls(required_total_extents, cl_sz);
2283 
2284     if (!adjust_for_resize)
2285         return;
2286 
2287     if (m_vscroll) {
2288         X scroll_x = cl_sz.x - SCROLL_WIDTH;
2289         Y scroll_y(0);
2290         m_vscroll->SizeMove(Pt(scroll_x, scroll_y),
2291                             Pt(scroll_x + SCROLL_WIDTH,
2292                                scroll_y + cl_sz.y - (m_hscroll ? SCROLL_WIDTH : 0)));
2293     }
2294 
2295     if (m_hscroll) {
2296         X scroll_x(0);
2297         Y scroll_y = cl_sz.y - SCROLL_WIDTH;
2298         m_hscroll->SizeMove(Pt(scroll_x, scroll_y),
2299                             Pt(scroll_x + cl_sz.x - (m_vscroll ? SCROLL_WIDTH : 0),
2300                                scroll_y + SCROLL_WIDTH));
2301     }
2302 
2303     // Resize rows to fit client area.
2304     if (vscroll_added_or_removed || adjust_for_resize) {
2305         RequirePreRender();
2306         X row_width(std::max(ClientWidth(), X(1)));
2307         for (auto& row : m_rows) {
2308             row->Resize(Pt(row_width, row->Height()));
2309         }
2310     }
2311 }
2312 
VScrolled(int tab_low,int tab_high,int low,int high)2313 void ListBox::VScrolled(int tab_low, int tab_high, int low, int high)
2314 {
2315     m_first_row_shown = m_rows.empty() ? m_rows.end() : m_rows.begin();
2316     Y position(BORDER_THICK);
2317 
2318     // scan through list of rows until the tab position is less than one of the rows' centres
2319     for (iterator it = m_rows.begin(); it != m_rows.end(); ++it) {
2320         // first shown row is at least the current row, and position is at least the top of the current row
2321         m_first_row_shown = it;
2322         Y row_height = (*it)->Height();
2323 
2324         // if this is the last row, abort
2325         iterator next_it = it;  ++next_it;
2326         if (next_it == m_rows.end())
2327             break;
2328 
2329         // current row is too far for the tab position to be moved to its end. current row remains the first one shown.
2330         if (tab_low < (-position) + row_height / 2)
2331             break;
2332 
2333         // position is at least at the bottom of the current row
2334         position = position - row_height;
2335     }
2336 
2337     if (position != m_first_row_offset.y)
2338         RequirePreRender();
2339 
2340     m_first_row_offset.y = position;
2341 
2342 }
2343 
HScrolled(int tab_low,int tab_high,int low,int high)2344 void ListBox::HScrolled(int tab_low, int tab_high, int low, int high)
2345 {
2346     m_first_col_shown = 0;
2347     X accum(BORDER_THICK);
2348     X position(BORDER_THICK);
2349     for (std::size_t i = 0; i < m_col_widths.size(); ++i) {
2350         X col_width = m_col_widths[i];
2351         if (tab_low < accum + col_width / 2) {
2352             m_first_col_shown = i;
2353             position = -accum;
2354             break;
2355         }
2356         accum += col_width;
2357     }
2358 
2359     m_first_row_offset.x = position;
2360     RequirePreRender();
2361 }
2362 
ClickAtRow(iterator it,Flags<ModKey> mod_keys)2363 void ListBox::ClickAtRow(iterator it, Flags<ModKey> mod_keys)
2364 {
2365     if (it == m_rows.end())
2366         return;
2367     if (m_rows.empty())
2368         return;
2369 
2370     SelectionSet previous_selections = m_selections;
2371 
2372     if (m_style & LIST_SINGLESEL) {
2373         // No special keys are being used; just clear all previous selections,
2374         // select this row, set the caret here.
2375         m_selections.clear();
2376         m_selections.insert(it);
2377         m_caret = it;
2378 
2379     } else {
2380         if (mod_keys & MOD_KEY_CTRL) { // control key depressed
2381             if (mod_keys & MOD_KEY_SHIFT && m_caret != m_rows.end()) {
2382                 // Both shift and control keys are depressed.
2383                 iterator low  = RowPtrIteratorLess()(m_caret, it) ? m_caret : it;
2384                 iterator high = RowPtrIteratorLess()(m_caret, it) ? it : m_caret;
2385 
2386                 bool erase = !m_selections.count(m_caret);
2387                 if (high != m_rows.end())
2388                     ++high;
2389                 for (iterator it2 = low; it2 != high; ++it2) {
2390                     if (erase)
2391                         m_selections.erase(it2);
2392                     else
2393                         m_selections.insert(it2);
2394                 }
2395             } else { // just the control key is depressed: toggle the item selected, adjust the caret
2396                 if (m_old_sel_row_selected)
2397                     m_selections.erase(it);
2398                 else
2399                     m_selections.insert(it);
2400                 m_caret = it;
2401             }
2402         } else if (mod_keys & MOD_KEY_SHIFT) { // shift key depressed
2403             bool erase = m_caret != m_rows.end() && !m_selections.count(m_caret);
2404             if (!(m_style & LIST_QUICKSEL))
2405                 m_selections.clear();
2406             if (m_caret == m_rows.end()) {
2407                 // No previous caret exists; mark the first row as the caret.
2408                 m_caret = m_rows.begin();
2409             }
2410             // select all rows between the caret and this row (inclusive), don't move the caret
2411             iterator low  = RowPtrIteratorLess()(m_caret, it) ? m_caret : it;
2412             iterator high = RowPtrIteratorLess()(m_caret, it) ? it : m_caret;
2413             if (high != m_rows.end())
2414                 ++high;
2415             for (iterator it2 = low; it2 != high; ++it2) {
2416                 if (erase)
2417                     m_selections.erase(it2);
2418                 else
2419                     m_selections.insert(it2);
2420             }
2421         } else { // unless LIST_QUICKSEL is used, this is treated just like LIST_SINGLESEL above
2422             if (m_style & LIST_QUICKSEL) {
2423                 if (m_old_sel_row_selected)
2424                     m_selections.erase(it);
2425                 else
2426                     m_selections.insert(it);
2427                 m_caret = it;
2428             } else {
2429                 m_selections.clear();
2430                 m_selections.insert(it);
2431                 m_caret = it;
2432             }
2433         }
2434     }
2435 
2436     if (previous_selections != m_selections)
2437         SelRowsChangedSignal(m_selections);
2438 }
2439 
NormalizeRow(Row * row)2440 void ListBox::NormalizeRow(Row* row)
2441 {
2442     assert(m_num_cols);
2443     row->SetMargin(m_cell_margin);
2444     row->resize(m_num_cols);
2445     row->SetColWidths(m_col_widths);
2446     row->SetColAlignments(m_col_alignments);
2447     row->SetColStretches(m_col_stretches);
2448     row->Resize(Pt(ClientWidth(), row->Height()));
2449 
2450     // Normalize row is only called during prerender.
2451     GUI::PreRenderWindow(row);
2452 }
2453 
FirstRowShownWhenBottomIs(iterator bottom_row)2454 ListBox::iterator ListBox::FirstRowShownWhenBottomIs(iterator bottom_row)
2455 {
2456     Y available_space = ClientHeight() - (*bottom_row)->Height();
2457     iterator it = bottom_row;
2458     while (it != m_rows.begin() && (*std::prev(it))->Height() <= available_space) {
2459         available_space -= (*--it)->Height();
2460     }
2461     return it;
2462 }
2463 
FirstColShownWhenRightIs(std::size_t right_col,X client_width)2464 std::size_t ListBox::FirstColShownWhenRightIs(std::size_t right_col, X client_width)
2465 {
2466     if (right_col == static_cast<std::size_t>(-1))
2467         return 0;
2468     X available_space = client_width - m_col_widths[right_col];
2469     std::size_t i = right_col;
2470     while (0 < i && m_col_widths[i - 1] <= available_space) {
2471         available_space -= m_col_widths[--i];
2472     }
2473     return i;
2474 }
2475