• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 27551410928

15 Jun 2026 01:57PM UTC coverage: 50.354% (-0.4%) from 50.749%
27551410928

Pull #1947

github

web-flow
Merge 223793751 into f299328f2
Pull Request #1947: Replace `boost::optional` by `std::optional`

35 of 103 new or added lines in 52 files covered. (33.98%)

5 existing lines in 2 files now uncovered.

23178 of 46030 relevant lines covered (50.35%)

43858.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

37.21
/libs/s25main/controls/ctrlTable.cpp
1
// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "ctrlTable.h"
6
#include "CollisionDetection.h"
7
#include "ctrlButton.h"
8
#include "ctrlScrollBar.h"
9
#include "driver/KeyEvent.h"
10
#include "driver/MouseCoords.h"
11
#include "helpers/containerUtils.h"
12
#include "helpers/mathFuncs.h"
13
#include "ogl/glFont.h"
14
#include "s25util/StringConversion.h"
15
#include "s25util/strAlgos.h"
16
#include <algorithm>
17
#include <numeric>
18
#include <sstream>
19

20
/// Return <0 for a < b; >0 for a>b and 0 for a==b
21
static int Compare(const std::string& a, const std::string& b, ctrlTable::SortType sortType)
65✔
22
{
23
    using SRT = ctrlTable::SortType;
24
    switch(sortType)
65✔
25
    {
26
        case SRT::Default:
11✔
27
        case SRT::String: return a.compare(b); break;
11✔
28
        case SRT::MapSize:
15✔
29
        {
30
            // Nach Mapgrößen-String sortieren: ZahlxZahl
31
            s25util::ClassicImbuedStream<std::istringstream> ss_a(a);
30✔
32
            s25util::ClassicImbuedStream<std::istringstream> ss_b(b);
15✔
33
            char x;
34
            int x_a, y_a, x_b, y_b;
35
            ss_a >> x_a >> x >> y_a;
15✔
36
            ss_b >> x_b >> x >> y_b;
15✔
37
            RTTR_Assert(ss_a);
15✔
38
            RTTR_Assert(ss_b);
15✔
39
            const int result = x_a * y_a - x_b * y_b;
15✔
40
            // In case of same number of nodes sort by first value
41
            return (result == 0) ? x_a - x_b : result;
15✔
42
        }
43
        break;
44
        case SRT::Number:
17✔
45
        {
46
            s25util::ClassicImbuedStream<std::istringstream> ss_a(a);
34✔
47
            s25util::ClassicImbuedStream<std::istringstream> ss_b(b);
17✔
48
            int num_a, num_b;
49
            ss_a >> num_a;
17✔
50
            ss_b >> num_b;
17✔
51
            RTTR_Assert(ss_a);
17✔
52
            RTTR_Assert(ss_b);
17✔
53
            return num_a - num_b;
17✔
54
        }
55
        break;
56
        case SRT::Date:
22✔
57
        {
58
            // Nach Datum im Format dd.mm.yyyy - hh:mm sortieren
59
            s25util::ClassicImbuedStream<std::istringstream> ss_a(a);
44✔
60
            s25util::ClassicImbuedStream<std::istringstream> ss_b(b);
44✔
61
            int d_a, d_b, m_a, m_b, y_a, y_b;
62
            char c;
63

64
            // "dd.mm.yyyy"
65
            ss_a >> d_a >> c >> m_a >> c >> y_a;
22✔
66
            ss_b >> d_b >> c >> m_b >> c >> y_b;
22✔
67

68
            RTTR_Assert(ss_a);
22✔
69
            RTTR_Assert(ss_b);
22✔
70
            if(y_a != y_b)
22✔
71
                return y_a - y_b;
5✔
72

73
            if(m_a != m_b)
17✔
74
                return m_a - m_b;
5✔
75

76
            if(d_a != d_b)
12✔
77
                return d_a - d_b;
7✔
78

79
            // " - "
80
            ss_a >> c;
5✔
81
            ss_b >> c;
5✔
82

83
            int h_a, h_b, min_a, min_b;
84

85
            // "hh:mm"
86
            ss_a >> h_a >> c >> min_a;
5✔
87
            ss_b >> h_b >> c >> min_b;
5✔
88

89
            RTTR_Assert(ss_a);
5✔
90
            RTTR_Assert(ss_b);
5✔
91
            if(h_a != h_b)
5✔
92
                return h_a - h_b;
3✔
93
            return min_a - min_b;
2✔
94
        }
95
        case SRT::Time:
×
96
        {
97
            // Sort by time with format h:mm:ss or mm:ss
98
            s25util::ClassicImbuedStream<std::istringstream> ss_a(a);
×
99
            s25util::ClassicImbuedStream<std::istringstream> ss_b(b);
×
100

101
            int seconds_a = 0;
×
102
            int seconds_b = 0;
×
103
            char c;
104

105
            // "h:mm:ss" or "mm:ss"
106
            int tmp;
107
            while(ss_a >> tmp)
×
108
            {
109
                seconds_a *= 60;
×
110
                seconds_a += tmp;
×
111
                ss_a >> c;
×
112
            }
113
            while(ss_b >> tmp)
×
114
            {
115
                seconds_b *= 60;
×
116
                seconds_b += tmp;
×
117
                ss_b >> c;
×
118
            }
119

120
            return seconds_a - seconds_b;
×
121
        }
122
        break;
123
    }
124
    return 0;
×
125
}
126

