GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/avatarlistbox.cpp Lines: 27 313 8.6 %
Date: 2021-03-17 Branches: 26 433 6.0 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2010  The Mana Developers
4
 *  Copyright (C) 2011-2019  The ManaPlus Developers
5
 *  Copyright (C) 2019-2021  Andrei Karas
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/avatarlistbox.h"
24
25
#include "actormanager.h"
26
#include "configuration.h"
27
28
#include "being/localplayer.h"
29
30
#include "enums/resources/map/mapitemtype.h"
31
32
#include "gui/gui.h"
33
#include "gui/popupmanager.h"
34
#include "gui/skin.h"
35
#include "gui/viewport.h"
36
37
#include "gui/fonts/font.h"
38
39
#include "gui/popups/popupmenu.h"
40
41
#include "gui/models/avatarlistmodel.h"
42
43
#include "gui/windows/chatwindow.h"
44
45
#include "render/vertexes/imagecollection.h"
46
47
#include "resources/image/image.h"
48
49
#include "resources/map/map.h"
50
51
#include "debug.h"
52
53
int AvatarListBox::instances = 0;
54
Image *AvatarListBox::onlineIcon = nullptr;
55
Image *AvatarListBox::offlineIcon = nullptr;
56
57
5
AvatarListBox::AvatarListBox(const Widget2 *const widget,
58
5
                             AvatarListModel *const model) :
59
    ListBox(widget, model, "avatarlistbox.xml"),
60


20
    mImagePadding(mSkin != nullptr ? mSkin->getOption("imagePadding") : 0),
61

20
    mShowGender(config.getBoolValue("showgender")),
62

55
    mShowLevel(config.getBoolValue("showlevel"))
63
{
64
5
    instances++;
65
66
5
    if (instances == 1)
67
    {
68

7
        onlineIcon = Theme::getImageFromThemeXml("circle-on.xml", "");
69

7
        offlineIcon = Theme::getImageFromThemeXml("circle-off.xml", "");
70
    }
71
72
5
    setWidth(200);
73
74

20
    config.addListener("showgender", this);
75

20
    config.addListener("showlevel", this);
76
77
10
    mForegroundColor = getThemeColor(ThemeColorId::TEXT, 255U);
78
10
    mForegroundColor2 = getThemeColor(ThemeColorId::TEXT_OUTLINE, 255U);
79
5
}
80
81
20
AvatarListBox::~AvatarListBox()
82
{
83
5
    config.removeListeners(this);
84
    CHECKLISTENERS
85
86
5
    instances--;
87
88
5
    if (instances == 0)
89
    {
90
1
        if (onlineIcon != nullptr)
91
        {
92
1
            onlineIcon->decRef();
93
1
            onlineIcon = nullptr;
94
        }
95
1
        if (offlineIcon != nullptr)
96
        {
97
1
            offlineIcon->decRef();
98
1
            offlineIcon = nullptr;
99
        }
100
    }
101
10
}
102
103
void AvatarListBox::draw(Graphics *const graphics)
104
{
105
    BLOCK_START("AvatarListBox::draw")
106
    if ((mListModel == nullptr) || (localPlayer == nullptr))
107
    {
108
        BLOCK_END("AvatarListBox::draw")
109
        return;
110
    }
111
112
    const Widget *const parent = mParent;
113
    if (parent == nullptr)
114
        return;
115
116
    AvatarListModel *const model = static_cast<AvatarListModel *>(
117
        mListModel);
118
    updateAlpha();
119
120
    Font *const font = getFont();
121
    const int fontHeight = font->getHeight();
122
    const int parentWidth = parent->getWidth();
123
    const std::string &name = localPlayer->getName();
124
125
    // Draw the list elements
126
    ImageCollection vertexes;
127
    const int num = model->getNumberOfElements();
128
    for (int i = 0, y = 0;
129
         i < num;
130
         ++i, y += fontHeight)
131
    {
132
        const Avatar *const a = model->getAvatarAt(i);
133
        if (a == nullptr)
134
            continue;
135
136
        const MapItemType::Type type = static_cast<MapItemType::Type>(
137
            a->getType());
138
        if (type != MapItemType::SEPARATOR)
139
        {
140
            // Draw online status
141
            const Image *const icon = a->getOnline()
142
                ? onlineIcon : offlineIcon;
143
            if (icon != nullptr)
144
            {
145
                graphics->calcTileCollection(&vertexes, icon,
146
                    mImagePadding, y + mPadding);
147
            }
148
        }
149
    }
150
151
    graphics->finalize(&vertexes);
152
    graphics->drawTileCollection(&vertexes);
153
154
    for (int i = 0, y = 0;
155
         i < num;
156
         ++i, y += fontHeight)
157
    {
158
        const Avatar *const a = model->getAvatarAt(i);
159
        if (a == nullptr)
160
            continue;
161
162
        const MapItemType::Type type = static_cast<MapItemType::Type>(
163
            a->getType());
164
        std::string text;
165
166
        if (a->getMaxHp() > 0)
167
        {
168
            if (mShowLevel && a->getLevel() > 1)
169
            {
170
                text = strprintf("%s %d/%d (%d)", a->getComplexName().c_str(),
171
                                 a->getHp(), a->getMaxHp(), a->getLevel());
172
            }
173
            else
174
            {
175
                text = strprintf("%s %d/%d", a->getComplexName().c_str(),
176
                                 a->getHp(), a->getMaxHp());
177
            }
178
            if (a->getMaxHp() != 0)
179
            {
180
                const bool isPoison = a->getPoison();
181
                const ProgressColorIdT themeColor = (isPoison
182
                    ? ProgressColorId::PROG_HP_POISON
183
                    : ProgressColorId::PROG_HP);
184
                Color color = Theme::getProgressColor(
185
                    themeColor, static_cast<float>(a->getHp())
186
                    / static_cast<float>(a->getMaxHp()));
187
                color.a = 80;
188
                graphics->setColor(color);
189
                graphics->fillRectangle(Rect(mPadding, y + mPadding,
190
                    parentWidth * a->getHp() / a->getMaxHp()
191
                    - 2 * mPadding, fontHeight));
192
            }
193
        }
194
        else if (a->getDamageHp() != 0 && a->getName() != name)
195
        {
196
            if (mShowLevel && a->getLevel() > 1)
197
            {
198
                text = strprintf("%s -%d (%d)", a->getComplexName().c_str(),
199
                                 a->getDamageHp(), a->getLevel());
200
            }
201
            else
202
            {
203
                text = strprintf("%s -%d", a->getComplexName().c_str(),
204
                                 a->getDamageHp());
205
            }
206
207
            const ProgressColorIdT themeColor = (a->getPoison()
208
                ? ProgressColorId::PROG_HP_POISON : ProgressColorId::PROG_HP);
209
            Color color = Theme::getProgressColor(themeColor, 1);
210
            color.a = 80;
211
            graphics->setColor(color);
212
            graphics->fillRectangle(Rect(mPadding, y + mPadding,
213
                parentWidth * a->getDamageHp() / 1024
214
                - 2 * mPadding, fontHeight));
215
216
            if (a->getLevel() > 1)
217
            {
218
                graphics->setColor(mForegroundColor);
219
                int minHp = 40 + ((a->getLevel() - 1) * 5);
220
                if (minHp < 0)
221
                    minHp = 40;
222
223
                graphics->drawLine(parentWidth * minHp / 1024
224
                    + mPadding, y + mPadding,
225
                    parentWidth * minHp / 1024, y + fontHeight);
226
            }
227
        }
228
        else
229
        {
230
            if (mShowLevel && a->getLevel() > 1)
231
            {
232
                text = strprintf("%s (%d)", a->getComplexName().c_str(),
233
                                 a->getLevel());
234
            }
235
            else
236
            {
237
                text = a->getComplexName();
238
            }
239
        }
240
241
        if (!a->getMap().empty())
242
        {
243
            if (a->getX() != -1)
244
            {
245
                text.append(strprintf(" [%d,%d %s]", a->getX(), a->getY(),
246
                    a->getMap().c_str()));
247
            }
248
            else
249
            {
250
                text.append(strprintf(" [%s]", a->getMap().c_str()));
251
            }
252
        }
253
254
        if (graphics->getSecure())
255
        {
256
            if (mShowGender)
257
            {
258
                switch (a->getGender())
259
                {
260
                    case Gender::FEMALE:
261
                        text.append(" \u2640 ");
262
                        break;
263
                    case Gender::MALE:
264
                        text.append(" \u2642 ");
265
                        break;
266
                    default:
267
                    case Gender::UNSPECIFIED:
268
                        break;
269
                }
270
            }
271
        }
272
        else
273
        {
274
            if (mShowGender)
275
            {
276
                switch (a->getGender())
277
                {
278
                    case Gender::FEMALE:
279
                        text.append(strprintf(" \u2640 %s",
280
                            a->getAdditionString().c_str()));
281
                        break;
282
                    case Gender::MALE:
283
                        text.append(strprintf(" \u2642 %s",
284
                            a->getAdditionString().c_str()));
285
                        break;
286
                    default:
287
                    case Gender::UNSPECIFIED:
288
                        break;
289
                }
290
            }
291
            else
292
            {
293
                text.append(a->getAdditionString());
294
            }
295
        }
296
297
        // Draw Name
298
        if (a->getDisplayBold())
299
        {
300
            if (type == MapItemType::SEPARATOR)
301
            {
302
                boldFont->drawString(graphics,
303
                    mForegroundColor,
304
                    mForegroundColor,
305
                    text,
306
                    mImagePadding + mPadding,
307
                    y + mPadding);
308
            }
309
            else
310
            {
311
                boldFont->drawString(graphics,
312
                    mForegroundColor,
313
                    mForegroundColor,
314
                    text,
315
                    15 + mImagePadding + mPadding,
316
                    y + mPadding);
317
            }
318
        }
319
        else
320
        {
321
            if (type == MapItemType::SEPARATOR)
322
            {
323
                font->drawString(graphics,
324
                    mForegroundColor,
325
                    mForegroundColor,
326
                    text,
327
                    mImagePadding + mPadding,
328
                    y + mPadding);
329
            }
330
            else
331
            {
332
                font->drawString(graphics,
333
                    mForegroundColor,
334
                    mForegroundColor,
335
                    text,
336
                    15 + mImagePadding + mPadding,
337
                    y + mPadding);
338
            }
339
        }
340
    }
341
342
    setWidth(parentWidth - 10);
343
    BLOCK_END("AvatarListBox::draw")
344
}
345
346
void AvatarListBox::safeDraw(Graphics *const graphics)
347
{
348
    BLOCK_START("AvatarListBox::draw")
349
    if ((mListModel == nullptr) || (localPlayer == nullptr))
350
    {
351
        BLOCK_END("AvatarListBox::draw")
352
        return;
353
    }
354
355
    const Widget *const parent = mParent;
356
    if (parent == nullptr)
357
        return;
358
359
    AvatarListModel *const model = static_cast<AvatarListModel *>(
360
        mListModel);
361
    updateAlpha();
362
363
    Font *const font = getFont();
364
    const int fontHeight = font->getHeight();
365
    const int parentWidth = parent->getWidth();
366
    const std::string &name = localPlayer->getName();
367
368
    // Draw the list elements
369
    const int num = model->getNumberOfElements();
370
    for (int i = 0, y = 0;
371
         i < num;
372
         ++i, y += fontHeight)
373
    {
374
        const Avatar *const a = model->getAvatarAt(i);
375
        if (a == nullptr)
376
            continue;
377
378
        const MapItemType::Type type = static_cast<MapItemType::Type>(
379
            a->getType());
380
        if (type != MapItemType::SEPARATOR)
381
        {
382
            // Draw online status
383
            const Image *const icon = a->getOnline()
384
                ? onlineIcon : offlineIcon;
385
            if (icon != nullptr)
386
                graphics->drawImage(icon, mImagePadding, y + mPadding);
387
        }
388
    }
389
390
    for (int i = 0, y = 0;
391
         i < model->getNumberOfElements();
392
         ++i, y += fontHeight)
393
    {
394
        const Avatar *const a = model->getAvatarAt(i);
395
        if (a == nullptr)
396
            continue;
397
398
        const MapItemType::Type type = static_cast<MapItemType::Type>(
399
            a->getType());
400
        std::string text;
401
402
        if (a->getMaxHp() > 0)
403
        {
404
            if (mShowLevel && a->getLevel() > 1)
405
            {
406
                text = strprintf("%s %d/%d (%d)", a->getComplexName().c_str(),
407
                                 a->getHp(), a->getMaxHp(), a->getLevel());
408
            }
409
            else
410
            {
411
                text = strprintf("%s %d/%d", a->getComplexName().c_str(),
412
                                 a->getHp(), a->getMaxHp());
413
            }
414
            if (a->getMaxHp() != 0)
415
            {
416
                const bool isPoison = a->getPoison();
417
                const ProgressColorIdT themeColor = (isPoison
418
                    ? ProgressColorId::PROG_HP_POISON
419
                    : ProgressColorId::PROG_HP);
420
                Color color = Theme::getProgressColor(
421
                    themeColor, static_cast<float>(a->getHp())
422
                    / static_cast<float>(a->getMaxHp()));
423
                color.a = 80;
424
                graphics->setColor(color);
425
                graphics->fillRectangle(Rect(mPadding, y + mPadding,
426
                    parentWidth * a->getHp() / a->getMaxHp()
427
                    - 2 * mPadding, fontHeight));
428
            }
429
        }
430
        else if (a->getDamageHp() != 0 && a->getName() != name)
431
        {
432
            if (mShowLevel && a->getLevel() > 1)
433
            {
434
                text = strprintf("%s -%d (%d)", a->getComplexName().c_str(),
435
                                 a->getDamageHp(), a->getLevel());
436
            }
437
            else
438
            {
439
                text = strprintf("%s -%d", a->getComplexName().c_str(),
440
                                 a->getDamageHp());
441
            }
442
443
            const ProgressColorIdT themeColor = (a->getPoison()
444
                ? ProgressColorId::PROG_HP_POISON : ProgressColorId::PROG_HP);
445
            Color color = Theme::getProgressColor(themeColor, 1);
446
            color.a = 80;
447
            graphics->setColor(color);
448
            graphics->fillRectangle(Rect(mPadding, y + mPadding,
449
                parentWidth * a->getDamageHp() / 1024
450
                - 2 * mPadding, fontHeight));
451
452
            if (a->getLevel() > 1)
453
            {
454
                graphics->setColor(mForegroundColor);
455
                int minHp = 40 + ((a->getLevel() - 1) * 5);
456
                if (minHp < 0)
457
                    minHp = 40;
458
459
                graphics->drawLine(parentWidth * minHp / 1024
460
                    + mPadding, y + mPadding,
461
                    parentWidth * minHp / 1024, y + fontHeight);
462
            }
463
        }
464
        else
465
        {
466
            if (mShowLevel && a->getLevel() > 1)
467
            {
468
                text = strprintf("%s (%d)", a->getComplexName().c_str(),
469
                                 a->getLevel());
470
            }
471
            else
472
            {
473
                text = a->getComplexName();
474
            }
475
        }
476
477
        if (!a->getMap().empty())
478
        {
479
            if (a->getX() != -1)
480
            {
481
                text.append(strprintf(" [%d,%d %s]", a->getX(), a->getY(),
482
                    a->getMap().c_str()));
483
            }
484
            else
485
            {
486
                text.append(strprintf(" [%s]", a->getMap().c_str()));
487
            }
488
        }
489
490
        if (graphics->getSecure())
491
        {
492
            if (mShowGender)
493
            {
494
                switch (a->getGender())
495
                {
496
                    case Gender::FEMALE:
497
                        text.append(" \u2640 ");
498
                        break;
499
                    case Gender::MALE:
500
                        text.append(" \u2642 ");
501
                        break;
502
                    default:
503
                    case Gender::UNSPECIFIED:
504
                        break;
505
                }
506
            }
507
        }
508
        else
509
        {
510
            if (mShowGender)
511
            {
512
                switch (a->getGender())
513
                {
514
                    case Gender::FEMALE:
515
                        text.append(strprintf(" \u2640 %s",
516
                            a->getAdditionString().c_str()));
517
                        break;
518
                    case Gender::MALE:
519
                        text.append(strprintf(" \u2642 %s",
520
                            a->getAdditionString().c_str()));
521
                        break;
522
                    default:
523
                    case Gender::UNSPECIFIED:
524
                        break;
525
                }
526
            }
527
            else
528
            {
529
                text.append(a->getAdditionString());
530
            }
531
        }
532
533
        // Draw Name
534
        if (a->getDisplayBold())
535
        {
536
            if (type == MapItemType::SEPARATOR)
537
            {
538
                boldFont->drawString(graphics,
539
                    mForegroundColor,
540
                    mForegroundColor,
541
                    text,
542
                    mImagePadding + mPadding,
543
                    y + mPadding);
544
            }
545
            else
546
            {
547
                boldFont->drawString(graphics,
548
                    mForegroundColor,
549
                    mForegroundColor,
550
                    text,
551
                    15 + mImagePadding + mPadding,
552
                    y + mPadding);
553
            }
554
        }
555
        else
556
        {
557
            if (type == MapItemType::SEPARATOR)
558
            {
559
                font->drawString(graphics,
560
                    mForegroundColor,
561
                    mForegroundColor,
562
                    text,
563
                    mImagePadding + mPadding,
564
                    y + mPadding);
565
            }
566
            else
567
            {
568
                font->drawString(graphics,
569
                    mForegroundColor,
570
                    mForegroundColor,
571
                    text,
572
                    15 + mImagePadding + mPadding,
573
                    y + mPadding);
574
            }
575
        }
576
    }
577
578
    setWidth(parentWidth - 10);
579
    BLOCK_END("AvatarListBox::draw")
580
}
581
582
void AvatarListBox::mousePressed(MouseEvent &event)
583
{
584
    if (actorManager == nullptr ||
585
        localPlayer == nullptr ||
586
        popupManager == nullptr)
587
    {
588
        return;
589
    }
590
591
    const int height = getFont()->getHeight();
592
    if (height == 0)
593
        return;
594
595
    const int y = (event.getY() - mPadding) / height;
596
    if ((mListModel == nullptr) || y > mListModel->getNumberOfElements())
597
        return;
598
599
    setSelected(y);
600
    distributeActionEvent();
601
    const int selected = getSelected();
602
    AvatarListModel *const model = static_cast<AvatarListModel *>(
603
        mListModel);
604
    if (model == nullptr)
605
        return;
606
    const Avatar *ava = model->getAvatarAt(selected);
607
    if (ava == nullptr)
608
        return;
609
610
    const MapItemType::Type type = static_cast<MapItemType::Type>(
611
        ava->getType());
612
613
    event.consume();
614
    const MouseButtonT eventButton = event.getButton();
615
    if (eventButton == MouseButton::LEFT)
616
    {
617
#ifdef TMWA_SUPPORT
618
        if (type == MapItemType::EMPTY)
619
        {
620
            const Being *const being = actorManager->findBeingByName(
621
                ava->getName(), ActorType::Player);
622
            if (being != nullptr)
623
                actorManager->heal(being);
624
        }
625
        else
626
#endif  // TMWA_SUPPORT
627
        {
628
            localPlayer->navigateTo(ava->getX(), ava->getY());
629
        }
630
    }
631
    else if (eventButton == MouseButton::RIGHT)
632
    {
633
        switch (type)
634
        {
635
            case MapItemType::EMPTY:
636
            {
637
                const Avatar *const avatar = model->getAvatarAt(selected);
638
                if (avatar != nullptr)
639
                {
640
                    const Being *const being = actorManager->findBeingByName(
641
                        avatar->getName(), ActorType::Player);
642
                    if (being != nullptr)
643
                    {
644
                        popupMenu->showPopup(viewport->mMouseX,
645
                            viewport->mMouseY,
646
                            being);
647
                    }
648
                    else
649
                    {
650
                        popupMenu->showPlayerPopup(avatar->getName());
651
                    }
652
                }
653
                break;
654
            }
655
            case MapItemType::ATTACK:
656
            case MapItemType::PRIORITY:
657
            case MapItemType::IGNORE_:
658
            {
659
                std::string name;
660
                if (model->getAvatarAt(selected)->getLevel() == 0)
661
                    name.clear();
662
                else
663
                    name = model->getAvatarAt(selected)->getName();
664
665
                popupMenu->showAttackMonsterPopup(viewport->mMouseX,
666
                    viewport->mMouseY,
667
                    name,
668
                    model->getAvatarAt(selected)->getType());
669
                break;
670
            }
671
            case MapItemType::PICKUP:
672
            case MapItemType::NOPICKUP:
673
            {
674
                std::string name;
675
                if (model->getAvatarAt(selected)->getLevel() == 0)
676
                    name.clear();
677
                else
678
                    name = model->getAvatarAt(selected)->getName();
679
680
                popupMenu->showPickupItemPopup(viewport->mMouseX,
681
                    viewport->mMouseY,
682
                    name);
683
                break;
684
            }
685
            case MapItemType::HOME:
686
            case MapItemType::ROAD:
687
            case MapItemType::CROSS:
688
            case MapItemType::ARROW_UP:
689
            case MapItemType::ARROW_DOWN:
690
            case MapItemType::ARROW_LEFT:
691
            case MapItemType::ARROW_RIGHT:
692
            case MapItemType::PORTAL:
693
            case MapItemType::MUSIC:
694
            case MapItemType::SEPARATOR:
695
            {
696
                break;
697
            }
698
            default:
699
            {
700
                const Map *const map = viewport->getMap();
701
                ava = model->getAvatarAt(selected);
702
                if ((map != nullptr) && (ava != nullptr))
703
                {
704
                    MapItem *const mapItem = map->findPortalXY(
705
                        ava->getX(), ava->getY());
706
                    popupMenu->showPopup(viewport->mMouseX,
707
                        viewport->mMouseY,
708
                        mapItem);
709
                }
710
                break;
711
            }
712
        }
713
    }
714
    else if (eventButton == MouseButton::MIDDLE)
715
    {
716
        if (type == MapItemType::EMPTY && (chatWindow != nullptr))
717
        {
718
            const std::string &name = model->getAvatarAt(selected)->getName();
719
            const WhisperTab *const tab = chatWindow->addWhisperTab(
720
                name, name, true);
721
            if (tab != nullptr)
722
                chatWindow->saveState();
723
        }
724
    }
725
}
726
727
void AvatarListBox::mouseReleased(MouseEvent &event A_UNUSED)
728
{
729
}
730
731
void AvatarListBox::optionChanged(const std::string &value)
732
{
733
    if (value == "showgender")
734
        mShowGender = config.getBoolValue("showgender");
735
    else if (value == "showlevel")
736
        mShowLevel = config.getBoolValue("showlevel");
737
2
}