GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/windows/tradewindow.cpp Lines: 96 248 38.7 %
Date: 2021-03-17 Branches: 87 317 27.4 %

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/tradewindow.h"
25
26
#include "configuration.h"
27
#include "game.h"
28
29
#include "being/localplayer.h"
30
#include "being/playerinfo.h"
31
#include "being/playerrelations.h"
32
33
#include "enums/gui/layouttype.h"
34
35
#include "gui/gui.h"
36
37
#include "gui/fonts/font.h"
38
39
#include "gui/windows/inventorywindow.h"
40
#include "gui/windows/itemamountwindow.h"
41
#include "gui/windows/setupwindow.h"
42
43
#include "gui/widgets/button.h"
44
#include "gui/widgets/containerplacer.h"
45
#include "gui/widgets/itemcontainer.h"
46
#include "gui/widgets/label.h"
47
#include "gui/widgets/layout.h"
48
#include "gui/widgets/scrollarea.h"
49
#include "gui/widgets/textfield.h"
50
51
#include "gui/widgets/tabs/chat/chattab.h"
52
53
#include "resources/db/unitsdb.h"
54
55
#include "resources/item/item.h"
56
57
#include "net/net.h"
58
#include "net/tradehandler.h"
59
60
#include "utils/delete2.h"
61
#include "utils/gettext.h"
62
63
#include "debug.h"
64
65
TradeWindow *tradeWindow = nullptr;
66
67
// TRANSLATORS: trade window button
68
#define CAPTION_PROPOSE _("Propose trade")
69
// TRANSLATORS: trade window button
70
#define CAPTION_CONFIRMED _("Confirmed. Waiting...")
71
// TRANSLATORS: trade window button
72
#define CAPTION_ACCEPT _("Agree trade")
73
// TRANSLATORS: trade window button
74
#define CAPTION_ACCEPTED _("Agreed. Waiting...")
75
76
1
TradeWindow::TradeWindow() :
77
    // TRANSLATORS: trade window caption
78
1
    Window(_("Trade: You"), Modal_false, nullptr, "trade.xml"),
79
    ActionListener(),
80
    SelectionListener(),
81

1
    mMyInventory(new Inventory(InventoryType::Trade, -1)),
82

1
    mPartnerInventory(new Inventory(InventoryType::Trade, -1)),
83
    mMyItemContainer(new ItemContainer(this, mMyInventory, 100000,
84

1
        ShowEmptyRows_false, ForceQuantity_false)),
85
    mPartnerItemContainer(new ItemContainer(this, mPartnerInventory, 100000,
86

1
        ShowEmptyRows_false, ForceQuantity_false)),
87
    // TRANSLATORS: trade window money label
88

2
    mMoneyLabel(new Label(this, strprintf(_("You get %s"), ""))),
89
    // TRANSLATORS: trade window button
90

2
    mAddButton(new Button(this, _("Add"), "add", BUTTON_SKIN, this)),
91
    mOkButton(new Button(this, "", "",
92

1
        BUTTON_SKIN, this)),  // Will be filled in later
93
    // TRANSLATORS: trade window money change button
94
1
    mMoneyChangeButton(new Button(this, _("Change"), "money",
95

1
        BUTTON_SKIN, this)),
96
2
    mMoneyField(new TextField(this, std::string(),
97

2
        LoseFocusOnTab_true, nullptr, std::string(), false)),
98
    mAutoAddItem(nullptr),
99
    mAutoAddToNick(""),
100
    mGotMoney(0),
101
    mGotMaxMoney(0),
102
    mAutoMoney(0),
103
    mAutoAddAmount(0),
104
    mStatus(PROPOSING),
105
    mOkOther(false),
106




40
    mOkMe(false)
