GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/dropdown.cpp Lines: 129 264 48.9 %
Date: 2021-03-17 Branches: 85 228 37.3 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2006-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/dropdown.h"
25
26
#include "settings.h"
27
28
#include "gui/gui.h"
29
#include "gui/skin.h"
30
31
#include "gui/fonts/font.h"
32
33
#include "gui/models/extendedlistmodel.h"
34
35
#include "gui/widgets/createwidget.h"
36
#include "gui/widgets/popuplist.h"
37
38
#include "render/graphics.h"
39
40
#include "resources/imagerect.h"
41
42
#include "debug.h"
43
44
int DropDown::instances = 0;
45
Image *DropDown::buttons[2][2];
46
1
ImageRect DropDown::skinRect;
47
float DropDown::mAlpha = 1.0;
48
Skin *DropDown::mSkin = nullptr;
49
50
3
static std::string const dropdownFiles[2] =
51
{
52
    "dropdown.xml",
53
    "dropdown_pressed.xml"
54

5
};
55
56
95
DropDown::DropDown(const Widget2 *const widget,
57
                   ListModel *const listModel,
58
                   const bool isExtended,
59
                   const Modal modal,
60
                   ActionListener *const listener,
61
95
                   const std::string &eventId) :
62
    ActionListener(),
63
    BasicContainer(widget),
64
    KeyListener(),
65
    MouseListener(),
66
    FocusListener(),
67
    SelectionListener(),
68

95
    mPopup(CREATEWIDGETR(PopupList, this, listModel, isExtended, modal)),
69
190
    mShadowColor(getThemeColor(ThemeColorId::DROPDOWN_SHADOW, 255U)),
70
190
    mHighlightColor(getThemeColor(ThemeColorId::HIGHLIGHT, 255U)),
71
    mPadding(1),
72
    mImagePadding(2),
73
    mSpacing(0),
74
    mFoldedUpHeight(0),
75
    mSelectionListeners(),
76
    mExtended(isExtended),
77
    mDroppedDown(false),
78
    mPushed(false),
79
1045
    mIsDragged(false)