127
ctrlTable::ctrlTable(Window* parent, unsigned id, const DrawPoint& pos, const Extent& size, TextureColor tc,
1✔
128
                     const glFont* font, Columns columns)
1✔
129
    : Window(parent, id, pos, elMax(size, Extent(20, 30))), tc(tc), font(font), columns_(std::move(columns)),
1✔
130
      selection_(-1), sortColumn_(-1), sortDir_(TableSortDir::Ascending)
2✔
131
{
132
    // We use unsigned short when handling the column count
133
    if(columns_.size() > std::numeric_limits<unsigned short>::max())
1✔
134
        throw std::range_error("Maximum amount of columns exceeded");
×
135

136
    header_height = font->getHeight() + 10;
1✔
137
    line_count = (GetSize().y - header_height - 2) / font->getHeight();
1✔
138

139
    // Scrollbar hinzufügen
140
    AddScrollBar(0, DrawPoint(GetSize().x - 20, 0), Extent(20, GetSize().y), 20, tc, line_count);
1✔
141

142
    for(unsigned i = 0; i < columns_.size(); ++i)
5✔
143
    {
144
        AddTextButton(i + 1, DrawPoint(0, 0), Extent(0, header_height), tc, columns_[i].title, font);
4✔
145
    }
146

147
    ResetButtonWidths();
1✔
148
}
1✔
149

150
/**
151
 *  Größe verändern
152
 */
153
void ctrlTable::Resize(const Extent& newSize)
×
154
{
155
    const bool heightIncreased = newSize.y > GetSize().y;
×
156
    Window::Resize(newSize);
×
157
    // Make header buttons keep their size
158
    for(unsigned i = 1; i <= columns_.size(); ++i)
×
159
        GetCtrl<ctrlButton>(i)->SetHeight(header_height);
×
160

161
    auto* scrollbar = GetCtrl<ctrlScrollBar>(0);
×
162

163
    // changed height
164

165
    scrollbar->Resize(Extent(20, newSize.y));
×
166
    scrollbar->SetPos(DrawPoint(newSize.x - scrollbar->GetSize().x, 0));
×
167

168
    line_count = (newSize.y - header_height - 2) / font->getHeight();
×
169
    scrollbar->SetPageSize(line_count);
×
170

171
    // If the size was enlarged we have to check that we don't try to
172
    // display more lines than present
173
    if(heightIncreased)
×
174
        while(rows_.size() - scrollbar->GetScrollPos() < line_count && scrollbar->GetScrollPos() > 0)
×
175
            scrollbar->SetScrollPos(scrollbar->GetScrollPos() - 1);
×
176

177
    // changed width
178

179
    ResetButtonWidths();
×
180
    if(scrollbar->IsVisible())
×
181
        Msg_ScrollShow(0, true);
×
182
}
×
183

184
/**
185
 *  löscht alle Items.
186
 */
187
void ctrlTable::DeleteAllItems()
×
188
{
189
    rows_.clear();
×
190

191
    GetCtrl<ctrlScrollBar>(0)->SetRange(0);
×
192

NEW
193
    SetSelection(std::nullopt);
×
194
    sortColumn_ = -1;
×
195
    sortDir_ = TableSortDir::Ascending;
×
196
}
×
197