107
{
108
4
    setWindowName("Trade");
109
1
    setResizable(true);
110
1
    setCloseButton(true);
111
1
    setStickyButtonLock(true);
112
1
    setDefaultSize(386, 180, ImagePosition::CENTER, 0, 0);
113
1
    setMinWidth(310);
114
1
    setMinHeight(180);
115
116
1
    if (setupWindow != nullptr)
117
        setupWindow->registerWindowForReset(this);
118
119
1
    const Font *const fnt = mOkButton->getFont();
120

5
    int width = std::max(fnt->getWidth(CAPTION_PROPOSE),
121

6
        fnt->getWidth(CAPTION_CONFIRMED));
122

4
    width = std::max(width, fnt->getWidth(CAPTION_ACCEPT));
123

4
    width = std::max(width, fnt->getWidth(CAPTION_ACCEPTED));
124
125
1
    mOkButton->setWidth(8 + width);
126
127
2
    mMyItemContainer->addSelectionListener(this);
128
129
1
    ScrollArea *const myScroll = new ScrollArea(this, mMyItemContainer,
130

4
        Opaque_true, "trade_background.xml");
131
1
    myScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
132
133
2
    mPartnerItemContainer->addSelectionListener(this);
134
135
    ScrollArea *const partnerScroll = new ScrollArea(this,
136
1
        mPartnerItemContainer,
137

4
        Opaque_true, "trade_background.xml");
138
1
    partnerScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
139
140
    // TRANSLATORS: trade window money label
141

3
    Label *const moneyLabel2 = new Label(this, _("You give:"));
142
143
1
    mMoneyField->setWidth(40);
144
145
1
    place(1, 0, mMoneyLabel, 1, 1);
146
2
    place(0, 1, myScroll, 1, 1).setPadding(3);
147
2
    place(1, 1, partnerScroll, 1, 1).setPadding(3);
148
1
    ContainerPlacer placer = getPlacer(0, 0);
149
1
    placer(0, 0, moneyLabel2, 1, 1);
150
1
    placer(1, 0, mMoneyField, 2, 1);
151
2
    placer(3, 0, mMoneyChangeButton, 1, 1).setHAlign(LayoutCell::LEFT);
152
1
    placer = getPlacer(0, 2);
153
1
    placer(0, 0, mAddButton, 1, 1);
154
1
    placer(1, 0, mOkButton, 1, 1);
155
1
    Layout &layout = getLayout();
156
1
    layout.extend(0, 2, 2, 1);
157
1
    layout.setRowHeight(1, LayoutType::SET);
158
1
    layout.setRowHeight(2, 0);
159
1
    layout.setColWidth(0, LayoutType::SET);
160
1
    layout.setColWidth(1, LayoutType::SET);
161
162
1
    loadWindowState();
163
2
    enableVisibleSound(true);
164
165
1
    reset();
166
1
}
167
168
6
TradeWindow::~TradeWindow()
169
{
170
1
    delete2(mMyInventory)
171
1
    delete2(mPartnerInventory)
172
2
}
173
174
1
void TradeWindow::setMoney(const int amount)
175
{
176

1
    if (amount < 0 || amount < mGotMaxMoney)
177
    {
178
        if (config.getBoolValue("securetrades"))
179
        {
180
            close();
181
            return;
182
        }
183
        mMoneyLabel->setForegroundColorAll(
184
            getThemeColor(ThemeColorId::WARNING, 255U),
185
            getThemeColor(ThemeColorId::WARNING_OUTLINE, 255U));
186
    }
187
    else
188
    {
189
3
        mMoneyLabel->setForegroundColorAll(
190
            getThemeColor(ThemeColorId::LABEL, 255U),
191
1
            getThemeColor(ThemeColorId::LABEL_OUTLINE, 255U));
192
1
        mGotMaxMoney = amount;
193
    }
194
195
1
    mGotMoney = amount;
196
    // TRANSLATORS: trade window money label
197
2
    mMoneyLabel->setCaption(strprintf(_("You get %s"),
198
3
        UnitsDb::formatCurrency(amount).c_str()));
199
1
    mMoneyLabel->adjustSize();
200
}
201
202
void TradeWindow::addItem(const int id,
203
                          const ItemTypeT type,
204
                          const bool own,
205
                          const int quantity,
206
                          const uint8_t refine,
207
                          const ItemColor color,
208
                          const Identified identified,
209
                          const Damaged damaged,
210
                          const Favorite favorite) const
