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