GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/guitable.cpp Lines: 62 321 19.3 %
Date: 2021-03-17 Branches: 24 258 9.3 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2008-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
6
 *  Copyright (C) 2019-2021  Andrei Karas
7
 *
8
 *  This file is part of The ManaPlus Client.
9
 *
10
 *  This program is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU General Public License as published by
12
 *  the Free Software Foundation; either version 2 of the License, or
13
 *  any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
#include "gui/widgets/guitable.h"
25
26
#include "settings.h"
27
28
#include "gui/gui.h"
29
30
#include "gui/models/tablemodel.h"
31
32
#include "listeners/guitableactionlistener.h"
33
34
#include "render/graphics.h"
35
36
#include "utils/delete2.h"
37
#include "utils/dtor.h"
38
39
#include "debug.h"
40
41
float GuiTable::mAlpha = 1.0;
42
43
4
GuiTable::GuiTable(const Widget2 *const widget,
44
                   TableModel *const initial_model,
45
4
                   const Opaque opacity) :
46
    Widget(widget),
47
    MouseListener(),
48
    KeyListener(),
49
    TableModelListener(),
50
    mModel(nullptr),
51
    mTopWidget(nullptr),
52
    mActionListeners2(),
53
8
    mHighlightColor(getThemeColor(ThemeColorId::HIGHLIGHT, 255U)),
54
    mSelectedRow(-1),
55
    mSelectedColumn(-1),
56
    mLinewiseMode(false),
57
    mWrappingEnabled(false),
58
    mOpaque(opacity),
59
24
    mSelectableGui(true)