211
{
212
    Inventory *const inv = own ? mMyInventory : mPartnerInventory;
213
    inv->addItem(id,
214
        type,
215
        quantity,
216
        refine,
217
        color,
218
        identified,
219
        damaged,
220
        favorite,
221
        Equipm_false,
222
        Equipped_false);
223
}
224
225
void TradeWindow::addItem2(const int id,
226
                           const ItemTypeT type,
227
                           const int *const cards,
228
                           const ItemOptionsList *const options,
229
                           const int sz,
230
                           const bool own,
231
                           const int quantity,
232
                           const uint8_t refine,
233
                           const ItemColor color,
234
                           const Identified identified,
235
                           const Damaged damaged,
236
                           const Favorite favorite,
237
                           const Equipm equipment) const
238
{
239
    Inventory *const inv = own ? mMyInventory : mPartnerInventory;
240
    const int slot = inv->addItem(id,
241
        type,
242
        quantity,
243
        refine,
244
        color,
245
        identified,
246
        damaged,
247
        favorite,
248
        equipment,
249
        Equipped_false);
250
    if (slot >= 0)
251
    {
252
        inv->setCards(slot, cards, sz);
253
        inv->setOptions(slot, options);
254
    }
255
}
256
257
void TradeWindow::changeQuantity(const int index, const bool own,
258
                                 const int quantity) const
259
{
260
    Item *item;
261
    if (own)
262
        item = mMyInventory->getItem(index);
263
    else
264
        item = mPartnerInventory->getItem(index);
265
    if (item != nullptr)
266
        item->setQuantity(quantity);
267
}
268
269
void TradeWindow::increaseQuantity(const int index, const bool own,
270
                                   const int quantity) const
271
{
272
    Item *item;
273
    if (own)
274
        item = mMyInventory->getItem(index);
275
    else
276
        item = mPartnerInventory->getItem(index);
277
    if (item != nullptr)
278
        item->increaseQuantity(quantity);
279
}
280
281
1
void TradeWindow::reset()
282
{
283
1
    mMyInventory->clear();
284
1
    mPartnerInventory->clear();
285
1
    mOkOther = false;
286
1
    mOkMe = false;
287
1
    setMoney(0);
288
2
    mMoneyField->setEnabled(true);
289
3
    mMoneyField->setText("");
290
3
    mMoneyLabel->setForegroundColorAll(
291
        getThemeColor(ThemeColorId::LABEL, 255U),
292
1
        getThemeColor(ThemeColorId::LABEL_OUTLINE, 255U));
293
2
    mAddButton->setEnabled(true);
294
2
    mMoneyChangeButton->setEnabled(true);
295
1
    mGotMoney = 0;
296
1
    mGotMaxMoney = 0;
297
1
    setStatus(PREPARING);
298
1
}
299
300
void TradeWindow::receivedOk(const bool own)
301
{
302
    if (own)
303
        mOkMe = true;
304
    else
305
        mOkOther = true;
306
307
    if (mOkMe && mOkOther)
308
        setStatus(ACCEPTING);
309
}
310
311
void TradeWindow::completeTrade()
312
{
313
    if (config.getBoolValue("tradescreenshot"))
314
        Game::createScreenshot(std::string());
315
    setVisible(Visible_false);
316
    reset();
317
}
318
319
void TradeWindow::tradeItem(const Item *const item, const int quantity,
320
                            const bool check) const