198
/**
199
 *  setzt die Auswahl.
200
 *
201
 *  @param[in] selection Der Auswahlindex
202
 */
NEW
203
void ctrlTable::SetSelection(const std::optional<unsigned>& selection)
×
204
{
205
    if(!selection)
×
NEW
206
        selection_ = std::nullopt;
×
207
    else if(*selection >= rows_.size())
×
208
        return;
×
209
    else
210
    {
211
        selection_ = selection;
×
212
        // Scroll into view
213
        auto* scrollbar = GetCtrl<ctrlScrollBar>(0);
×
214
        if(*selection_ < scrollbar->GetScrollPos())
×
215
            scrollbar->SetScrollPos(*selection_);
×
216
        else if(*selection_ >= static_cast<unsigned>(scrollbar->GetScrollPos() + scrollbar->GetPageSize()))
×
217
            scrollbar->SetScrollPos(*selection_ - scrollbar->GetPageSize() + 1);
×
218
    }
219

220
    if(GetParent())
×
221
        GetParent()->Msg_TableSelectItem(GetID(), selection_);
×
222
}
223

224
void ctrlTable::AddRow(std::vector<std::string> row)
6✔
225
{
226
    // We use unsigned short when handling the row count
227
    if(rows_.size() == std::numeric_limits<unsigned short>::max())
6✔
228
        throw std::range_error("Maximum amount of rows exceeded");
×
229
    if(row.size() > GetNumColumns())
6✔
230
        throw std::logic_error("Invalid number of columns for added row");
×
231
    for(unsigned i = row.size(); i < GetNumColumns(); ++i)
6✔
232
    {
233
        row.push_back("");
×
234
    }
235

236
    rows_.emplace_back(Row{std::move(row)});
6✔
237
    GetCtrl<ctrlScrollBar>(0)->SetRange(GetNumRows());
6✔
238
}
6✔
239

240
void ctrlTable::RemoveRow(unsigned rowIdx)
×
241
{
242
    if(rowIdx >= rows_.size())
×
243
        return;
×
244
    rows_.erase(rows_.begin() + rowIdx);
×
245
    GetCtrl<ctrlScrollBar>(0)->SetRange(static_cast<unsigned short>(rows_.size()));
×
246
    if(selection_ && *selection_ >= rows_.size())
×
247
    {
248
        if(rows_.empty())
×
249
            selection_.reset();
×
250
        else
251
            selection_ = rows_.size() - 1u;
×
252
    }
253
    SetSelection(selection_);
×
254
}
255

256
/**
257
 *  liefert den Wert eines Feldes.
258
 *
259
 *  @param[in] row    Die Zeile
260
 *  @param[in] column Die Spalte
261
 *
262
 *  @return Text in Feld <@p column,@p row>
263
 */
264
const std::string& ctrlTable::GetItemText(unsigned short row, unsigned short column) const
120✔
265
{
266
    static std::string empty;
120✔
267
    if(row >= rows_.size() || column >= columns_.size())
120✔
268
        return empty;
×
269

270
    return rows_[row].columns[column];
120✔
271
}
272

273
/**
274
 *  sortiert die Zeilen.
275
 *
276
 *  @param[in] column    Die Spalte nach der sortiert werden soll.
277
 *  @param[in] ascending Die Richtung in die sortiert werden soll
278
 *                         @p true  - A-Z,
279
 *                         @p false - Z-A
280
 */
281
void ctrlTable::SortRows(unsigned column, const TableSortDir sortDir)
5✔
282
{
283
    sortDir_ = sortDir;
5✔
284
    sortColumn_ = column;
5✔
285

286
    if(sortColumn_ >= GetNumColumns())
5✔
287
    {
288
        sortColumn_ = -1;
×
289
        return;
×
290
    }
291

292
    const TableSortType sortType = columns_[column].sortType;
5✔
293
    const auto compareFunc = [sortType, sortDir, column](const Row& lhs, const Row& rhs) {
260✔
294
        const std::string a = s25util::toLower(lhs.columns[column]);
130✔
295
        const std::string b = s25util::toLower(rhs.columns[column]);
65✔
296
        const int cmpResult = Compare(a, b, sortType);
65✔
297
        return (sortDir == TableSortDir::Ascending) ? cmpResult < 0 : cmpResult > 0;
130✔
298
    };
5✔
299
    helpers::sort(rows_, compareFunc);
5✔
300
}
301