80
{
81
95
    mAllowLogic = false;
82
95
    mFrameSize = 2;
83
190
    mForegroundColor2 = getThemeColor(ThemeColorId::DROPDOWN_OUTLINE, 255U);
84
85
95
    mPopup->setHeight(100);
86
87
    // Initialize graphics
88

95
    if (instances == 0 && (theme != nullptr))
89
    {
90
        // Load the background skin
91
50
        for (int i = 0; i < 2; i ++)
92
        {
93
80
            Skin *const skin = theme->load(dropdownFiles[i],
94
                "dropdown.xml",
95
                true,
96
40
                Theme::getThemePath());
97
20
            if (skin != nullptr)
98
            {
99
20
                if (i == 0)
100
10
                    mSkin = skin;
101
20
                const ImageRect &rect = skin->getBorder();
102
60
                for (int f = 0; f < 2; f ++)
103
                {
104
40
                    if (rect.grid[f] != nullptr)
105
                    {
106
40
                        rect.grid[f]->incRef();
107
40
                        buttons[f][i] = rect.grid[f];
108
40
                        buttons[f][i]->setAlpha(mAlpha);
109
                    }
110
                    else
111
                    {
112
                        buttons[f][i] = nullptr;
113
                    }
114
                }
115
20
                if (i != 0)
116
10
                    theme->unload(skin);
117
            }
118
            else
119
            {
120
                for (int f = 0; f < 2; f ++)
121
                    buttons[f][i] = nullptr;
122
            }
123
        }
124
125
        // get the border skin
126

70
        theme->loadRect(skinRect, "dropdown_background.xml", "", 0, 8);
127
    }
128
129
95
    instances++;
130
131
95
    setWidth(100);
132
95
    setFocusable(true);
133
95
    setListModel(listModel);
134
135

95
    if (mPopup->getSelected() < 0)
136
2
        mPopup->setSelected(0);
137
138
95
    addMouseListener(this);
139
95
    addKeyListener(this);
140
95
    addFocusListener(this);
141
142
95
    adjustHeight();
143
//    mPopup->setForegroundColorAll(getThemeColor(ThemeColorId::DROPDOWN,
144
//        255U),
145
//        getThemeColor(ThemeColorId::DROPDOWN_OUTLINE, 255U));
146
190
    mForegroundColor = getThemeColor(ThemeColorId::DROPDOWN, 255U);
147
190
    mForegroundColor2 = getThemeColor(ThemeColorId::DROPDOWN_OUTLINE, 255U);
148
149
95
    if (!eventId.empty())
150
4
        setActionEventId(eventId);
151
152
95
    if (listener != nullptr)
153
4
        addActionListener(listener);
154
155
95
    mPopup->adjustSize();
156
157
95
    if (mSkin != nullptr)
158
    {
159

380
        mSpacing = mSkin->getOption("spacing");
160

380
        mFrameSize = CAST_U32(mSkin->getOption("frameSize"));
161
190
        mPadding = mSkin->getPadding();
162

380
        mImagePadding = mSkin->getOption("imagePadding");
163
    }
164
95
    adjustHeight();
165
95
}
166
167
760
DropDown::~DropDown()
168
{
169
95
    if (gui != nullptr)
170
95
        gui->removeDragged(this);
171
172
95
    instances--;
173
95
    if (instances == 0)
174
    {
175
50
        for (int f = 0; f < 2; f ++)
176
        {
177
100
            for (int i = 0; i < 2; i ++)
178
            {
179
40
                if (buttons[f][i] != nullptr)
180
40
                    buttons[f][i]->decRef();
181
            }
182
        }
183
10
        if (theme != nullptr)
184
        {
185
10
            theme->unload(mSkin);
186
10
            Theme::unloadRect(skinRect, 0, 8);
187
        }
188
    }
189
190
}
190
191
8
void DropDown::updateAlpha()
192
{
193
    const float alpha = std::max(settings.guiAlpha,
194
24
        theme->getMinimumOpacity());
195
196
8
    if (mAlpha != alpha)
197
    {
198
        mAlpha = alpha;
199
200
        if (buttons[0][0] != nullptr)
201
            buttons[0][0]->setAlpha(mAlpha);
202
        if (buttons[0][1] != nullptr)
203
            buttons[0][1]->setAlpha(mAlpha);
204
        if (buttons[1][0] != nullptr)
205
            buttons[1][0]->setAlpha(mAlpha);
206
        if (buttons[1][1] != nullptr)
207
            buttons[1][1]->setAlpha(mAlpha);
208
209
        for (int a = 0; a < 9; a++)
210
        {
211
            if (skinRect.grid[a] != nullptr)
212
                skinRect.grid[a]->setAlpha(mAlpha);
213
        }
214
    }
215
8
}
216
217
8
void DropDown::draw(Graphics *const graphics)
218
{
219
    BLOCK_START("DropDown::draw")
220
    int h;
221
222
8
    if (mDroppedDown)
223
        h = mFoldedUpHeight;
224
    else
225
8
        h = mDimension.height;
226
227
8
    updateAlpha();
228
229
8
    const unsigned int alpha = CAST_U32(mAlpha * 255.0F);
230
8
    mHighlightColor.a = alpha;
231
8
    mShadowColor.a = alpha;
232
233
8
    ListModel *const model = mPopup->getListModel();
234

8
    if ((model != nullptr) && mPopup->getSelected() >= 0)
235
    {
236
8
        Font *const font = getFont();
237
8
        if (mExtended)
238
        {
239
            const int sel = mPopup->getSelected();
240
            ExtendedListModel *const model2
241
                = static_cast<ExtendedListModel *>(model);
242
            const Image *const image = model2->getImageAt(sel);
243
            if (image == nullptr)
244
            {
245
                font->drawString(graphics,
246
                    mForegroundColor,
247
                    mForegroundColor2,
248
                    model->getElementAt(sel),
249
                    mPadding, mPadding);
250
            }
251
            else
252
            {
253
                graphics->drawImage(image,
254
                    mImagePadding,
255
                    (mDimension.height - image->getHeight()) / 2 + mPadding);
256
                font->drawString(graphics,
257
                    mForegroundColor,
258
                    mForegroundColor2,
259
                    model->getElementAt(sel),
260
                    image->getWidth() + mImagePadding + mSpacing, mPadding);
261
            }
262
        }
263
        else
264
        {
265
16
            font->drawString(graphics,
266
                mForegroundColor,
267
                mForegroundColor2,
268
16
                model->getElementAt(mPopup->getSelected()),
269
8
                mPadding, mPadding);
270
        }
271
    }
272
273
8
    if (isFocused())
274
    {
275
        const int pad = 2 * mPadding;
276
        graphics->setColor(mHighlightColor);
277
        graphics->drawRectangle(Rect(mPadding, mPadding,
278
            mDimension.width - h - pad, h - pad));
279
    }
280
281
8
    drawButton(graphics);
282
283
8
    if (mDroppedDown)
284
    {
285
        // Draw two lines separating the ListBox with selected
286
        // element view.
287
        const int w = mDimension.width;
288
        graphics->setColor(mHighlightColor);
289
        graphics->drawLine(0, h, w, h);
290
        graphics->setColor(mShadowColor);
291
        graphics->drawLine(0, h + 1, w, h + 1);
292
    }
293
    BLOCK_END("DropDown::draw")
294
8
}
295
296
void DropDown::safeDraw(Graphics *const graphics)
297
{
298
    DropDown::draw(graphics);
299
}
300
301
8
void DropDown::drawFrame(Graphics *const graphics)
302
{
303
    BLOCK_START("DropDown::drawFrame")
304
16
    const int bs2 = CAST_S32(getFrameSize());
305
8
    const Rect &rect = mDimension;
306
16
    graphics->drawImageRect(0, 0,
307
16
        rect.width + bs2, rect.height + bs2,
308
16
        skinRect);
309
    BLOCK_END("DropDown::drawFrame")
310
8
}
311
312
void DropDown::safeDrawFrame(Graphics *const graphics)
313
{
314
    BLOCK_START("DropDown::drawFrame")
315
    const int bs2 = CAST_S32(getFrameSize());
316
    const Rect &rect = mDimension;
317
    graphics->drawImageRect(0, 0,
318
        rect.width + bs2, rect.height + bs2,
319
        skinRect);
320
    BLOCK_END("DropDown::drawFrame")
321
}
322
323
8
void DropDown::drawButton(Graphics *const graphics)
324
{
325
8
    Image *const image = buttons[mDroppedDown][mPushed];
326
8
    if (image != nullptr)
327
    {
328
8
        const int height = mDroppedDown ? mFoldedUpHeight : mDimension.height;
329
16
        graphics->drawImage(image,
330
16
            mDimension.width - image->getWidth() - mImagePadding,
331
24
            (height - image->getHeight()) / 2);
332
    }
333
8
}
334
335
void DropDown::keyPressed(KeyEvent& event)
336
{
337
    if (event.isConsumed())
338
        return;
339
340
    const InputActionT actionId = event.getActionId();
341
    PRAGMA45(GCC diagnostic push)
342
    PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
343
    switch (actionId)
344
    {
345
        case InputAction::GUI_SELECT:
346
        case InputAction::GUI_SELECT2:
347
            dropDown();
348
            break;
349
350
        case InputAction::GUI_UP:
351
            setSelected(getSelected() - 1);
352
            break;
353
354
        case InputAction::GUI_DOWN:
355
            setSelected(getSelected() + 1);
356
            break;
357
358
        case InputAction::GUI_HOME:
359
            setSelected(0);
360
            break;
361
362
        case InputAction::GUI_END:
363
            if (mPopup->getListModel() != nullptr)
364
            {
365
                setSelected(mPopup->getListModel()->
366
                    getNumberOfElements() - 1);
367
            }
368
            break;
369
370
        default:
371
            return;
372
    }
373
    PRAGMA45(GCC diagnostic pop)
374
375
    event.consume();
376
}
377
378
3
void DropDown::hideDrop(bool event)
379
{
380
3
    if (event)
381
1
        distributeActionEvent();
382
3
    mPopup->setVisible(Visible_false);
383
3
}
384
385
void DropDown::mousePressed(MouseEvent& event)
386
{
387
    event.consume();
388
    // If we have a mouse press on the widget.
389
    if (event.getButton() == MouseButton::LEFT
390
        && !mDroppedDown && event.getSource() == this)
391
    {
392
        mPushed = true;
393
        dropDown();
394
    }
395
    else
396
    {
397
        mPushed = false;
398
        foldUp();
399
        hideDrop(true);
400
    }
401
}
402
403
void DropDown::mouseReleased(MouseEvent &event)
404
{
405
    if (mIsDragged)
406
        mPushed = false;
407
408
    const MouseButtonT button = event.getButton();
409
    const int x = event.getX();
410
    const int y = event.getY();
411
    // Released outside of widget. Can happen when we have modal
412
    // input focus.
413
    if ((0 > y || y >= mDimension.height || x < 0 || x >= mDimension.width)
414
        && button == MouseButton::LEFT)
415
    {
416
        if (mIsDragged)
417
            foldUp();
418
    }
419
    else if (button == MouseButton::LEFT)
420
    {
421
        mPushed = false;
422
    }
423
424
    mIsDragged = false;
425
}
426
427
void DropDown::mouseDragged(MouseEvent &event)
428
{
429
    mIsDragged = true;
430
    event.consume();
431
}
432
433
void DropDown::mouseWheelMovedUp(MouseEvent& event)
434
{
435
    setSelected(getSelected() - 1);
436
    event.consume();
437
}
438
439
void DropDown::mouseWheelMovedDown(MouseEvent& event)
440
{
441
    setSelected(getSelected() + 1);
442
    event.consume();
443
}
444
445
19
void DropDown::setSelectedString(const std::string &str)
446
{
447
19
    ListModel *const listModel = mPopup->getListModel();
448
19
    if (listModel == nullptr)
449
        return;
450
451
299
    for (int f = 0; f < listModel->getNumberOfElements(); f ++)
452
    {
453
318
        if (listModel->getElementAt(f) == str)
454
        {
455
            setSelected(f);
456
            break;
457
        }
458
    }
459
}
460
461
std::string DropDown::getSelectedString() const
462
{
463
    ListModel *const listModel = mPopup->getListModel();
464
    if (listModel == nullptr)
465
        return "";
466
467
    return listModel->getElementAt(getSelected());
468
}
469
470
291
void DropDown::adjustHeight()
471
{
472
291
    setHeight(getFont()->getHeight() + 2 * mPadding);
473
291
}
474
475
void DropDown::dropDown()
476
{
477
    if (!mDroppedDown)
478
    {
479
        if (mParent == nullptr)
480
            return;
481
        mDroppedDown = true;
482
        mFoldedUpHeight = getHeight();
483
        adjustHeight();
484
485
        int x = 0;
486
        int y = 0;
487
        getAbsolutePosition(x, y);
488
        const int frame = CAST_S32(mParent->getFrameSize());
489
        const int pad = mPopup->getPadding();
490
        const int pad2 = pad * 2;
491
492
        // here width should be adjusted on some other parameters
493
        mPopup->setWidth(mDimension.width - pad2 + 8);
494
        mPopup->show(x - mPadding - frame - 1, y + mDimension.height);
495
        mPopup->requestMoveToTop();
496
        mPopup->requestFocus();
497
    }
498
}
499
500
void DropDown::foldUp()
501
{
502
    if (mDroppedDown)
503
    {
504
        mDroppedDown = false;
505
        adjustHeight();
506
    }
507
}
508
509
3
int DropDown::getSelected() const
510
{
511
3
    return mPopup->getSelected();
512
}
513
514
80
void DropDown::setSelected(int selected)
515
{
516



99
    if (selected >= 0)
517
99
        mPopup->setSelected(selected);
518
80
}
519
520
95
void DropDown::setListModel(ListModel *const listModel)
521
{
522
95
    mPopup->setListModel(listModel);
523
524
95
    if (mPopup->getSelected() < 0)
525
95
        mPopup->setSelected(0);
526
527
95
    adjustHeight();
528
95
}
529
530
ListModel *DropDown::getListModel()
531
{
532
    return mPopup->getListModel();
533
}
534
535
void DropDown::action(const ActionEvent &actionEvent A_UNUSED)
536
{
537
    foldUp();
538
    distributeActionEvent();
539
}
540
541
Rect DropDown::getChildrenArea()
542
{
543
    if (mDroppedDown)
544
    {
545
        // Calculate the children area (with the one pixel border in mind)
546
        return Rect(1, mFoldedUpHeight + 1,
547
            mDimension.width - 2, mDimension.height - mFoldedUpHeight - 2);
548
    }
549
550
    return Rect();
551
}
552
553
void DropDown::valueChanged(const SelectionEvent& event A_UNUSED)
554
{
555
}
556
557
void DropDown::updateSelection()
558
{
559
    mDroppedDown = false;
560
    mPushed = false;
561
    distributeActionEvent();
562
    distributeValueChangedEvent();
563
}
564
565
void DropDown::addSelectionListener(SelectionListener* selectionListener)
566
{
567
    mSelectionListeners.push_back(selectionListener);
568
}
569
570
void DropDown::removeSelectionListener(SelectionListener* listener)
571
{
572
    mSelectionListeners.remove(listener);
573
}
574
575
void DropDown::distributeValueChangedEvent()
576
{
577
    for (SelectionListenerIterator iter = mSelectionListeners.begin();
578
          iter != mSelectionListeners.end();
579
          ++iter)
580
    {
581
        SelectionEvent event(this);
582
        (*iter)->valueChanged(event);
583
    }
584

3
}