GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/listbox.cpp Lines: 76 178 42.7 %
Date: 2021-03-17 Branches: 29 135 21.5 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-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
/*      _______   __   __   __   ______   __   __   _______   __   __
25
 *     / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___  /\ /  |\/ /\
26
 *    / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / /
27
 *   / / /__   / / // / // / // / /    / ___  / // ___  / // /| ' / /
28
 *  / /_// /\ / /_// / // / // /_/_   / / // / // /\_/ / // / |  / /
29
 * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ /
30
 * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/
31
 *
32
 * Copyright (c) 2004 - 2008 Olof Naessén and Per Larsson
33
 *
34
 *
35
 * Per Larsson a.k.a finalman
36
 * Olof Naessén a.k.a jansem/yakslem
37
 *
38
 * Visit: http://guichan.sourceforge.net
39
 *
40
 * License: (BSD)
41
 * Redistribution and use in source and binary forms, with or without
42
 * modification, are permitted provided that the following conditions
43
 * are met:
44
 * 1. Redistributions of source code must retain the above copyright
45
 *    notice, this list of conditions and the following disclaimer.
46
 * 2. Redistributions in binary form must reproduce the above copyright
47
 *    notice, this list of conditions and the following disclaimer in
48
 *    the documentation and/or other materials provided with the
49
 *    distribution.
50
 * 3. Neither the name of Guichan nor the names of its contributors may
51
 *    be used to endorse or promote products derived from this software
52
 *    without specific prior written permission.
53
 *
54
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
55
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
56
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
57
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
58
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
59
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
60
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
61
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
62
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
63
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65
 */
66
67
#include "gui/widgets/listbox.h"
68
69
#include "settings.h"
70
71
#include "gui/focushandler.h"
72
#include "gui/gui.h"
73
#include "gui/skin.h"
74
75
#include "gui/fonts/font.h"
76
77
#include "gui/models/listmodel.h"
78
79
#include "listeners/selectionlistener.h"
80
81
#include "utils/foreach.h"
82
83
#include "render/graphics.h"
84
85
#include "debug.h"
86
87
float ListBox::mAlpha = 1.0;
88
89
130
ListBox::ListBox(const Widget2 *const widget,
90
                 ListModel *const listModel,
91
130
                 const std::string &skin) :
92
    Widget(widget),
93
    MouseListener(),
94
    KeyListener(),
95
    mSelected(-1),
96
    mListModel(listModel),
97
    mWrappingEnabled(false),
98
    mSelectionListeners(),
99
260
    mHighlightColor(getThemeColor(ThemeColorId::HIGHLIGHT, 255U)),
100
    mForegroundSelectedColor(getThemeColor(ThemeColorId::LISTBOX_SELECTED,
101
260
        255U)),
102
    mForegroundSelectedColor2(getThemeColor(
103
260
        ThemeColorId::LISTBOX_SELECTED_OUTLINE, 255U)),
104
    mOldSelected(-1),
105
    mPadding(0),
106
    mPressedIndex(-2),
107
    mRowHeight(0),
108
    mItemPadding(1),
109
    mSkin(nullptr),
110
    mDistributeMousePressed(true),
111
910
    mCenterText(false)