321
{
322
    if (check && !checkItem(item))
323
        return;
324
325
    tradeHandler->addItem(item, quantity);
326
}
327
328
void TradeWindow::valueChanged(const SelectionEvent &event)
329
{
330
    if ((mMyItemContainer == nullptr) || (mPartnerItemContainer == nullptr))
331
        return;
332
333
    /* If an item is selected in one container, make sure no item is selected
334
     * in the other container.
335
     */
336
    if (event.getSource() == mMyItemContainer &&
337
        (mMyItemContainer->getSelectedItem() != nullptr))
338
    {
339
        mPartnerItemContainer->selectNone();
340
    }
341
    else if (mPartnerItemContainer->getSelectedItem() != nullptr)
342
    {
343
        mMyItemContainer->selectNone();
344
    }
345
}
346
347
1
void TradeWindow::setStatus(const Status s)
348
{
349
1
    if (s == mStatus)
350
        return;
351
1
    mStatus = s;
352
353

1
    switch (s)
354
    {
355
        case PREPARING:
356
4
            mOkButton->setCaption(CAPTION_PROPOSE);
357
4
            mOkButton->setActionEventId("ok");
358
1
            break;
359
        case PROPOSING:
360
            mOkButton->setCaption(CAPTION_CONFIRMED);
361
            mOkButton->setActionEventId("");
362
            break;
363
        case ACCEPTING:
364
            mOkButton->setCaption(CAPTION_ACCEPT);
365
            mOkButton->setActionEventId("trade");
366
            break;
367
        case ACCEPTED:
368
            mOkButton->setCaption(CAPTION_ACCEPTED);
369
            mOkButton->setActionEventId("");
370
            break;
371
        default:
372
            break;
373
    }
374
375
1
    mOkButton->setEnabled((s != PROPOSING && s != ACCEPTED));
376
}
377
378
void TradeWindow::action(const ActionEvent &event)
379
{
380
    if (inventoryWindow == nullptr)
381
        return;
382
383
    Item *const item = inventoryWindow->getSelectedItem();
384
    const std::string &eventId = event.getId();
385
386
    if (eventId == "add")
387
    {
388
        if (mStatus != PREPARING)
389
            return;
390
391
        if (!inventoryWindow->isWindowVisible())
392
        {
393
            inventoryWindow->setVisible(Visible_true);
394
            return;
395
        }
396
397
        if (item == nullptr)
398
            return;
399
400
        if (mMyInventory->getFreeSlot() == -1)
401
            return;
402
403
        if (!checkItem(item))
404
            return;
405
406
        // Choose amount of items to trade
407
        ItemAmountWindow::showWindow(ItemAmountWindowUsage::TradeAdd,
408
            this,
409
            item,
410
            0,
411
            0);
412
413
        setStatus(PREPARING);
414
    }
415
    else if (eventId == "cancel")
416
    {
417
        setVisible(Visible_false);
418
        reset();
419
        PlayerInfo::setTrading(Trading_false);
420
        tradeHandler->cancel();
421
    }
422
    else if (eventId == "ok")
423
    {
424
        mMoneyField->setEnabled(false);
425
        mAddButton->setEnabled(false);
426
        mMoneyChangeButton->setEnabled(false);
427
        receivedOk(true);
428
        setStatus(PROPOSING);
429
        tradeHandler->confirm();
430
    }
431
    else if (eventId == "trade")
432
    {
433
        receivedOk(true);
434
        setStatus(ACCEPTED);
435
        tradeHandler->finish();
436
    }
437
    else if (eventId == "money")
438
    {
439
        if (mStatus != PREPARING)
440
            return;
441
442
        int v = atoi(mMoneyField->getText().c_str());
443
        const int curMoney = PlayerInfo::getAttribute(Attributes::MONEY);
444
        if (v > curMoney)
445
        {
446
            if (localChatTab != nullptr)
447
            {
448
                // TRANSLATORS: trade error
449
                localChatTab->chatLog(_("You don't have enough money."),
450
                    ChatMsgType::BY_SERVER,
451
                    IgnoreRecord_false,
452
                    TryRemoveColors_true);
453
            }
454
            v = curMoney;
455
        }
456
        tradeHandler->setMoney(v);
457
        mMoneyField->setText(strprintf("%d", v));
458
    }
459
}
460
461
void TradeWindow::close()
462
{
463
    tradeHandler->cancel();
464
    clear();
465
}
466
467
void TradeWindow::clear()
468
{
469
    mAutoAddItem = nullptr;
470
    mAutoAddToNick.clear();
471
    mAutoMoney = 0;
472
    mAutoAddAmount = 0;
473
    mGotMoney = 0;
474
    mGotMaxMoney = 0;
475
    mMoneyLabel->setForegroundColorAll(
476
        getThemeColor(ThemeColorId::LABEL, 255U),
477
        getThemeColor(ThemeColorId::LABEL_OUTLINE, 255U));
478
}
479
480
void TradeWindow::addAutoItem(const std::string &nick, Item* const item,
481
                              const int amount)