302
/**
303
 *  Zeichenmethode
304
 *
305
 *  @return @p true bei Erfolg, @p false bei Fehler
306
 */
307
void ctrlTable::Draw_()
×
308
{
309
    Draw3D(Rect(GetDrawPos(), GetSize()), tc, false);
×
310

311
    Window::Draw_();
×
312

313
    const auto lines = static_cast<unsigned>(line_count > rows_.size() ? rows_.size() : line_count);
×
314
    const auto* scroll = GetCtrl<ctrlScrollBar>(0);
×
315
    DrawPoint curPos = GetDrawPos() + DrawPoint(2, 2 + header_height);
×
316
    for(unsigned i = 0; i < lines; ++i)
×
317
    {
318
        const unsigned curRow = i + scroll->GetScrollPos();
×
319
        RTTR_Assert(curRow < GetNumRows());
×
320
        const bool isSelected = selection_ == curRow;
×
321
        if(isSelected)
×
322
        {
323
            // durchsichtig schwarze Markierung malen
324
            DrawRectangle(Rect(curPos, GetSize().x - 4 - (scroll->IsVisible() ? 24 : 0), font->getHeight()),
×
325
                          0x80000000);
326
        }
327

328
        DrawPoint colPos = curPos;
×
329
        for(unsigned c = 0; c < columns_.size(); ++c)
×
330
        {
331
            if(columns_[c].width == 0)
×
332
                continue;
×
333

334
            const auto* bt = GetCtrl<ctrlButton>(c + 1);
×
335
            font->Draw(colPos, rows_[curRow].columns[c], FontStyle{}, (isSelected ? 0xFFFFAA00 : COLOR_YELLOW),
×
336
                       bt->GetSize().x, "");
×
337
            colPos.x += bt->GetSize().x;
×
338
        }
339
        curPos.y += font->getHeight();
×
340
    }
341
}
×
342

343
void ctrlTable::Msg_ButtonClick(const unsigned ctrl_id)
×
344
{
345
    RTTR_Assert(ctrl_id > 0);
×
346
    const int column = ctrl_id - 1;
×
347
    SortRows(column, column == GetSortColumn() && GetSortDirection() == TableSortDir::Ascending ?
×
348
                       TableSortDir::Descending :
349
                       TableSortDir::Ascending);
350
    SetSelection(selection_);
×
351
}
×
352

353
Rect ctrlTable::GetContentDrawArea() const
×
354
{
355
    DrawPoint orig = GetDrawPos();
×
356
    orig.y += header_height;
×
357
    Extent size = GetSize() - Extent(20, header_height);
×
358
    return Rect(orig, size);
×
359
}
360

361
Rect ctrlTable::GetFullDrawArea() const
×
362
{
363
    DrawPoint orig = GetDrawPos() + DrawPoint(2, 2);
×
364
    Extent size = GetSize() - Extent(2, 4);
×
365
    return Rect(orig, size);
×
366
}
367

368
bool ctrlTable::Msg_LeftDown(const MouseCoords& mc)
×
369
{
370
    if(IsPointInRect(mc.pos, GetContentDrawArea()))
×
371
    {
372
        SetSelection(GetSelectionFromMouse(mc));
×
373
        if(GetParent())
×
374
            GetParent()->Msg_TableLeftButton(this->GetID(), selection_);
×
375

376
        return true;
×
377
    } else
378
        return RelayMouseMessage(&Window::Msg_LeftDown, mc);
×
379
}
380

381
bool ctrlTable::Msg_RightDown(const MouseCoords& mc)
×
382
{
383
    if(IsPointInRect(mc.pos, GetContentDrawArea()))
×
384
    {
385
        SetSelection(GetSelectionFromMouse(mc));
×
386
        if(GetParent())
×
387
            GetParent()->Msg_TableRightButton(this->GetID(), selection_);
×
388

389
        return true;
×
390
    } else
391
        return RelayMouseMessage(&Window::Msg_RightDown, mc);
×
392
}
393

NEW
394
std::optional<unsigned> ctrlTable::GetSelectionFromMouse(const MouseCoords& mc) const
×
395
{
396
    const int visibleItem = (mc.pos.y - GetContentDrawArea().top) / font->getHeight();
×
397
    if(visibleItem < 0)
×
NEW
398
        return std::nullopt;
×
399
    const unsigned itemIdx = static_cast<unsigned>(visibleItem) + GetCtrl<ctrlScrollBar>(0)->GetScrollPos();
×
400
    if(itemIdx >= GetNumRows())
×
NEW
401
        return std::nullopt;
×
402
    return itemIdx;
×
403
}
404