60
{
61
4
    mAllowLogic = false;
62
8
    mBackgroundColor = getThemeColor(ThemeColorId::BACKGROUND, 255U);
63
64
4
    setModel(initial_model);
65
4
    setFocusable(true);
66
67
4
    addMouseListener(this);
68
4
    addKeyListener(this);
69
4
}
70
71
28
GuiTable::~GuiTable()
72
{
73
4
    if (gui != nullptr)
74
4
        gui->removeDragged(this);
75
76
4
    uninstallActionListeners();
77
4
    delete2(mModel)
78
8
}
79
80
const TableModel *GuiTable::getModel() const
81
{
82
    return mModel;
83
}
84
85
4
void GuiTable::setModel(TableModel *const new_model)
86
{
87
4
    if (mModel != nullptr)
88
    {
89
        uninstallActionListeners();
90
        mModel->removeListener(this);
91
    }
92
93
4
    mModel = new_model;
94
4
    installActionListeners();
95
96
4
    new_model->installListener(this);
97
4
    recomputeDimensions();
98
4
}
99
100
8
void GuiTable::recomputeDimensions()
101
{
102
8
    if (mModel == nullptr)
103
        return;
104
105
8
    const int rows_nr = mModel->getRows();
106
8
    const int columns_nr = mModel->getColumns();
107
8
    int width = 0;
108
109
8
    if (mSelectableGui)
110
    {
111
4
        if (mSelectedRow >= rows_nr)
112
            mSelectedRow = rows_nr - 1;
113
114
4
        if (mSelectedColumn >= columns_nr)
115
            mSelectedColumn = columns_nr - 1;
116
    }
117
118
40
    for (int i = 0; i < columns_nr; i++)
119
16
        width += getColumnWidth(i);
120
121
8
    setWidth(width);
122
8
    setHeight(getRowHeight() * rows_nr);
123
}
124
125
void GuiTable::setSelected(const int row, const int column)
126
{
127
    mSelectedColumn = column;
128
    mSelectedRow = row;
129
}
130
131
int GuiTable::getSelectedRow() const
132
{
133
    return mSelectedRow;
134
}
135
136
int GuiTable::getSelectedColumn() const
137
{
138
    return mSelectedColumn;
139
}
140
141
int GuiTable::getRowHeight() const
142
{
143
8
    return mModel->getRowHeight() + 4;  // border
144
}
145
146
int GuiTable::getColumnWidth(const int i) const
147
{
148
16
    return mModel->getColumnWidth(i) + 4;  // border
149
}
150
151
void GuiTable::setSelectedRow(const int selected)
152
{
153
    if (!mSelectableGui)
154
    {
155
        mSelectedRow = -1;
156
    }
157
    else
158
    {
159
        const int rows = mModel->getRows();
160
        if (selected < 0 && !mWrappingEnabled)
161
        {
162
            mSelectedRow = -1;
163
        }
164
        else if (selected >= rows && mWrappingEnabled)
165
        {
166
            mSelectedRow = 0;
167
        }
168
        else if ((selected >= rows && !mWrappingEnabled) ||
169
                 (selected < 0 && mWrappingEnabled))
170
        {
171
            mSelectedRow = rows - 1;
172
        }
173
        else
174
        {
175
            mSelectedRow = selected;
176
        }
177
    }
178
}
179
180
void GuiTable::setSelectedColumn(const int selected)
181
{
182
    const int columns = mModel->getColumns();
183
    if ((selected >= columns && mWrappingEnabled) ||
184
        (selected < 0 && !mWrappingEnabled))
185
    {
186
        mSelectedColumn = 0;
187
    }
188
    else if ((selected >= columns && !mWrappingEnabled) ||
189
             (selected < 0 && mWrappingEnabled))
190
    {
191
        mSelectedColumn = columns - 1;
192
    }
193
    else
194
    {
195
        mSelectedColumn = selected;
196
    }
197
}
198
199
8
void GuiTable::uninstallActionListeners()
200
{
201
16
    delete_all(mActionListeners2);
202
16
    mActionListeners2.clear();
203
8
}
204
205
8
void GuiTable::installActionListeners()
206
{
207
8
    const int rows = mModel->getRows();
208
8
    const int columns = mModel->getColumns();
209
210
14
    for (int row = 0; row < rows; ++row)
211
    {
212
30
        for (int column = 0; column < columns; ++column)
213
        {
214
12
            Widget *const widget = mModel->getElementAt(row, column);
215
12
            if (widget != nullptr)
216
            {
217
12
                mActionListeners2.push_back(new GuiTableActionListener(
218
12
                    this, widget, row, column));
219
            }
220
        }
221
    }
222
223
16
    setFocusHandler(getFocusHandler());
224
8
}
225
226
// -- widget ops
227
void GuiTable::draw(Graphics *const graphics)
228
{
229
    if (getRowHeight() == 0)
230
        return;
231
232
    BLOCK_START("GuiTable::draw")
233
    if (settings.guiAlpha != mAlpha)
234
        mAlpha = settings.guiAlpha;
235
236
    const Rect &rect = mDimension;
237
    const int width = rect.width;
238
    const int height = rect.height;
239
    const int y = rect.y;
240
    if (mOpaque == Opaque_true)
241
    {
242
        mBackgroundColor.a = CAST_U32(mAlpha * 255.0F);
243
        graphics->setColor(mBackgroundColor);
244
        graphics->fillRectangle(Rect(0, 0, width, height));
245
    }
246
247
    // First, determine how many rows we need to draw,
248
    // and where we should start.
249
    int rHeight = getRowHeight();
250
    if (rHeight == 0)
251
        rHeight = 1;
252
    int first_row = -(y / rHeight);
253
254
    if (first_row < 0)
255
        first_row = 0;
256
257
    unsigned int rows_nr = CAST_U32(1 +
258
        height / rHeight);  // May overestimate by one.
259
    unsigned int max_rows_nr;
260
    if (mModel->getRows() < first_row)
261
    {
262
        max_rows_nr = 0U;
263
    }
264
    else
265
    {
266
        max_rows_nr = CAST_U32(
267
            mModel->getRows() - first_row);  // clip if neccessary:
268
    }
269
    if (max_rows_nr < rows_nr)
270
        rows_nr = max_rows_nr;
271
272
    // Now determine the first and last column
273
    // Take the easy way out; these are usually bounded and all visible.
274
    const unsigned first_column = 0;
275
    const unsigned last_column1 = CAST_U32(
276
        mModel->getColumns());
277
278
    int y_offset = first_row * rHeight;
279
280
    for (unsigned int r = CAST_U32(first_row);
281
         r < CAST_U32(first_row) + rows_nr;
282
         ++r)
283
    {
284
        int x_offset = 0;
285
286
        for (unsigned c = first_column; c + 1 <= last_column1; ++c)
287
        {
288
            Widget *const widget = mModel->getElementAt(CAST_S32(r),
289
                CAST_S32(c));
290
            const int cWidth = CAST_S32(getColumnWidth(
291
                CAST_S32(c)));
292
            if (widget != nullptr)
293
            {
294
                Rect bounds(x_offset, y_offset, cWidth, rHeight);
295
296
                if (widget == mTopWidget)
297
                {
298
                    bounds.height = widget->getHeight();
299
                    bounds.width = widget->getWidth();
300
                }
301
302
                widget->setDimension(bounds);
303
304
                if (mSelectedRow > -1)
305
                {
306
                    mHighlightColor.a = CAST_U32(
307
                        mAlpha * 255.0F);
308
                    graphics->setColor(mHighlightColor);
309
310
                    if (mLinewiseMode && r == CAST_U32(
311
                        mSelectedRow) && c == 0)
312
                    {
313
                        graphics->fillRectangle(Rect(0, y_offset,
314
                            width, rHeight));
315
                    }
316
                    else if (!mLinewiseMode && mSelectedColumn > 0
317
                             && c == CAST_U32(mSelectedColumn)
318
                             && r == CAST_U32(mSelectedRow))
319
                    {
320
                        graphics->fillRectangle(Rect(
321
                            x_offset, y_offset, cWidth, rHeight));
322
                    }
323
                }
324
                graphics->pushClipArea(bounds);
325
                widget->draw(graphics);
326
                graphics->popClipArea();
327
            }
328
329
            x_offset += cWidth;
330
        }
331
332
        y_offset += rHeight;
333
    }
334
335
    if (mTopWidget != nullptr)
336
    {
337
        const Rect &bounds = mTopWidget->getDimension();
338
        graphics->pushClipArea(bounds);
339
        mTopWidget->draw(graphics);
340
        graphics->popClipArea();
341
    }
342
    BLOCK_END("GuiTable::draw")
343
}
344
345
void GuiTable::safeDraw(Graphics *const graphics)
346
{
347
    if (getRowHeight() == 0)
348
        return;
349
350
    BLOCK_START("GuiTable::draw")
351
    if (settings.guiAlpha != mAlpha)
352
        mAlpha = settings.guiAlpha;
353
354
    const Rect &rect = mDimension;
355
    const int width = rect.width;
356
    const int height = rect.height;
357
    const int y = rect.y;
358
    if (mOpaque == Opaque_true)
359
    {
360
        mBackgroundColor.a = CAST_U32(mAlpha * 255.0F);
361
        graphics->setColor(mBackgroundColor);
362
        graphics->fillRectangle(Rect(0, 0, width, height));
363
    }
364
365
    // First, determine how many rows we need to draw,
366
    // and where we should start.
367
    int rHeight = getRowHeight();
368
    if (rHeight == 0)
369
        rHeight = 1;
370
    int first_row = -(y / rHeight);
371
372
    if (first_row < 0)
373
        first_row = 0;
374
375
    unsigned int rows_nr = CAST_U32(
376
        1 + height / rHeight);  // May overestimate by one.
377
    unsigned int max_rows_nr;
378
    if (mModel->getRows() < first_row)
379
    {
380
        max_rows_nr = 0;
381
    }
382
    else
383
    {
384
        max_rows_nr = CAST_U32(
385
            mModel->getRows() - first_row);  // clip if neccessary:
386
    }
387
    if (max_rows_nr < rows_nr)
388
        rows_nr = max_rows_nr;
389
390
    // Now determine the first and last column
391
    // Take the easy way out; these are usually bounded and all visible.
392
    const unsigned int first_column = 0;
393
    const unsigned int last_column1 = CAST_U32(
394
        mModel->getColumns());
395
396
    int y_offset = first_row * rHeight;
397
398
    for (unsigned int r = CAST_U32(first_row);
399
         r < CAST_U32(first_row + CAST_S32(rows_nr));
400
         ++r)
401
    {
402
        int x_offset = 0;
403
404
        for (unsigned c = first_column; c + 1 <= last_column1; ++c)
405
        {
406
            Widget *const widget = mModel->getElementAt(CAST_S32(r),
407
                CAST_S32(c));
408
            const int cWidth = CAST_S32(getColumnWidth(
409
                CAST_S32(c)));
410
            if (widget != nullptr)
411
            {
412
                Rect bounds(x_offset, y_offset, cWidth, rHeight);
413
414
                if (widget == mTopWidget)
415
                {
416
                    bounds.height = widget->getHeight();
417
                    bounds.width = widget->getWidth();
418
                }
419
420
                widget->setDimension(bounds);
421
422
                if (mSelectedRow > -1)
423
                {
424
                    mHighlightColor.a = CAST_U32(
425
                        mAlpha * 255.0F);
426
                    graphics->setColor(mHighlightColor);
427
428
                    if (mLinewiseMode && r == CAST_U32(
429
                        mSelectedRow) && c == 0)
430
                    {
431
                        graphics->fillRectangle(Rect(0, y_offset,
432
                            width, rHeight));
433
                    }
434
                    else if (!mLinewiseMode && mSelectedColumn > 0
435
                             && c == CAST_U32(mSelectedColumn)
436
                             && r == CAST_U32(mSelectedRow))
437
                    {
438
                        graphics->fillRectangle(Rect(
439
                            x_offset, y_offset, cWidth, rHeight));
440
                    }
441
                }
442
                graphics->pushClipArea(bounds);
443
                widget->safeDraw(graphics);
444
                graphics->popClipArea();
445
            }
446
447
            x_offset += cWidth;
448
        }
449
450
        y_offset += rHeight;
451
    }
452
453
    if (mTopWidget != nullptr)
454
    {
455
        const Rect &bounds = mTopWidget->getDimension();
456
        graphics->pushClipArea(bounds);
457
        mTopWidget->safeDraw(graphics);
458
        graphics->popClipArea();
459
    }
460
    BLOCK_END("GuiTable::draw")
461
}
462
463
void GuiTable::moveToTop(Widget *const widget)
464
{
465
    Widget::moveToTop(widget);
466
    mTopWidget = widget;
467
}
468
469
void GuiTable::moveToBottom(Widget *const widget)
470
{
471
    Widget::moveToBottom(widget);
472
    if (widget == mTopWidget)
473
        mTopWidget = nullptr;
474
}
475
476
Rect GuiTable::getChildrenArea()
477
{
478
    return Rect(0, 0, mDimension.width, mDimension.height);
479
}
480
481
// -- KeyListener notifications
482
void GuiTable::keyPressed(KeyEvent& event)
483
{
484
    const InputActionT action = event.getActionId();
485
486
    if (action == InputAction::GUI_SELECT)
487
    {
488
        distributeActionEvent();
489
        event.consume();
490
    }
491
    else if (action == InputAction::GUI_UP)
492
    {
493
        setSelectedRow(mSelectedRow - 1);
494
        event.consume();
495
    }
496
    else if (action == InputAction::GUI_DOWN)
497
    {
498
        setSelectedRow(mSelectedRow + 1);
499
        event.consume();
500
    }
501
    else if (action == InputAction::GUI_LEFT)
502
    {
503
        setSelectedColumn(mSelectedColumn - 1);
504
        event.consume();
505
    }
506
    else if (action == InputAction::GUI_RIGHT)
507
    {
508
        setSelectedColumn(mSelectedColumn + 1);
509
        event.consume();
510
    }
511
    else if (action == InputAction::GUI_HOME)
512
    {
513
        setSelectedRow(0);
514
        setSelectedColumn(0);
515
        event.consume();
516
    }
517
    else if (action == InputAction::GUI_END && (mModel != nullptr))
518
    {
519
        setSelectedRow(mModel->getRows() - 1);
520
        setSelectedColumn(mModel->getColumns() - 1);
521
        event.consume();
522
    }
523
}
524
525
// -- MouseListener notifications
526
void GuiTable::mousePressed(MouseEvent& event)
527
{
528
    if (!mSelectableGui)
529
        return;
530
531
    if (event.getButton() == MouseButton::LEFT)
532
    {
533
        const int row = getRowForY(event.getY());
534
        const int column = getColumnForX(event.getX());
535
536
        if (row > -1 && column > -1 &&
537
            row < mModel->getRows() && column < mModel->getColumns())
538
        {
539
            mSelectedColumn = column;
540
            mSelectedRow = row;
541
            event.consume();
542
        }
543
544
        distributeActionEvent();
545
    }
546
}
547
548
void GuiTable::mouseWheelMovedUp(MouseEvent& event)
549
{
550
    if (isFocused())
551
    {
552
        const int selRow = getSelectedRow();
553
        if (selRow > 0 || (selRow == 0 && mWrappingEnabled))
554
            setSelectedRow(selRow - 1);
555
        event.consume();
556
    }
557
}
558
559
void GuiTable::mouseWheelMovedDown(MouseEvent& event)
560
{
561
    if (isFocused())
562
    {
563
        setSelectedRow(getSelectedRow() + 1);
564
        event.consume();
565
    }
566
}
567
568
void GuiTable::mouseDragged(MouseEvent& event)
569
{
570
    if (event.getButton() != MouseButton::LEFT)
571
        return;
572
573
    // Make table selection update on drag
574
    const int x = std::max(0, event.getX());
575
    const int y = std::max(0, event.getY());
576
577
    setSelectedRow(getRowForY(y));
578
    setSelectedColumn(getColumnForX(x));
579
}
580
581
8
void GuiTable::modelUpdated(const bool completed)
582
{
583
8
    if (completed)
584
    {
585
4
        recomputeDimensions();
586
4
        installActionListeners();
587
    }
588
    else
589
    {   // before the update?
590
4
        mTopWidget = nullptr;  // No longer valid in general
591
4
        uninstallActionListeners();
592
    }
593
8
}
594
595
Widget *GuiTable::getWidgetAt(int x, int y)
596
{
597
    if (mModel == nullptr)
598
        return nullptr;
599
600
    const int row = getRowForY(y);
601
    const int column = getColumnForX(x);
602
603
    if (mTopWidget != nullptr &&
604
        mTopWidget->getDimension().isPointInRect(x, y))
605
    {
606
        return mTopWidget;
607
    }
608
609
    if (row > -1 && column > -1)
610
    {
611
        Widget *const w = mModel->getElementAt(row, column);
612
        if (w != nullptr && w->isFocusable())
613
            return w;
614
    }
615
    return nullptr;
616
}
617
618
int GuiTable::getRowForY(const int y) const
619
{
620
    int row = -1;
621
622
    const int rowHeight = getRowHeight();
623
    if (rowHeight > 0)
624
       row = y / rowHeight;
625
626
    if (row < 0 || row >= mModel->getRows())
627
        return -1;
628
    return row;
629
}
630
631
int GuiTable::getColumnForX(const int x) const
632
{
633
    int column;
634
    int delta = 0;
635
636
    const int colnum = mModel->getColumns();
637
    for (column = 0; column < colnum; column ++)
638
    {
639
        delta += getColumnWidth(column);
640
        if (x <= delta)
641
            break;
642
    }
643
644
    if (column >= colnum)
645
        return -1;
646
    return column;
647
}
648
649
14
void GuiTable::setFocusHandler(FocusHandler *const focusHandler)
650
{
651
    // add check for focusHandler. may be need remove it?
652
653
14
    if (focusHandler == nullptr)
654
        return;
655
656
    Widget::setFocusHandler(focusHandler);
657
658
    const int rows = mModel->getRows();
659
    const int cols = mModel->getColumns();
660
    for (int r = 0; r < rows; ++r)
661
    {
662
        for (int c = 0; c < cols ; ++c)
663
        {
664
            Widget *const w = mModel->getElementAt(r, c);
665
            if (w != nullptr)
666
                w->setFocusHandler(focusHandler);
667
        }
668
    }
669
}
670
671
void GuiTable::requestFocus()
672
{
673
    if (mFocusHandler == nullptr)
674
        return;
675
    Widget::requestFocus();
676
}