482
{
483
    mAutoAddToNick = nick;
484
    mAutoAddItem = item;
485
    mAutoAddAmount = amount;
486
}
487
488
void TradeWindow::addAutoMoney(const std::string &nick, const int money)
489
{
490
    mAutoAddToNick = nick;
491
    mAutoMoney = money;
492
}
493
494
void TradeWindow::initTrade(const std::string &nick)
495
{
496
    if (localPlayer == nullptr)
497
        return;
498
499
    if (!mAutoAddToNick.empty() && mAutoAddToNick == nick)
500
    {
501
        if ((mAutoAddItem != nullptr) && (mAutoAddItem->getQuantity() != 0))
502
        {
503
            const Inventory *const inv = PlayerInfo::getInventory();
504
            if (inv != nullptr)
505
            {
506
                const Item *const item = inv->findItem(mAutoAddItem->getId(),
507
                    mAutoAddItem->getColor());
508
                if (item != nullptr)
509
                    tradeItem(item, mAutoAddAmount, false);
510
            }
511
        }
512
        if (mAutoMoney != 0)
513
        {
514
            tradeHandler->setMoney(mAutoMoney);
515
            mMoneyField->setText(strprintf("%d", mAutoMoney));
516
        }
517
    }
518
    clear();
519
    if (!playerRelations.isGoodName(nick))
520
        setCaptionFont(gui->getSecureFont());
521
}
522
523
bool TradeWindow::checkItem(const Item *const item) const
524
{
525
    if (item == nullptr)
526
        return false;
527
528
    const int itemId = item->getId();
529
    if (PlayerInfo::isItemProtected(itemId))
530
        return false;
531
    const Item *const tItem = mMyInventory->findItem(
532
        itemId, item->getColor());
533
534
    if ((tItem != nullptr) && (tItem->getQuantity() > 1
535
        || item->getQuantity() > 1))
536
    {
537
        if (localChatTab != nullptr)
538
        {
539
            // TRANSLATORS: trade error
540
            localChatTab->chatLog(_("Failed adding item. You can not "
541
                "overlap one kind of item on the window."),
542
                ChatMsgType::BY_SERVER,
543
                IgnoreRecord_false,
544
                TryRemoveColors_true);
545
        }
546
        return false;
547
    }
548
    if (Net::getNetworkType() != ServerType::TMWATHENA &&
549
        item->isEquipped() == Equipped_true)
550
    {
551
        if (localChatTab != nullptr)
552
        {
553
            localChatTab->chatLog(
554
                // TRANSLATORS: trade error
555
                _("Failed adding item. You can not trade equipped items."),
556
                ChatMsgType::BY_SERVER,
557
                IgnoreRecord_false,
558
                TryRemoveColors_true);
559
        }
560
        return false;
561
    }
562
    return true;
563
}
564
565
bool TradeWindow::isInpupFocused() const
566
{
567
    return mMoneyField != nullptr && mMoneyField->isFocused();
568

3
}