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

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

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

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

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