405
bool ctrlTable::Msg_WheelUp(const MouseCoords& mc)
×
406
{
407
    // If mouse in list or scrollbar
408
    if(IsPointInRect(mc.pos, GetFullDrawArea()))
×
409
    {
410
        auto* scrollbar = GetCtrl<ctrlScrollBar>(0);
×
411
        scrollbar->Scroll(-1);
×
412
        return true;
×
413
    } else
414
        return false;
×
415
}
416

417
bool ctrlTable::Msg_WheelDown(const MouseCoords& mc)
×
418
{
419
    if(IsPointInRect(mc.pos, GetFullDrawArea()))
×
420
    {
421
        auto* scrollbar = GetCtrl<ctrlScrollBar>(0);
×
422
        scrollbar->Scroll(+1);
×
423
        return true;
×
424
    } else
425
        return false;
×
426
}
427

428
bool ctrlTable::Msg_LeftUp(const MouseCoords& mc)
×
429
{
430
    if(IsPointInRect(mc.pos, GetContentDrawArea()))
×
431
    {
432
        if(mc.dbl_click && GetParent())
×
433
        {
434
            const auto oldSelection = GetSelection();
×
435
            SetSelection(GetSelectionFromMouse(mc));
×
436
            if(selection_ && oldSelection == selection_)
×
437
            {
438
                GetParent()->Msg_TableChooseItem(this->GetID(), *selection_);
×
439
                return true;
×
440
            }
441
        }
442
    }
443
    return RelayMouseMessage(&Window::Msg_LeftUp, mc);
×
444
}
445

446
bool ctrlTable::Msg_MouseMove(const MouseCoords& mc)
×
447
{
448
    // ButtonMessages weiterleiten
449
    return RelayMouseMessage(&Window::Msg_MouseMove, mc);
×
450
}
451

452
void ctrlTable::Msg_ScrollShow(const unsigned /*ctrl_id*/, const bool /*visible*/)
×
453
{
454
    ResetButtonWidths();
×
455
}
×
456

457
void ctrlTable::ResetButtonWidths()
1✔
458
{
459
    auto addColumnWidth = [](unsigned cur, const Column& c) { return cur + c.width; };
4✔
460
    const unsigned sumWidth = std::max(1u, std::accumulate(columns_.begin(), columns_.end(), 0u, addColumnWidth));
1✔
461
    const auto* scrollbar = GetCtrl<ctrlScrollBar>(0);
1✔
462
    const unsigned availableSize =
463
      std::max<int>(0, GetSize().x - (scrollbar->IsVisible() ? scrollbar->GetSize().x : 0));
1✔
464
    const double sizeFactor = static_cast<double>(availableSize) / static_cast<double>(sumWidth);
1✔
465
    DrawPoint btPos(0, 0);
1✔
466
    for(unsigned i = 0; i < columns_.size(); ++i)
5✔
467
    {
468
        auto* button = GetCtrl<ctrlButton>(i + 1);
4✔
469
        button->SetWidth(helpers::iround<unsigned>(columns_[i].width * sizeFactor));
4✔
470
        button->SetPos(btPos);
4✔
471
        btPos.x += button->GetSize().x;
4✔
472
    }
473

474
    // Adjust last column size to cater for rounding error
475
    for(unsigned i = columns_.size(); i > 0; --i)
1✔
476
    {
477
        if(columns_[i - 1].width)
1✔
478
        {
479
            auto* bt = GetCtrl<ctrlButton>(i);
1✔
480
            bt->SetWidth(std::max(0, static_cast<int>(availableSize) - bt->GetPos().x));
1✔
481
            break;
1✔
482
        }
483
    }
484
}
1✔
485

486
bool ctrlTable::Msg_KeyDown(const KeyEvent& ke)
×
487
{
488
    switch(ke.kt)
×
489
    {
490
        default: return false;
×
491
        case KeyType::Up:
×
492
            if(selection_.value_or(0u) > 0u)
×
493
                SetSelection(*selection_ - 1);
×
494
            return true;
×
495
        case KeyType::Down: SetSelection(selection_.value_or(0u) + 1u); return true;
×
496
    }
497
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc