GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/charselectdialog.cpp Lines: 82 293 28.0 %
Date: 2021-03-17 Branches: 81 404 20.0 %

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
#include "gui/windows/charselectdialog.h"
25
26
#include "client.h"
27
#include "configuration.h"
28
#include "pincodemanager.h"
29
30
#include "listeners/charrenamelistener.h"
31
32
#include "gui/windows/charcreatedialog.h"
33
#include "gui/windows/chardeleteconfirm.h"
34
#include "gui/windows/editdialog.h"
35
#include "gui/windows/logindialog.h"
36
#include "gui/windows/okdialog.h"
37
#include "gui/windows/textdialog.h"
38
39
#include "gui/widgets/button.h"
40
#include "gui/widgets/characterdisplay.h"
41
#include "gui/widgets/characterviewnormal.h"
42
#include "gui/widgets/characterviewsmall.h"
43
#include "gui/widgets/containerplacer.h"
44
#include "gui/widgets/createwidget.h"
45
46
#include "net/character.h"
47
#include "net/charserverhandler.h"
48
#include "net/logindata.h"
49
#ifdef TMWA_SUPPORT
50
#include "net/net.h"
51
#endif  // TMWA_SUPPORT
52
#include "net/serverfeatures.h"
53
54
#include "utils/foreach.h"
55
56
#include "resources/db/unitsdb.h"
57
58
#include "debug.h"
59
60
// Character slots per row in the dialog
61
static const int SLOTS_PER_ROW = 5;
62
63
1
CharSelectDialog::CharSelectDialog(LoginData &data) :
64
    // TRANSLATORS: char select dialog name
65
4
    Window(strprintf(_("Account %s (last login time %s)"),
66
        data.username.c_str(), data.lastLogin.c_str()),
67
        Modal_false,
68
        nullptr,
69
        "char.xml"),
70
    ActionListener(),
71
    KeyListener(),
72
    mLoginData(&data),
73
    // TRANSLATORS: char select dialog. button.
74
1
    mSwitchLoginButton(new Button(this, _("Switch"), "switch",
75

1
        BUTTON_SKIN, this)),
76
    // TRANSLATORS: char select dialog. button.
77
1
    mChangePasswordButton(new Button(this, _("Password"), "change_password",
78

1
        BUTTON_SKIN, this)),
79
    // TRANSLATORS: char select dialog. button.
80

2
    mPlayButton(new Button(this, _("Play"), "use", BUTTON_SKIN, this)),
81
    // TRANSLATORS: char select dialog. button.
82

2
    mInfoButton(new Button(this, _("Info"), "info", BUTTON_SKIN, this)),
83
    // TRANSLATORS: char select dialog. button.
84

2
    mDeleteButton(new Button(this, _("Delete"), "delete", BUTTON_SKIN, this)),
85
    // TRANSLATORS: char select dialog. button.
86
    mRenameButton(nullptr),
87
    mChangePinButton(nullptr),
88
    mCharacterView(nullptr),
89
    mCharacterEntries(0),
90
    mCharServerHandler(charServerHandler),
91
    mDeleteDialog(nullptr),
92
    mDeleteIndex(-1),
93
    mLocked(false),
94
1
    mSmallScreen(mainGraphics->getWidth() < 470
95







45
                 || mainGraphics->getHeight() < 370)
96
{
97
1
    setCloseButton(true);
98
1
    setFocusable(true);
99
100
1
    ContainerPlacer placer(nullptr, nullptr);
101
1
    placer = getPlacer(0, 0);
102
103
1
    placer(0, 0, mSwitchLoginButton, 1, 1);
104
105
1
    int n = 1;
106
1
    placer(n, 0, mChangePasswordButton, 1, 1);
107
1
    n ++;
108
1
    placer(n, 0, mDeleteButton, 1, 1);
109
1
    n ++;
110
#ifdef TMWA_SUPPORT
111

1
    if (Net::getNetworkType() != ServerType::TMWATHENA)
112
#endif
113
    {
114
1
        mRenameButton = new Button(this,
115
            // TRANSLATORS: character rename button
116
1
            _("Rename"),
117
            "rename",
118
            BUTTON_SKIN,
119


7
            this);
120
1
        placer(n, 0, mRenameButton, 1, 1);
121
        n ++;
122
    }
123
#ifdef TMWA_SUPPORT
124

1
    if (serverFeatures->havePincode())
125
#endif
126
    {
127
1
        mChangePinButton = new Button(this,
128
            // TRANSLATORS: change pincode button
129
1
            _("Change pin"),
130
            "changepin",
131
            BUTTON_SKIN,
132


7
            this);
133
1
        placer(n, 0, mChangePinButton, 1, 1);
134
1
        n ++;
135
    }
136
1
    placer(n, 0, mInfoButton, 1, 1);
137
    n ++;
138
139
19
    for (int i = 0; i < CAST_S32(mLoginData->characterSlots); i++)
140
    {
141

9
        CharacterDisplay *const character = new CharacterDisplay(this, this);
142
9
        character->setVisible(Visible_false);
143
9
        mCharacterEntries.push_back(character);
144
    }
145
146
1
    placer(0, 2, mPlayButton, 1, 1);
147
148
1
    if (!mSmallScreen)
149
    {
150
1
        mCharacterView = new CharacterViewNormal(
151

1
            this, &mCharacterEntries, mPadding);
152
1
        placer(0, 1, mCharacterView, 10, 1);
153
1
        int sz = 410 + 2 * mPadding;
154

4
        if (config.getIntValue("fontSize") > 18)
155
            sz = 500 + 2 * mPadding;
156
2
        const int width = mCharacterView->getWidth() + 2 * mPadding;
157
1
        if (sz < width)
158
1
            sz = width;
159

1
        if (sz > mainGraphics->getWidth())
160
            sz = mainGraphics->getWidth();
161
1
        reflowLayout(sz, 0);
162
    }
163
    else
164
    {
165
        // TRANSLATORS: char select dialog name
166
        setCaption(strprintf(_("Account %s"), mLoginData->username.c_str()));
167
        mCharacterView = new CharacterViewSmall(
168
            this, &mCharacterEntries, mPadding);
169
        mCharacterView->setWidth(mainGraphics->getWidth()
170
            - 2 * getPadding());
171
        placer(0, 1, mCharacterView, 10, 1);
172
        reflowLayout(0, 0);
173
    }
174
1
    addKeyListener(this);
175
1
    center();
176
177
1
    charServerHandler->setCharSelectDialog(this);
178
1
    mCharacterView->show(0);
179
1
    updateState();
180
1
}
181
182
6
CharSelectDialog::~CharSelectDialog()
183
{
184
1
    mDeleteDialog = nullptr;
185
2
}
186
187
1
void CharSelectDialog::postInit()
188
{
189
1
    Window::postInit();
190
1
    setVisible(Visible_true);
191
1
    requestFocus();
192
1
}
193
194
void CharSelectDialog::action(const ActionEvent &event)
195
{
196
    // Check if a button of a character was pressed
197
    const Widget *const sourceParent = event.getSource()->getParent();
198
    int selected = -1;
199
    for (unsigned int i = 0, fsz = CAST_U32(
200
         mCharacterEntries.size());
201
         i < fsz;
202
         ++i)
203
    {
204
        if (mCharacterEntries[i] == sourceParent)
205
        {
206
            selected = i;
207
            mCharacterView->show(i);
208
            updateState();
209
            break;
210
        }
211
    }
212
    if (selected == -1)
213
        selected = mCharacterView->getSelected();
214
215
    const std::string &eventId = event.getId();
216
    const bool blocked = pincodeManager.isBlocked();
217
218
    if (selected >= 0)
219
    {
220
        if (blocked)
221
            return;
222
        if (eventId == "use")
223
        {
224
            use(selected);
225
            return;
226
        }
227
        else if (eventId == "delete"
228
                 && (mCharacterEntries[selected]->getCharacter() != nullptr))
229
        {
230
            CREATEWIDGET(CharDeleteConfirm, this, selected);
231
            return;
232
        }
233
        else if (eventId == "rename"
234
                 && (mCharacterEntries[selected]->getCharacter() != nullptr))
235
        {
236
            const LocalPlayer *const player = mCharacterEntries[
237
                selected]->getCharacter()->dummy;
238
            EditDialog *const dialog = CREATEWIDGETR(EditDialog,
239
                // TRANSLATORS: character rename dialog header.
240
                _("Please enter new name"),
241
                player->getName(),
242
                "OK",
243
                300,
244
                nullptr,
245
                Modal_true);
246
            charRenameListener.setId(player->getId());
247
            charRenameListener.setDialog(dialog);
248
            dialog->addActionListener(&charRenameListener);
249
        }
250
        else if (eventId == "info")
251
        {
252
            Net::Character *const character = mCharacterEntries[
253
                selected]->getCharacter();
254
            if (character == nullptr)
255
                return;
256
257
            const LocalPlayer *const data = character->dummy;
258
            if (data == nullptr)
259
                return;
260
261
            const std::string strExp = toString(CAST_U64(
262
                character->data.mAttributes[Attributes::PLAYER_EXP]));
263
            const std::string msg = strprintf(
264
                // TRANSLATORS: char select dialog. player info message.
265
                _("Hp: %u/%u\nMp: %u/%u\nLevel: %u\n"
266
                "Experience: %s\nMoney: %s"),
267
                CAST_U32(
268
                character->data.mAttributes[Attributes::PLAYER_HP]),
269
                CAST_U32(
270
                character->data.mAttributes[Attributes::PLAYER_MAX_HP]),
271
                CAST_U32(
272
                character->data.mAttributes[Attributes::PLAYER_MP]),
273
                CAST_U32(
274
                character->data.mAttributes[Attributes::PLAYER_MAX_MP]),
275
                CAST_U32(
276
                character->data.mAttributes[Attributes::PLAYER_BASE_LEVEL]),
277
                strExp.c_str(),
278
                UnitsDb::formatCurrency(CAST_S32(
279
                character->data.mAttributes[Attributes::MONEY])).c_str());
280
            CREATEWIDGET(OkDialog, data->getName(), msg,
281
                // TRANSLATORS: ok dialog button
282
                _("OK"),
283
                DialogType::SILENCE,
284
                Modal_true,
285
                ShowCenter_true,
286
                nullptr,
287
                260);
288
        }
289
    }
290
    if (eventId == "switch")
291
    {
292
        charServerHandler->clear();
293
        close();
294
    }
295
    else if (eventId == "change_password")
296
    {
297
        if (blocked)
298
            return;
299
        client->setState(State::CHANGEPASSWORD);
300
    }
301
    else if (eventId == "change_email")
302
    {
303
        if (blocked)
304
            return;
305
        client->setState(State::CHANGEEMAIL);
306
    }
307
    else if (eventId == "try delete character")
308
    {
309
        if (blocked)
310
            return;
311
        if ((mDeleteDialog != nullptr) && mDeleteIndex != -1)
312
        {
313
            if (serverFeatures->haveEmailOnDelete())
314
            {
315
                attemptCharacterDelete(mDeleteIndex, mDeleteDialog->getText());
316
                mDeleteDialog = nullptr;
317
            }
318
            else if (mDeleteDialog->getText() == LoginDialog::savedPassword)
319
            {
320
                attemptCharacterDelete(mDeleteIndex, "");
321
                mDeleteDialog = nullptr;
322
            }
323
            else
324
            {
325
                CREATEWIDGET(OkDialog,
326
                    // TRANSLATORS: error header
327
                    _("Error"),
328
                    // TRANSLATORS: error message
329
                    _("Incorrect password"),
330
                    // TRANSLATORS: ok dialog button
331
                    _("OK"),
332
                    DialogType::ERROR,
333
                    Modal_true,
334
                    ShowCenter_true,
335
                    nullptr,
336
                    260);
337
            }
338
        }
339
        mDeleteIndex = -1;
340
    }
341
    else if (eventId == "changepin")
342
    {
343
        if (blocked)
344
            return;
345
        pincodeManager.setState(PincodeState::Change);
346
        pincodeManager.updateState();
347
    }
348
}
349
350
void CharSelectDialog::use(const int selected)
351
{
352
    if ((mCharacterEntries[selected] != nullptr)
353
        && (mCharacterEntries[selected]->getCharacter() != nullptr))
354
    {
355
        attemptCharacterSelect(selected);
356
    }
357
    else
358
    {
359
        CharCreateDialog *const charCreateDialog =
360
            CREATEWIDGETR(CharCreateDialog, this, selected);
361
        mCharServerHandler->setCharCreateDialog(charCreateDialog);
362
    }
363
}
364
365
void CharSelectDialog::keyPressed(KeyEvent &event)
366
{
367
    const InputActionT actionId = event.getActionId();
368
    const bool blocked = pincodeManager.isBlocked();
369
    PRAGMA45(GCC diagnostic push)
370
    PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
371
    switch (actionId)
372
    {
373
        case InputAction::GUI_CANCEL:
374
            event.consume();
375
            action(ActionEvent(mSwitchLoginButton,
376
                mSwitchLoginButton->getActionEventId()));
377
            break;
378
379
        case InputAction::GUI_RIGHT:
380
        {
381
            event.consume();
382
            int idx = mCharacterView->getSelected();
383
            if (idx >= 0)
384
            {
385
                if ((idx + 1) % SLOTS_PER_ROW == 0)
386
                    break;
387
                idx ++;
388
                mCharacterView->show(idx);
389
                updateState();
390
            }
391
            break;
392
        }
393
394
        case InputAction::GUI_LEFT:
395
        {
396
            event.consume();
397
            int idx = mCharacterView->getSelected();
398
            if (idx >= 0)
399
            {
400
                if (idx == 0 || idx % SLOTS_PER_ROW == 0)
401
                    break;
402
                idx --;
403
                mCharacterView->show(idx);
404
                updateState();
405
            }
406
            break;
407
        }
408
409
        case InputAction::GUI_UP:
410
        {
411
            event.consume();
412
            int idx = mCharacterView->getSelected();
413
            if (idx >= 0)
414
            {
415
                if (idx < SLOTS_PER_ROW)
416
                    break;
417
                idx -= SLOTS_PER_ROW;
418
                mCharacterView->show(idx);
419
                updateState();
420
            }
421
            break;
422
        }
423
424
        case InputAction::GUI_DOWN:
425
        {
426
            event.consume();
427
            int idx = mCharacterView->getSelected();
428
            if (idx >= 0)
429
            {
430
                if (idx >= mLoginData->characterSlots - SLOTS_PER_ROW)
431
                    break;
432
                idx += SLOTS_PER_ROW;
433
                mCharacterView->show(idx);
434
                updateState();
435
            }
436
            break;
437
        }
438
439
        case InputAction::GUI_DELETE:
440
        {
441
            if (blocked)
442
                return;
443
            event.consume();
444
            const int idx = mCharacterView->getSelected();
445
            if (idx >= 0 && (mCharacterEntries[idx] != nullptr)
446
                && (mCharacterEntries[idx]->getCharacter() != nullptr))
447
            {
448
                CREATEWIDGET(CharDeleteConfirm, this, idx);
449
            }
450
            break;
451
        }
452
453
        case InputAction::GUI_SELECT:
454
        {
455
            if (blocked)
456
                return;
457
            event.consume();
458
            use(mCharacterView->getSelected());
459
            break;
460
        }
461
        default:
462
            break;
463
    }
464
    PRAGMA45(GCC diagnostic pop)
465
}
466
467
/**
468
 * Communicate character deletion to the server.
469
 */
470
void CharSelectDialog::attemptCharacterDelete(const int index,
471
                                              const std::string &email)
472
{
473
    if (mLocked)
474
        return;
475
476
    if (mCharacterEntries[index] != nullptr)
477
    {
478
        mCharServerHandler->deleteCharacter(
479
            mCharacterEntries[index]->getCharacter(),
480
            email);
481
    }
482
    lock();
483
}
484
485
void CharSelectDialog::askPasswordForDeletion(const int index)
486
{
487
    mDeleteIndex = index;
488
    if (serverFeatures->haveEmailOnDelete())
489
    {
490
        CREATEWIDGETV(mDeleteDialog, TextDialog,
491
            // TRANSLATORS: char deletion question.
492
            _("Enter your email for deleting character"),
493
            // TRANSLATORS: email label.
494
            _("Enter email:"),
495
            this,
496
            false);
497
    }
498
    else
499
    {
500
        CREATEWIDGETV(mDeleteDialog, TextDialog,
501
            // TRANSLATORS: char deletion question.
502
            _("Enter password for deleting character"),
503
            // TRANSLATORS: email label.
504
            _("Enter password:"),
505
            this,
506
            true);
507
    }
508
    mDeleteDialog->setActionEventId("try delete character");
509
    mDeleteDialog->addActionListener(this);
510
}
511
512
/**
513
 * Communicate character selection to the server.
514
 */
515
void CharSelectDialog::attemptCharacterSelect(const int index)
516
{
517
    if (mLocked || (mCharacterEntries[index] == nullptr))
518
        return;
519
520
    setVisible(Visible_false);
521
    if (mCharServerHandler != nullptr)
522
    {
523
        mCharServerHandler->chooseCharacter(
524
            mCharacterEntries[index]->getCharacter());
525
    }
526
    lock();
527
}
528
529
1
void CharSelectDialog::setCharacters(const Net::Characters &characters)
530
{
531
    // Reset previous characters
532
15
    FOR_EACH (STD_VECTOR<CharacterDisplay*>::const_iterator,
533
              iter, mCharacterEntries)
534
    {
535
9
        if (*iter != nullptr)
536
9
            (*iter)->setCharacter(nullptr);
537
    }
538
539
3
    FOR_EACH (Net::Characters::const_iterator, i, characters)
540
        setCharacter(*i);
541
1
    updateState();
542
1
}
543
544
void CharSelectDialog::setCharacter(Net::Character *const character)
545
{
546
    if (character == nullptr)
547
        return;
548
    const int characterSlot = character->slot;
549
    if (characterSlot >= CAST_S32(mCharacterEntries.size()))
550
    {
551
        logger->log("Warning: slot out of range: %d", character->slot);
552
        return;
553
    }
554
555
    if (mCharacterEntries[characterSlot] != nullptr)
556
        mCharacterEntries[characterSlot]->setCharacter(character);
557
}
558
559
void CharSelectDialog::lock()
560
{
561
    if (!mLocked)
562
        setLocked(true);
563
}
564
565
void CharSelectDialog::unlock()
566
{
567
    setLocked(false);
568
}
569
570
void CharSelectDialog::setLocked(const bool locked)
571
{
572
    mLocked = locked;
573
574
    if (mSwitchLoginButton != nullptr)
575
        mSwitchLoginButton->setEnabled(!locked);
576
    if (mChangePasswordButton != nullptr)
577
        mChangePasswordButton->setEnabled(!locked);
578
    mPlayButton->setEnabled(!locked);
579
    if (mDeleteButton != nullptr)
580
        mDeleteButton->setEnabled(!locked);
581
582
    for (size_t i = 0, sz = mCharacterEntries.size(); i < sz; ++i)
583
    {
584
        if (mCharacterEntries[i] != nullptr)
585
            mCharacterEntries[i]->setActive(!mLocked);
586
    }
587
}
588
589
bool CharSelectDialog::selectByName(const std::string &name,
590
                                    const SelectAction selAction)
591
{
592
    if (mLocked)
593
        return false;
594
595
    for (size_t i = 0, sz = mCharacterEntries.size(); i < sz; ++i)
596
    {
597
        if (mCharacterEntries[i] != nullptr)
598
        {
599
            const Net::Character *const character
600
                = mCharacterEntries[i]->getCharacter();
601
            if (character != nullptr)
602
            {
603
                if (character->dummy != nullptr &&
604
                    character->dummy->getName() == name)
605
                {
606
                    mCharacterView->show(CAST_S32(i));
607
                    updateState();
608
                    if (selAction == Choose)
609
                        attemptCharacterSelect(CAST_S32(i));
610
                    return true;
611
                }
612
            }
613
        }
614
    }
615
616
    return false;
617
}
618
619
void CharSelectDialog::close()
620
{
621
    client->setState(State::SWITCH_LOGIN);
622
    Window::close();
623
}
624
625
1
void CharSelectDialog::widgetResized(const Event &event)
626
{
627
1
    Window::widgetResized(event);
628
1
    mCharacterView->resize();
629
1
}
630
631
2
void CharSelectDialog::updateState()
632
{
633
2
    const int idx = mCharacterView->getSelected();
634
2
    if (idx == -1)
635
    {
636
        mPlayButton->setEnabled(false);
637
        return;
638
    }
639
4
    mPlayButton->setEnabled(true);
640
641

6
    if (mCharacterEntries[idx] != nullptr &&
642
2
        mCharacterEntries[idx]->getCharacter() != nullptr)
643
    {
644
        // TRANSLATORS: char select dialog. button.
645
        mPlayButton->setCaption(_("Play"));
646
647
        const LocalPlayer *const player = mCharacterEntries[
648
            idx]->getCharacter()->dummy;
649
        if ((player != nullptr) && (mRenameButton != nullptr))
650
            mRenameButton->setEnabled(player->getRename() ? true : false);
651
    }
652
    else
653
    {
654
        // TRANSLATORS: char select dialog. button.
655
10
        mPlayButton->setCaption(_("Create"));
656
    }
657
}
658
659
void CharSelectDialog::setName(const BeingId id, const std::string &newName)
660
{
661
    for (unsigned int i = 0, fsz = CAST_U32(
662
         mCharacterEntries.size());
663
         i < fsz;
664
         ++i)
665
    {
666
        CharacterDisplay *const character = mCharacterEntries[i];
667
        if (character == nullptr)
668
            continue;
669
        LocalPlayer *const player = character->getCharacter()->dummy;
670
        if ((player != nullptr) && player->getId() == id)
671
        {
672
            player->setName(newName);
673
            character->update();
674
            return;
675
        }
676
    }
677

3
}