GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/guitable.cpp Lines: 62 321 19.3 %
Date: 2017-11-29 Branches: 24 248 9.7 %

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