112
{
113
130
    setWidth(100);
114
130
    setFocusable(true);
115
130
    addMouseListener(this);
116
130
    addKeyListener(this);
117
118
260
    mForegroundColor = getThemeColor(ThemeColorId::LISTBOX, 255U);
119
260
    mForegroundColor2 = getThemeColor(ThemeColorId::LISTBOX_OUTLINE, 255U);
120
121
130
    if (theme != nullptr)
122
    {
123

520
        mSkin = theme->load(skin,
124
            "listbox.xml",
125
            true,
126
130
            Theme::getThemePath());
127
    }
128
129
130
    if (mSkin != nullptr)
130
    {
131
260
        mPadding = mSkin->getPadding();
132

520
        mItemPadding = mSkin->getOption("itemPadding");
133
    }
134
135
130
    const Font *const font = getFont();
136
130
    mRowHeight = CAST_U32(
137
130
        font->getHeight() + 2 * mItemPadding);
138
130
}
139
140
130
void ListBox::postInit()
141
{
142
130
    adjustSize();
143
130
}
144
145
757
ListBox::~ListBox()
146
{
147
130
    if (gui != nullptr)
148
130
        gui->removeDragged(this);
149
150
130
    if (theme != nullptr)
151
130
        theme->unload(mSkin);
152
237
}
153
154
2
void ListBox::updateAlpha()
155
{
156
    const float alpha = std::max(settings.guiAlpha,
157
12
        theme->getMinimumOpacity());
158
159

4
    if (mAlpha != alpha)
160
        mAlpha = alpha;
161
2
}
162
163
2
void ListBox::draw(Graphics *const graphics)
164
{
165
2
    if (mListModel == nullptr)
166
        return;
167
168
    BLOCK_START("ListBox::draw")
169
2
    updateAlpha();
170
171
2
    mHighlightColor.a = CAST_U32(mAlpha * 255.0F);
172
2
    graphics->setColor(mHighlightColor);
173
2
    Font *const font = getFont();
174
2
    const int rowHeight = CAST_S32(getRowHeight());
175
2
    const int width = mDimension.width;
176
177
2
    if (mCenterText)
178
    {
179
        // Draw filled rectangle around the selected list element
180
        if (mSelected >= 0)
181
        {
182
            graphics->fillRectangle(Rect(mPadding,
183
                rowHeight * mSelected + mPadding,
184
                mDimension.width - 2 * mPadding, rowHeight));
185
186
            const std::string str = mListModel->getElementAt(mSelected);
187
            font->drawString(graphics,
188
                mForegroundSelectedColor,
189
                mForegroundSelectedColor2,
190
                str,
191
                (width - font->getWidth(str)) / 2,
192
                mSelected * rowHeight + mPadding + mItemPadding);
193
        }
194
        // Draw the list elements
195
        const int sz = mListModel->getNumberOfElements();
196
        for (int i = 0, y = mPadding + mItemPadding;
197
             i < sz; ++i, y += rowHeight)
198
        {
199
            if (i != mSelected)
200
            {
201
                const std::string str = mListModel->getElementAt(i);
202
                font->drawString(graphics,
203
                    mForegroundColor,
204
                    mForegroundColor2,
205
                    str,
206
                    (width - font->getWidth(str)) / 2, y);
207
            }
208
        }
209
    }
210
    else
211
    {
212
        // Draw filled rectangle around the selected list element
213
2
        if (mSelected >= 0)
214
        {
215
            graphics->fillRectangle(Rect(mPadding,
216
                rowHeight * mSelected + mPadding,
217
                mDimension.width - 2 * mPadding, rowHeight));
218
219
            const std::string str = mListModel->getElementAt(mSelected);
220
            font->drawString(graphics,
221
                mForegroundSelectedColor,
222
                mForegroundSelectedColor2,
223
                str,
224
                mPadding,
225
                mSelected * rowHeight + mPadding + mItemPadding);
226
        }
227
        // Draw the list elements
228
2
        const int sz = mListModel->getNumberOfElements();
229
2
        for (int i = 0, y = mPadding + mItemPadding; i < sz;
230
             ++i, y += rowHeight)
231
        {
232
            if (i != mSelected)
233
            {
234
                const std::string str = mListModel->getElementAt(i);
235
                font->drawString(graphics,
236
                    mForegroundColor,
237
                    mForegroundColor2,
238
                    str,
239
                    mPadding, y);
240
            }
241
        }
242
    }
243
    BLOCK_END("ListBox::draw")
244
}
245
246
void ListBox::keyPressed(KeyEvent &event)
247
{
248
    const InputActionT action = event.getActionId();
249
    if (action == InputAction::GUI_SELECT)
250
    {
251
        distributeActionEvent();
252
        event.consume();
253
    }
254
    else if (action == InputAction::GUI_UP)
255
    {
256
        if (mSelected > 0)
257
        {
258
            setSelected(mSelected - 1);
259
        }
260
        else if (mSelected == 0 &&
261
                 mWrappingEnabled &&
262
                 getListModel() != nullptr)
263
        {
264
            setSelected(getListModel()->getNumberOfElements() - 1);
265
        }
266
        event.consume();
267
    }
268
    else if (action == InputAction::GUI_DOWN)
269
    {
270
        const int num = getListModel()->getNumberOfElements() - 1;
271
        if (mSelected < num)
272
            setSelected(mSelected + 1);
273
        else if (mSelected == num && mWrappingEnabled)
274
            setSelected(0);
275
        event.consume();
276
    }
277
    else if (action == InputAction::GUI_HOME)
278
    {
279
        setSelected(0);
280
        event.consume();
281
    }
282
    else if (action == InputAction::GUI_END && (getListModel() != nullptr))
283
    {
284
        setSelected(getListModel()->getNumberOfElements() - 1);
285
        event.consume();
286
    }
287
}
288
289
void ListBox::safeDraw(Graphics *const graphics)
290
{
291
    ListBox::draw(graphics);
292
}
293
294
// Don't do anything on scrollwheel. ScrollArea will deal with that.
295
296
void ListBox::mouseWheelMovedUp(MouseEvent &event A_UNUSED)
297
{
298
}
299
300
void ListBox::mouseWheelMovedDown(MouseEvent &event A_UNUSED)
301
{
302
}
303
304
void ListBox::mousePressed(MouseEvent &event)
305
{
306
    mPressedIndex = getSelectionByMouse(event.getY());
307
    if (mMouseConsume && mPressedIndex != -1)
308
        event.consume();
309
}
310
311
void ListBox::mouseReleased(MouseEvent &event)
312
{
313
    if (mPressedIndex != getSelectionByMouse(event.getY()))
314
        return;
315
316
    if (mDistributeMousePressed)
317
    {
318
        mouseReleased1(event);
319
    }
320
    else
321
    {
322
        switch (event.getClickCount())
323
        {
324
            case 1:
325
                mouseDragged(event);
326
                mOldSelected = mSelected;
327
                break;
328
            case 2:
329
                if (gui != nullptr)
330
                    gui->resetClickCount();
331
                if (mOldSelected == mSelected)
332
                    mouseReleased1(event);
333
                else
334
                    mouseDragged(event);
335
                mOldSelected = mSelected;
336
                break;
337
            default:
338
                mouseDragged(event);
339
                mOldSelected = mSelected;
340
                break;
341
        }
342
    }
343
    mPressedIndex = -2;
344
}
345
346
void ListBox::mouseReleased1(const MouseEvent &event)
347
{
348
    if (event.getButton() == MouseButton::LEFT)
349
    {
350
        setSelected(std::max(0, getSelectionByMouse(event.getY())));
351
        distributeActionEvent();
352
    }
353
}
354
355
void ListBox::mouseDragged(MouseEvent &event)
356
{
357
    if (event.getButton() != MouseButton::LEFT || getRowHeight() == 0)
358
        return;
359
360
    // Make list selection update on drag, but guard against negative y
361
    if (getRowHeight() != 0U)
362
        setSelected(std::max(0, getSelectionByMouse(event.getY())));
363
}
364
365
void ListBox::refocus()
366
{
367
    if (mFocusHandler == nullptr)
368
        return;
369
370
    if (isFocusable())
371
        mFocusHandler->requestFocus(this);
372
}
373
374
515
void ListBox::adjustSize()
375
{
376
    BLOCK_START("ListBox::adjustSize")
377
515
    if (mListModel != nullptr)
378
    {
379
1545
        setHeight(CAST_S32(getRowHeight()) *
380
1545
            mListModel->getNumberOfElements() + 2 * mPadding);
381
    }
382
    BLOCK_END("ListBox::adjustSize")
383
515
}
384
385
void ListBox::logic()
386
{
387
    BLOCK_START("ListBox::logic")
388
    adjustSize();
389
    BLOCK_END("ListBox::logic")
390
}
391
392
int ListBox::getSelectionByMouse(const int y) const
393
{
394
    if (y < mPadding)
395
        return -1;
396
    return (y - mPadding) / CAST_S32(getRowHeight());
397
}
398
399
210
void ListBox::setSelected(const int selected)
400
{
401
210
    if (mListModel == nullptr)
402
    {
403
        mSelected = -1;
404
    }
405
    else
406
    {
407
210
        if (selected < 0)
408
            mSelected = -1;
409
210
        else if (selected >= mListModel->getNumberOfElements())
410
6
            mSelected = mListModel->getNumberOfElements() - 1;
411
        else
412
204
            mSelected = selected;
413
    }
414
415
420
    Rect scroll;
416
417
210
    if (mSelected < 0)
418
        scroll.y = 0;
419
    else
420
204
        scroll.y = CAST_S32(getRowHeight()) * mSelected;
421
422
210
    scroll.height = CAST_S32(getRowHeight());
423
210
    showPart(scroll);
424
425
210
    distributeValueChangedEvent();
426
210
}
427
428
99
void ListBox::setListModel(ListModel *const listModel)
429
{
430
99
    mSelected = -1;
431
99
    mListModel = listModel;
432
99
    adjustSize();
433
99
}
434
435
15
void ListBox::addSelectionListener(SelectionListener *const selectionListener)
436
{
437
30
    mSelectionListeners.push_back(selectionListener);
438
15
}
439
440
void ListBox::removeSelectionListener(SelectionListener *const
441
                                      selectionListener)
442
{
443
    mSelectionListeners.remove(selectionListener);
444
}
445
446
void ListBox::distributeValueChangedEvent()
447
{
448

630
    FOR_EACH (SelectionListenerIterator, iter, mSelectionListeners)
449
    {
450
12
        SelectionEvent event(this);
451
4
        (*iter)->valueChanged(event);
452
    }
453
}