GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/itemcontainer.cpp Lines: 75 595 12.6 %
Date: 2021-03-17 Branches: 68 674 10.1 %

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/widgets/itemcontainer.h"
25
26
#include "dragdrop.h"
27
#include "settings.h"
28
29
#include "being/playerinfo.h"
30
31
#include "gui/gui.h"
32
#include "gui/skin.h"
33
#include "gui/viewport.h"
34
35
#include "gui/fonts/font.h"
36
37
#include "gui/shortcut/itemshortcut.h"
38
39
#include "gui/popups/itempopup.h"
40
41
#include "gui/windows/chatwindow.h"
42
#include "gui/windows/inventorywindow.h"
43
#include "gui/windows/shopwindow.h"
44
#include "gui/windows/shortcutwindow.h"
45
#include "gui/windows/itemamountwindow.h"
46
#include "gui/windows/maileditwindow.h"
47
#include "gui/windows/npcdialog.h"
48
49
#include "input/inputmanager.h"
50
51
#include "net/inventoryhandler.h"
52
#include "net/net.h"
53
#include "net/mail2handler.h"
54
#include "net/npchandler.h"
55
#include "net/tradehandler.h"
56
57
#include "utils/delete2.h"
58
#include "utils/foreach.h"
59
#include "utils/gettext.h"
60
#include "utils/stringutils.h"
61
62
#include "render/vertexes/imagecollection.h"
63
64
#include "resources/iteminfo.h"
65
66
#include <algorithm>
67
68
#include "debug.h"
69
70
namespace
71
{
72
    class ItemIdPair final
73
    {
74
        public:
75
            A_DELETE_COPY(ItemIdPair)
76
77
            ItemIdPair(const int id, Item *const item) :
78
                mId(id), mItem(item)
79
            {
80
            }
81
82
            int mId;
83
            Item* mItem;
84
    };
85
86
    class SortItemAlphaFunctor final
87
    {
88
        public:
89
            A_DEFAULT_COPY(SortItemAlphaFunctor)
90
91
            bool operator() (const ItemIdPair *const pair1,
92
                             const ItemIdPair *const pair2) const
93
            {
94
                const Item *const item1 = pair1->mItem;
95
                const Item *const item2 = pair2->mItem;
96
                if ((item1 == nullptr) || (item2 == nullptr))
97
                    return false;
98
99
                const std::string name1 = item1->getInfo().getName(
100
                    item1->getColor());
101
                const std::string name2 = item2->getInfo().getName(
102
                    item2->getColor());
103
                if (name1 == name2)
104
                {
105
                    return item1->getInvIndex() <
106
                        item2->getInvIndex();
107
                }
108
                return name1 < name2;
109
            }
110
    } itemAlphaInvSorter;
111
112
    class SortItemIdFunctor final
113
    {
114
        public:
115
            A_DEFAULT_COPY(SortItemIdFunctor)
116
117
            bool operator() (const ItemIdPair *const pair1,
118
                             const ItemIdPair *const pair2) const
119
            {
120
                if ((pair1->mItem == nullptr) || (pair2->mItem == nullptr))
121
                    return false;
122
123
                const int id1 = pair1->mItem->getId();
124
                const int id2 = pair2->mItem->getId();
125
                if (id1 == id2)
126
                {
127
                    return pair1->mItem->getInvIndex() <
128
                        pair2->mItem->getInvIndex();
129
                }
130
                return id1 < id2;
131
            }
132
    } itemIdInvSorter;
133
134
    class SortItemWeightFunctor final
135
    {
136
        public:
137
            A_DEFAULT_COPY(SortItemWeightFunctor)
138
139
            bool operator() (const ItemIdPair *const pair1,
140
                             const ItemIdPair *const pair2) const
141
            {
142
                if ((pair1->mItem == nullptr) || (pair2->mItem == nullptr))
143
                    return false;
144
145
                const int w1 = pair1->mItem->getInfo().getWeight();
146
                const int w2 = pair2->mItem->getInfo().getWeight();
147
                if (w1 == w2)
148
                {
149
                    const std::string name1 =
150
                        pair1->mItem->getInfo().getName();
151
                    const std::string name2 =
152
                        pair2->mItem->getInfo().getName();
153
                    if (name1 == name2)
154
                    {
155
                        return pair1->mItem->getInvIndex() <
156
                            pair2->mItem->getInvIndex();
157
                    }
158
                    return name1 < name2;
159
                }
160
                return w1 < w2;
161
            }
162
    } itemWeightInvSorter;
163
164
    class SortItemAmountFunctor final
165
    {
166
        public:
167
            A_DEFAULT_COPY(SortItemAmountFunctor)
168
169
            bool operator() (const ItemIdPair *const pair1,
170
                             const ItemIdPair *const pair2) const
171
            {
172
                if ((pair1->mItem == nullptr) || (pair2->mItem == nullptr))
173
                    return false;
174
175
                const int c1 = pair1->mItem->getQuantity();
176
                const int c2 = pair2->mItem->getQuantity();
177
                if (c1 == c2)
178
                {
179
                    const std::string name1 =
180
                        pair1->mItem->getInfo().getName();
181
                    const std::string name2 =
182
                        pair2->mItem->getInfo().getName();
183
                    if (name1 == name2)
184
                    {
185
                        return pair1->mItem->getInvIndex() <
186
                            pair2->mItem->getInvIndex();
187
                    }
188
                    return name1 < name2;
189
                }
190
                return c1 < c2;
191
            }
192
    } itemAmountInvSorter;
193
194
    class SortItemTypeFunctor final
195
    {
196
        public:
197
            A_DEFAULT_COPY(SortItemTypeFunctor)
198
199
            bool operator() (const ItemIdPair *const pair1,
200
                             const ItemIdPair *const pair2) const
201
            {
202
                if ((pair1->mItem == nullptr) || (pair2->mItem == nullptr))
203
                    return false;
204
205
                const ItemInfo &info1 = pair1->mItem->getInfo();
206
                const ItemInfo &info2 = pair2->mItem->getInfo();
207
                const ItemDbTypeT t1 = info1.getType();
208
                const ItemDbTypeT t2 = info2.getType();
209
                if (t1 == t2)
210
                {
211
                    const std::string &name1 = info1.getName();
212
                    const std::string &name2 = info2.getName();
213
                    if (name1 == name2)
214
                    {
215
                        return pair1->mItem->getInvIndex() <
216
                            pair2->mItem->getInvIndex();
217
                    }
218
                    return name1 < name2;
219
                }
220
                return t1 < t2;
221
            }
222
    } itemTypeInvSorter;
223
}  // namespace
224
225
6
ItemContainer::ItemContainer(const Widget2 *const widget,
226
                             Inventory *const inventory,
227
                             const int maxColumns,
228
                             const ShowEmptyRows showEmptyRows,
229
6
                             const ForceQuantity forceQuantity) :
230
    Widget(widget),
231
    KeyListener(),
232
    MouseListener(),
233
    WidgetListener(),
234
    mInventory(inventory),
235

42
    mSelImg(Theme::getImageFromThemeXml("item_selection.xml", "")),
236

24
    mProtectedImg(Theme::getImageFromTheme("lock.png")),
237

42
    mCellBackgroundImg(Theme::getImageFromThemeXml("inventory_cell.xml", "")),
238
    mName(),
239
    mShowMatrix(nullptr),
240




42
    mSkin(theme != nullptr ? theme->load("itemcontainer.xml", "",
241

12
        true, Theme::getThemePath()) : nullptr),
242

6
    mVertexes(new ImageCollection),
243
12
    mEquipedColor(getThemeColor(ThemeColorId::ITEM_EQUIPPED, 255U)),
244
12
    mEquipedColor2(getThemeColor(ThemeColorId::ITEM_EQUIPPED_OUTLINE, 255U)),
245
12
    mUnEquipedColor(getThemeColor(ThemeColorId::ITEM_NOT_EQUIPPED, 255U)),
246
    mUnEquipedColor2(getThemeColor(ThemeColorId::ITEM_NOT_EQUIPPED_OUTLINE,
247
12
        255U)),
248
    mSelectionListeners(),
249
    mGridColumns(1),
250
    mGridRows(1),
251
    mDrawRows(1),
252
    mSelectedIndex(-1),
253
    mLastUsedSlot(-1),
254
    mTag(0),
255
    mSortType(0),
256
    mClicks(1),
257


24
    mBoxWidth(mSkin != nullptr ? mSkin->getOption("boxWidth", 35) : 35),
258


24
    mBoxHeight(mSkin != nullptr ? mSkin->getOption("boxHeight", 43) : 43),
259


24
    mEquippedTextPadding(mSkin != nullptr ? mSkin->getOption(
260
                         "equippedTextPadding", 29) : 29),
261


24
    mPaddingItemX(mSkin != nullptr ? mSkin->getOption("paddingItemX", 0) : 0),
262


24
    mPaddingItemY(mSkin != nullptr ? mSkin->getOption("paddingItemY", 0) : 0),
263
    mMaxColumns(maxColumns),
264
    mSelectionStatus(SEL_NONE),
265
    mForceQuantity(forceQuantity),
266
    mShowEmptyRows(showEmptyRows),
267
132
    mDescItems(false)
268
{
269
6
    setFocusable(true);
270
6
    addKeyListener(this);
271
6
    addMouseListener(this);
272
6
    addWidgetListener(this);
273
6
    mAllowLogic = false;
274
6
}
275
276
48
ItemContainer::~ItemContainer()
277
{
278
6
    if (gui != nullptr)
279
6
        gui->removeDragged(this);
280
281
6
    if (mSelImg != nullptr)
282
    {
283
6
        mSelImg->decRef();
284
6
        mSelImg = nullptr;
285
    }
286
6
    if (mProtectedImg != nullptr)
287
    {
288
6
        mProtectedImg->decRef();
289
6
        mProtectedImg = nullptr;
290
    }
291
6
    if (mCellBackgroundImg != nullptr)
292
    {
293
        mCellBackgroundImg->decRef();
294
        mCellBackgroundImg = nullptr;
295
    }
296
297
6
    if (theme != nullptr)
298
6
        theme->unload(mSkin);
299
300
6
    delete []mShowMatrix;
301
6
    delete2(mVertexes)
302
12
}
303
304
void ItemContainer::logic()
305
{
306
    BLOCK_START("ItemContainer::logic")
307
    Widget::logic();
308
309
    if (mInventory == nullptr)
310
    {
311
        BLOCK_END("ItemContainer::logic")
312
        return;
313
    }
314
315
    const int lastUsedSlot = mInventory->getLastUsedSlot();
316
317
    if (lastUsedSlot != mLastUsedSlot)
318
    {
319
        mLastUsedSlot = lastUsedSlot;
320
        adjustHeight();
321
    }
322
    BLOCK_END("ItemContainer::logic")
323
}
324
325
2
void ItemContainer::draw(Graphics *const graphics)
326
{
327

2
    if ((mInventory == nullptr) || (mShowMatrix == nullptr))
328
        return;
329
330
    BLOCK_START("ItemContainer::draw")
331
    Font *const font = getFont();
332
333
    if (mCellBackgroundImg != nullptr)
334
    {
335
        if (mRedraw || graphics->getRedraw())
336
        {
337
            mRedraw = false;
338
            mVertexes->clear();
339
340
            const int invSize = mInventory->getSize();
341
            const int maxRows = mShowEmptyRows == ShowEmptyRows_true ?
342
                std::max(invSize / mGridColumns, mGridRows) : mGridRows;
343
            const int maxColumns = mGridColumns > invSize ?
344
                invSize : mGridColumns;
345
            for (int j = 0; j < maxRows; j++)
346
            {
347
                const int intY0 = j * mBoxHeight;
348
                for (int i = 0; i < maxColumns; i++)
349
                {
350
                    int itemX = i * mBoxWidth;
351
                    int itemY = intY0;
352
                    graphics->calcTileCollection(mVertexes,
353
                        mCellBackgroundImg,
354
                        itemX + mPaddingItemX,
355
                        itemY + mPaddingItemY);
356
                }
357
            }
358
            graphics->finalize(mVertexes);
359
        }
360
        graphics->drawTileCollection(mVertexes);
361
    }
362
363
    for (int j = 0; j < mDrawRows; j++)
364
    {
365
        const int intY0 = j * mBoxHeight;
366
        int itemIndex = j * mGridColumns - 1;
367
        for (int i = 0; i < mGridColumns; i++)
368
        {
369
            itemIndex ++;
370
            if (mShowMatrix[itemIndex] < 0)
371
                continue;
372
373
            const Item *const item = mInventory->getItem(
374
                mShowMatrix[itemIndex]);
375
376
            if ((item == nullptr) || item->getId() == 0)
377
                continue;
378
379
            Image *const image = item->getImage();
380
            if (image != nullptr)
381
            {
382
                int itemX = i * mBoxWidth;
383
                int itemY = intY0;
384
                if (mShowMatrix[itemIndex] == mSelectedIndex)
385
                {
386
                    if (mSelImg != nullptr)
387
                        graphics->drawImage(mSelImg, itemX, itemY);
388
                }
389
                image->setAlpha(1.0F);  // ensure the image if fully drawn...
390
                graphics->drawImage(image,
391
                    itemX + mPaddingItemX,
392
                    itemY + mPaddingItemY);
393
                if ((mProtectedImg != nullptr) && PlayerInfo::isItemProtected(
394
                    item->getId()))
395
                {
396
                    graphics->drawImage(mProtectedImg,
397
                        itemX + mPaddingItemX,
398
                        itemY + mPaddingItemY);
399
                }
400
            }
401
        }
402
    }
403
404
    for (int j = 0; j < mDrawRows; j++)
405
    {
406
        const int intY0 = j * mBoxHeight;
407
        int itemIndex = j * mGridColumns - 1;
408
        for (int i = 0; i < mGridColumns; i++)
409
        {
410
            int itemX = i * mBoxWidth;
411
            int itemY = intY0;
412
            itemIndex ++;
413
            if (mShowMatrix[itemIndex] < 0)
414
                continue;
415
416
            const Item *const item = mInventory->getItem(
417
                mShowMatrix[itemIndex]);
418
419
            if ((item == nullptr) || item->getId() == 0)
420
                continue;
421
422
            // Draw item caption
423
            std::string caption;
424
            if (item->getQuantity() > 1 ||
425
                mForceQuantity == ForceQuantity_true)
426
            {
427
                caption = toString(item->getQuantity());
428
            }
429
            else if (item->isEquipped() == Equipped_true)
430
            {
431
                // TRANSLATORS: Text under equipped items (should be small)
432
                caption = _("Eq.");
433
            }
434
435
            if (item->isEquipped() == Equipped_true)
436
            {
437
                font->drawString(graphics,
438
                    mEquipedColor, mEquipedColor2,
439
                    caption,
440
                    itemX + (mBoxWidth - font->getWidth(caption)) / 2,
441
                    itemY + mEquippedTextPadding);
442
            }
443
            else
444
            {
445
                font->drawString(graphics,
446
                    mUnEquipedColor, mUnEquipedColor2,
447
                    caption,
448
                    itemX + (mBoxWidth - font->getWidth(caption)) / 2,
449
                    itemY + mEquippedTextPadding);
450
            }
451
        }
452
    }
453
    BLOCK_END("ItemContainer::draw")
454
}
455
456
void ItemContainer::safeDraw(Graphics *const graphics)
457
{
458
    if ((mInventory == nullptr) || (mShowMatrix == nullptr))
459
        return;
460
461
    BLOCK_START("ItemContainer::draw")
462
    Font *const font = getFont();
463
464
    if (mCellBackgroundImg != nullptr)
465
    {
466
        const int invSize = mInventory->getSize();
467
        const int maxRows = mShowEmptyRows == ShowEmptyRows_true ?
468
            std::max(invSize / mGridColumns, mGridRows) : mGridRows;
469
        const int maxColumns = mGridColumns > invSize ?
470
            invSize : mGridColumns;
471
        for (int j = 0; j < maxRows; j++)
472
        {
473
            const int intY0 = j * mBoxHeight;
474
            for (int i = 0; i < maxColumns; i++)
475
            {
476
                int itemX = i * mBoxWidth;
477
                int itemY = intY0;
478
                graphics->drawImage(mCellBackgroundImg,
479
                    itemX + mPaddingItemX,
480
                    itemY + mPaddingItemY);
481
            }
482
        }
483
    }
484
485
    for (int j = 0; j < mDrawRows; j++)
486
    {
487
        const int intY0 = j * mBoxHeight;
488
        int itemIndex = j * mGridColumns - 1;
489
        for (int i = 0; i < mGridColumns; i++)
490
        {
491
            itemIndex ++;
492
            if (mShowMatrix[itemIndex] < 0)
493
                continue;
494
495
            const Item *const item = mInventory->getItem(
496
                mShowMatrix[itemIndex]);
497
498
            if ((item == nullptr) || item->getId() == 0)
499
                continue;
500
501
            Image *const image = item->getImage();
502
            if (image != nullptr)
503
            {
504
                int itemX = i * mBoxWidth;
505
                int itemY = intY0;
506
                if (mShowMatrix[itemIndex] == mSelectedIndex)
507
                {
508
                    if (mSelImg != nullptr)
509
                        graphics->drawImage(mSelImg, itemX, itemY);
510
                }
511
                image->setAlpha(1.0F);  // ensure the image if fully drawn...
512
                graphics->drawImage(image,
513
                    itemX + mPaddingItemX,
514
                    itemY + mPaddingItemY);
515
                if ((mProtectedImg != nullptr) && PlayerInfo::isItemProtected(
516
                    item->getId()))
517
                {
518
                    graphics->drawImage(mProtectedImg,
519
                        itemX + mPaddingItemX,
520
                        itemY + mPaddingItemY);
521
                }
522
            }
523
        }
524
    }
525
526
    for (int j = 0; j < mDrawRows; j++)
527
    {
528
        const int intY0 = j * mBoxHeight;
529
        int itemIndex = j * mGridColumns - 1;
530
        for (int i = 0; i < mGridColumns; i++)
531
        {
532
            int itemX = i * mBoxWidth;
533
            int itemY = intY0;
534
            itemIndex ++;
535
            if (mShowMatrix[itemIndex] < 0)
536
                continue;
537
538
            const Item *const item = mInventory->getItem(
539
                mShowMatrix[itemIndex]);
540
541
            if ((item == nullptr) || item->getId() == 0)
542
                continue;
543
544
            // Draw item caption
545
            std::string caption;
546
            if (item->getQuantity() > 1 ||
547
                mForceQuantity == ForceQuantity_true)
548
            {
549
                caption = toString(item->getQuantity());
550
            }
551
            else if (item->isEquipped() == Equipped_true)
552
            {
553
                // TRANSLATORS: Text under equipped items (should be small)
554
                caption = _("Eq.");
555
            }
556
557
            if (item->isEquipped() == Equipped_true)
558
            {
559
                font->drawString(graphics,
560
                    mEquipedColor, mEquipedColor2,
561
                    caption,
562
                    itemX + (mBoxWidth - font->getWidth(caption)) / 2,
563
                    itemY + mEquippedTextPadding);
564
            }
565
            else
566
            {
567
                font->drawString(graphics,
568
                    mUnEquipedColor, mUnEquipedColor2,
569
                    caption,
570
                    itemX + (mBoxWidth - font->getWidth(caption)) / 2,
571
                    itemY + mEquippedTextPadding);
572
            }
573
        }
574
    }
575
    BLOCK_END("ItemContainer::draw")
576
}
577
578
void ItemContainer::selectNone()
579
{
580
    dragDrop.clear();
581
582
    setSelectedIndex(-1);
583
    mSelectionStatus = SEL_NONE;
584
/*
585
    if (outfitWindow)
586
        outfitWindow->setItemSelected(-1);
587
    if (shopWindow)
588
        shopWindow->setItemSelected(-1);
589
*/
590
}
591
592
void ItemContainer::setSelectedIndex(const int newIndex)
593
{
594
    if (mSelectedIndex != newIndex)
595
    {
596
        mSelectedIndex = newIndex;
597
        distributeValueChangedEvent();
598
    }
599
}
600
601
1
Item *ItemContainer::getSelectedItem() const
602
{
603
1
    if (mInventory != nullptr)
604
1
        return mInventory->getItem(mSelectedIndex);
605
    return nullptr;
606
}
607
608
void ItemContainer::distributeValueChangedEvent()
609
{
610
    FOR_EACH (SelectionListenerIterator, i, mSelectionListeners)
611
    {
612
        if (*i != nullptr)
613
        {
614
            SelectionEvent event(this);
615
            (*i)->valueChanged(event);
616
        }
617
    }
618
}
619
620
void ItemContainer::keyPressed(KeyEvent &event A_UNUSED)
621
{
622
}
623
624
void ItemContainer::keyReleased(KeyEvent &event A_UNUSED)
625
{
626
}
627
628
void ItemContainer::mousePressed(MouseEvent &event)
629
{
630
    if (mInventory == nullptr)
631
        return;
632
633
    const MouseButtonT button = event.getButton();
634
    mClicks = event.getClickCount();
635
636
    if (button == MouseButton::LEFT || button == MouseButton::RIGHT)
637
    {
638
        event.consume();
639
        const int index = getSlotIndex(event.getX(), event.getY());
640
        if (index == Inventory::NO_SLOT_INDEX)
641
            return;
642
643
        Item *const item = mInventory->getItem(index);
644
645
        // put item name into chat window
646
        if ((item != nullptr) && mDescItems && (chatWindow != nullptr))
647
            chatWindow->addItemText(item->getInfo().getName());
648
649
        DragDropSourceT src = DragDropSource::Empty;
650
        switch (mInventory->getType())
651
        {
652
            case InventoryType::Inventory:
653
                src = DragDropSource::Inventory;
654
                break;
655
            case InventoryType::Storage:
656
                src = DragDropSource::Storage;
657
                break;
658
            case InventoryType::Trade:
659
                src = DragDropSource::Trade;
660
                break;
661
            case InventoryType::Npc:
662
                src = DragDropSource::Npc;
663
                break;
664
            case InventoryType::Cart:
665
                src = DragDropSource::Cart;
666
                break;
667
            case InventoryType::MailEdit:
668
                src = DragDropSource::MailEdit;
669
                break;
670
            case InventoryType::MailView:
671
                src = DragDropSource::MailView;
672
                break;
673
            case InventoryType::Craft:
674
                src = DragDropSource::Craft;
675
                break;
676
            default:
677
            case InventoryType::Vending:
678
            case InventoryType::TypeEnd:
679
                break;
680
        }
681
        if (src == DragDropSource::MailView)
682
            return;
683
        if (mSelectedIndex == index && mClicks != 2)
684
        {
685
            dragDrop.dragItem(item, src, index);
686
            dragDrop.select(item);
687
            mSelectionStatus = SEL_DESELECTING;
688
        }
689
        else if ((item != nullptr) && (item->getId() != 0))
690
        {
691
            if (index >= 0)
692
            {
693
                dragDrop.dragItem(item, src, index);
694
                dragDrop.select(item);
695
                setSelectedIndex(index);
696
                mSelectionStatus = SEL_SELECTING;
697
698
                if (itemShortcutWindow != nullptr)
699
                {
700
                    const int num = itemShortcutWindow->getTabIndex();
701
                    if (num >= 0 && num < CAST_S32(SHORTCUT_TABS))
702
                    {
703
                        if (itemShortcut[num] != nullptr)
704
                            itemShortcut[num]->setItemSelected(item);
705
                    }
706
                }
707
                if (shopWindow != nullptr)
708
                    shopWindow->setItemSelected(item->getId());
709
            }
710
        }
711
        else
712
        {
713
            dragDrop.deselect();
714
            selectNone();
715
        }
716
    }
717
}
718
719
void ItemContainer::mouseDragged(MouseEvent &event A_UNUSED)
720
{
721
    if (mSelectionStatus != SEL_NONE)
722
        mSelectionStatus = SEL_DRAGGING;
723
}
724
725
void ItemContainer::mouseReleased(MouseEvent &event)
726
{
727
    if (mClicks == 2 ||
728
        inventoryHandler == nullptr ||
729
        tradeHandler == nullptr)
730
    {
731
        return;
732
    }
733
734
    switch (mSelectionStatus)
735
    {
736
        case SEL_SELECTING:
737
            mSelectionStatus = SEL_SELECTED;
738
            break;
739
        case SEL_DESELECTING:
740
            selectNone();
741
            break;
742
        case SEL_DRAGGING:
743
            mSelectionStatus = SEL_SELECTED;
744
            break;
745
        case SEL_NONE:
746
        case SEL_SELECTED:
747
        default:
748
            break;
749
    }
750
751
    if (dragDrop.isEmpty())
752
    {
753
        const int index = getSlotIndex(event.getX(), event.getY());
754
        if (index == Inventory::NO_SLOT_INDEX)
755
            return;
756
        if (index == mSelectedIndex || mSelectedIndex == -1)
757
            return;
758
        inventoryHandler->moveItem(mSelectedIndex, index);
759
        selectNone();
760
    }
761
    else if (mInventory != nullptr)
762
    {
763
        const DragDropSourceT src = dragDrop.getSource();
764
        DragDropSourceT dst = DragDropSource::Empty;
765
        switch (mInventory->getType())
766
        {
767
            case InventoryType::Inventory:
768
                dst = DragDropSource::Inventory;
769
                break;
770
            case InventoryType::Storage:
771
                dst = DragDropSource::Storage;
772
                break;
773
            case InventoryType::Trade:
774
                dst = DragDropSource::Trade;
775
                break;
776
            case InventoryType::Npc:
777
                dst = DragDropSource::Npc;
778
                break;
779
            case InventoryType::MailEdit:
780
                dst = DragDropSource::MailEdit;
781
                break;
782
            case InventoryType::MailView:
783
                dst = DragDropSource::MailView;
784
                break;
785
            case InventoryType::Cart:
786
                dst = DragDropSource::Cart;
787
                break;
788
            case InventoryType::Craft:
789
                dst = DragDropSource::Craft;
790
                break;
791
            default:
792
            case InventoryType::Vending:
793
            case InventoryType::TypeEnd:
794
                break;
795
        }
796
        InventoryTypeT srcContainer = InventoryType::TypeEnd;
797
        InventoryTypeT dstContainer = InventoryType::TypeEnd;
798
        bool checkProtection(false);
799
        Inventory *inventory = nullptr;
800
        if (src == DragDropSource::Inventory
801
            && dst == DragDropSource::Storage)
802
        {
803
            srcContainer = InventoryType::Inventory;
804
            dstContainer = InventoryType::Storage;
805
            inventory = PlayerInfo::getInventory();
806
        }
807
        else if (src == DragDropSource::Storage
808
                 && dst == DragDropSource::Inventory)
809
        {
810
            srcContainer = InventoryType::Storage;
811
            dstContainer = InventoryType::Inventory;
812
            inventory = PlayerInfo::getStorageInventory();
813
        }
814
        if (src == DragDropSource::Inventory
815
            && dst == DragDropSource::Cart)
816
        {
817
            srcContainer = InventoryType::Inventory;
818
            dstContainer = InventoryType::Cart;
819
            inventory = PlayerInfo::getInventory();
820
        }
821
        if (src == DragDropSource::Inventory
822
            && dst == DragDropSource::Inventory)
823
        {
824
            if (Net::getNetworkType() == ServerType::TMWATHENA)
825
                return;
826
            const int index = getSlotIndex(event.getX(), event.getY());
827
            if (index == Inventory::NO_SLOT_INDEX)
828
                return;
829
            if (index == mSelectedIndex || mSelectedIndex == -1)
830
                return;
831
            if (inventoryWindow != nullptr)
832
                inventoryWindow->combineItems(index, mSelectedIndex);
833
            return;
834
        }
835
        else if (src == DragDropSource::Cart
836
                 && dst == DragDropSource::Inventory)
837
        {
838
            srcContainer = InventoryType::Cart;
839
            dstContainer = InventoryType::Inventory;
840
            inventory = PlayerInfo::getCartInventory();
841
        }
842
        else if (src == DragDropSource::Cart
843
                 && dst == DragDropSource::Storage)
844
        {
845
            srcContainer = InventoryType::Cart;
846
            dstContainer = InventoryType::Storage;
847
            inventory = PlayerInfo::getCartInventory();
848
        }
849
        else if (src == DragDropSource::Storage
850
                 && dst == DragDropSource::Cart)
851
        {
852
            srcContainer = InventoryType::Storage;
853
            dstContainer = InventoryType::Cart;
854
            inventory = PlayerInfo::getStorageInventory();
855
        }
856
        if (src == DragDropSource::Inventory
857
            && dst == DragDropSource::Trade)
858
        {
859
            checkProtection = true;
860
            inventory = PlayerInfo::getInventory();
861
        }
862
        else if (src == DragDropSource::Inventory &&
863
                 dst == DragDropSource::Npc)
864
        {
865
            inventory = PlayerInfo::getInventory();
866
            if (inventory != nullptr)
867
            {
868
                Item *const item = inventory->getItem(dragDrop.getTag());
869
                if (mInventory->addVirtualItem(
870
                    item,
871
                    getSlotByXY(event.getX(), event.getY()),
872
                    1))
873
                {
874
                    inventory->virtualRemove(item, 1);
875
                }
876
            }
877
            return;
878
        }
879
        else if (src == DragDropSource::Inventory &&
880
                 dst == DragDropSource::MailEdit)
881
        {
882
            inventory = PlayerInfo::getInventory();
883
            if (inventory == nullptr)
884
                return;
885
            Item *const item = inventory->getItem(dragDrop.getTag());
886
            if (item == nullptr)
887
                return;
888
            if (settings.enableNewMailSystem)
889
            {
890
                if (item->getQuantity() > 1
891
                    && !inputManager.isActionActive(InputAction::STOP_ATTACK))
892
                {
893
                    ItemAmountWindow::showWindow(
894
                        ItemAmountWindowUsage::MailAdd,
895
                        mailEditWindow,
896
                        item,
897
                        0,
898
                        0);
899
                }
900
                else
901
                {
902
                    mail2Handler->addItem(item, 1);
903
                }
904
            }
905
            else
906
            {
907
                if (mInventory->addVirtualItem(
908
                    item,
909
                    getSlotByXY(event.getX(), event.getY()),
910
                    1))
911
                {
912
                    inventory->virtualRemove(item, 1);
913
                }
914
            }
915
            return;
916
        }
917
        else if (src == DragDropSource::Npc)
918
        {
919
            inventory = PlayerInfo::getInventory();
920
            if (dst == DragDropSource::Npc)
921
            {
922
                const Item *const item = mInventory->getItem(
923
                    dragDrop.getTag());
924
                const int index = getSlotByXY(event.getX(), event.getY());
925
                if (index == Inventory::NO_SLOT_INDEX)
926
                {
927
                    if (inventory != nullptr)
928
                        inventory->virtualRestore(item, 1);
929
                    mInventory->removeItemAt(dragDrop.getTag());
930
                    return;
931
                }
932
                mInventory->removeItemAt(index);
933
                mInventory->setItem(index,
934
                    item->getId(),
935
                    item->getType(),
936
                    1,
937
                    1,
938
                    item->getColor(),
939
                    item->getIdentified(),
940
                    item->getDamaged(),
941
                    item->getFavorite(),
942
                    Equipm_false,
943
                    Equipped_false);
944
                Item *const item2 = mInventory->getItem(index);
945
                if (item2 != nullptr)
946
                    item2->setTag(item->getTag());
947
                mInventory->removeItemAt(dragDrop.getTag());
948
            }
949
            else
950
            {
951
                if (inventory != nullptr)
952
                {
953
                    const Item *const item = inventory->getItem(
954
                        dragDrop.getTag());
955
                    if (item != nullptr)
956
                    {
957
                        inventory->virtualRestore(item, 1);
958
                        mInventory->removeItemAt(dragDrop.getTag());
959
                    }
960
                }
961
                return;
962
            }
963
        }
964
        else if (src == DragDropSource::Inventory &&
965
                 dst == DragDropSource::Craft)
966
        {
967
            inventory = PlayerInfo::getInventory();
968
            if (inventory != nullptr)
969
            {
970
                Item *const item = inventory->getItem(dragDrop.getTag());
971
                if ((item == nullptr) || item->isEquipped() == Equipped_true)
972
                    return;
973
                const int slot = getSlotByXY(event.getX(), event.getY());
974
                if (item->getQuantity() > 1
975
                    && !inputManager.isActionActive(InputAction::STOP_ATTACK))
976
                {
977
                    ItemAmountWindow::showWindow(
978
                        ItemAmountWindowUsage::CraftAdd,
979
                        npcHandler->getCurrentNpcDialog(),
980
                        item,
981
                        0,
982
                        slot);
983
                }
984
                else
985
                {
986
                    if (mInventory->addVirtualItem(
987
                        item,
988
                        slot,
989
                        1))
990
                    {
991
                        inventory->virtualRemove(item, 1);
992
                    }
993
                }
994
            }
995
            return;
996
        }
997
        else if (src == DragDropSource::Craft)
998
        {
999
            inventory = PlayerInfo::getInventory();
1000
            if (dst == DragDropSource::Craft)
1001
            {
1002
                const Item *const item = mInventory->getItem(
1003
                    dragDrop.getTag());
1004
                const int index = getSlotByXY(event.getX(), event.getY());
1005
                if (index == Inventory::NO_SLOT_INDEX)
1006
                {
1007
                    if (inventory != nullptr)
1008
                    {
1009
                        inventory->virtualRestore(item,
1010
                            item->getQuantity());
1011
                        mInventory->removeItemAt(dragDrop.getTag());
1012
                    }
1013
                    return;
1014
                }
1015
                mInventory->moveItem(index, dragDrop.getTag());
1016
            }
1017
            else
1018
            {
1019
                if (inventory != nullptr)
1020
                {
1021
                    const Item *const item = inventory->getItem(
1022
                        dragDrop.getTag());
1023
                    if (item != nullptr)
1024
                    {
1025
                        inventory->virtualRestore(item,
1026
                            item->getQuantity());
1027
                        mInventory->removeItemAt(dragDrop.getTag());
1028
                    }
1029
                }
1030
                return;
1031
            }
1032
        }
1033
        else if (src == DragDropSource::MailEdit)
1034
        {
1035
            if (event.getType() == MouseEventType::RELEASED2)
1036
                return;
1037
            if (settings.enableNewMailSystem)
1038
            {
1039
                if (mailEditWindow == nullptr)
1040
                    return;
1041
                inventory = mailEditWindow->getInventory();
1042
                if (inventory == nullptr)
1043
                    return;
1044
                const Item *const item = inventory->getItem(dragDrop.getTag());
1045
                if (item == nullptr)
1046
                    return;
1047
                mail2Handler->removeItem(item->getTag(),
1048
                    item->getQuantity());
1049
            }
1050
            else
1051
            {
1052
                inventory = PlayerInfo::getInventory();
1053
                if (inventory == nullptr)
1054
                    return;
1055
                const Item *const item = inventory->getItem(dragDrop.getTag());
1056
                if (item == nullptr)
1057
                    return;
1058
                mInventory->removeItemAt(dragDrop.getTag());
1059
            }
1060
            return;
1061
        }
1062
1063
        if (inventory != nullptr)
1064
        {
1065
            const Item *const item = inventory->getItem(dragDrop.getTag());
1066
            if (item != nullptr)
1067
            {
1068
                if (srcContainer != InventoryType::TypeEnd)
1069
                {   // inventory <--> storage, cart
1070
                    inventoryHandler->moveItem2(srcContainer,
1071
                        item->getInvIndex(),
1072
                        item->getQuantity(),
1073
                        dstContainer);
1074
                }
1075
                else
1076
                {   // inventory --> trade
1077
                    if (!checkProtection || !PlayerInfo::isItemProtected(
1078
                        item->getId()))
1079
                    {
1080
                        tradeHandler->addItem(item, item->getQuantity());
1081
                    }
1082
                }
1083
            }
1084
        }
1085
    }
1086
}
1087
1088
void ItemContainer::mouseMoved(MouseEvent &event)
1089
{
1090
    if (mInventory == nullptr)
1091
        return;
1092
1093
    const Item *const item = mInventory->getItem(
1094
        getSlotIndex(event.getX(), event.getY()));
1095
1096
    if ((item != nullptr) && (viewport != nullptr))
1097
    {
1098
        itemPopup->setItem(item, false);
1099
        itemPopup->position(viewport->mMouseX, viewport->mMouseY);
1100
    }
1101
    else
1102
    {
1103
        itemPopup->setVisible(Visible_false);
1104
    }
1105
}
1106
1107
void ItemContainer::mouseExited(MouseEvent &event A_UNUSED)
1108
{
1109
    itemPopup->setVisible(Visible_false);
1110
}
1111
1112
void ItemContainer::widgetResized(const Event &event A_UNUSED)
1113
{
1114
    updateSize();
1115
}
1116
1117
void ItemContainer::updateSize()
1118
{
1119
    mGridColumns = std::min(mMaxColumns,
1120
        std::max(1, mDimension.width / mBoxWidth));
1121
    if (mGridColumns > mMaxColumns)
1122
        mGridColumns = mMaxColumns;
1123
    adjustHeight();
1124
    mRedraw = true;
1125
}
1126
1127
void ItemContainer::widgetMoved(const Event &event A_UNUSED)
1128
{
1129
    mRedraw = true;
1130
}
1131
1132
void ItemContainer::adjustHeight()
1133
{
1134
    if (mGridColumns == 0)
1135
        return;
1136
1137
    mGridRows = (mLastUsedSlot + 1) / mGridColumns;
1138
    if (mGridRows == 0 || (mLastUsedSlot + 1) % mGridColumns > 0)
1139
        ++mGridRows;
1140
1141
    const int invSize = mInventory->getSize();
1142
    int maxRows = mShowEmptyRows == ShowEmptyRows_true ?
1143
        std::max(invSize / mGridColumns, mGridRows) : mGridRows;
1144
1145
    if (mShowEmptyRows == ShowEmptyRows_true)
1146
    {
1147
        if (mGridColumns * maxRows < invSize)
1148
            maxRows ++;
1149
        mGridRows = maxRows;
1150
    }
1151
1152
    if (mShowEmptyRows == ShowEmptyRows_false)
1153
    {
1154
        const int num = updateMatrix();
1155
        mDrawRows = num / mGridColumns;
1156
        if (mDrawRows == 0 || num % mGridColumns > 0)
1157
            ++mDrawRows;
1158
1159
        maxRows = mDrawRows;
1160
    }
1161
    else
1162
    {
1163
        mDrawRows = mGridRows;
1164
    }
1165
    setHeight(maxRows * mBoxHeight);
1166
}
1167
1168
3
int ItemContainer::updateMatrix()
1169
{
1170
3
    if (mInventory == nullptr)
1171
        return 0;
1172
1173
3
    mRedraw = true;
1174
3
    delete []mShowMatrix;
1175
3
    mShowMatrix = new int[CAST_SIZE(mGridRows * mGridColumns)];
1176
1177
3
    STD_VECTOR<ItemIdPair*> sortedItems;
1178
3
    int i = 0;
1179
3
    int j = 0;
1180
1181
9
    std::string temp = mName;
1182
3
    toLower(temp);
1183
1184
6
    const unsigned int invSize = mInventory->getSize();
1185
303
    for (unsigned int idx = 0; idx < invSize; idx ++)
1186
    {
1187
300
        Item *const item = mInventory->getItem(idx);
1188
1189

300
        if (item == nullptr ||
1190
            item->getId() == 0 ||
1191

300
            !item->isHaveTag(mTag) ||
1192
            item->getQuantity() == 0)
1193
        {
1194
300
            if (mShowEmptyRows == ShowEmptyRows_true)
1195
                sortedItems.push_back(new ItemIdPair(idx, nullptr));
1196
300
            continue;
1197
        }
1198
1199
        if (!item->isHaveTag(mTag))
1200
            continue;
1201
1202
        if (mName.empty())
1203
        {
1204
            sortedItems.push_back(new ItemIdPair(idx, item));
1205
            continue;
1206
        }
1207
        std::string name = item->getInfo().getName();
1208
        toLower(name);
1209
        if (name.find(temp) != std::string::npos)
1210
            sortedItems.push_back(new ItemIdPair(idx, item));
1211
    }
1212
1213

3
    switch (mSortType)
1214
    {
1215
        case 0:
1216
        default:
1217
            break;
1218
        case 1:
1219
            std::sort(sortedItems.begin(), sortedItems.end(),
1220
                itemAlphaInvSorter);
1221
            break;
1222
        case 2:
1223
            std::sort(sortedItems.begin(), sortedItems.end(), itemIdInvSorter);
1224
            break;
1225
        case 3:
1226
            std::sort(sortedItems.begin(), sortedItems.end(),
1227
                itemWeightInvSorter);
1228
            break;
1229
        case 4:
1230
            std::sort(sortedItems.begin(), sortedItems.end(),
1231
                itemAmountInvSorter);
1232
            break;
1233
        case 5:
1234
            std::sort(sortedItems.begin(), sortedItems.end(),
1235
                itemTypeInvSorter);
1236
            break;
1237
    }
1238
1239
3
    int jMult = j * mGridColumns;
1240
3
    const int maxSize = mGridRows * mGridColumns;
1241
15
    FOR_EACH (STD_VECTOR<ItemIdPair*>::const_iterator, iter, sortedItems)
1242
    {
1243
        if (jMult >= maxSize)
1244
            break;
1245
1246
        mShowMatrix[jMult + i] = (*iter)->mId;
1247
1248
        i ++;
1249
        if (i >= mGridColumns)
1250
        {
1251
            i = 0;
1252
            j ++;
1253
            jMult += mGridColumns;
1254
        }
1255
    }
1256
1257
6
    for (int idx = j * mGridColumns + i; idx < maxSize; idx ++)
1258
3
        mShowMatrix[idx] = -1;
1259
1260
3
    const int num = CAST_S32(sortedItems.size());
1261
3
    for (size_t idx = 0, sz = num; idx < sz; idx ++)
1262
        delete sortedItems[idx];
1263
3
    return num;
1264
}
1265
1266
int ItemContainer::getSlotIndex(int x, int y) const
1267
{
1268
    if (mShowMatrix == nullptr)
1269
        return Inventory::NO_SLOT_INDEX;
1270
1271
    if (x < mDimension.width && y < mDimension.height && x >= 0 && y >= 0)
1272
    {
1273
        if (x > mBoxWidth * mGridColumns)
1274
            return Inventory::NO_SLOT_INDEX;
1275
        const int idx = (y / mBoxHeight) * mGridColumns + (x / mBoxWidth);
1276
        if (idx >= 0 && idx < mGridRows * mGridColumns
1277
            && mShowMatrix[idx] >= 0)
1278
        {
1279
            return mShowMatrix[idx];
1280
        }
1281
    }
1282
1283
    return Inventory::NO_SLOT_INDEX;
1284
}
1285
1286
int ItemContainer::getSlotByXY(int x, int y) const
1287
{
1288
    if (mShowMatrix == nullptr)
1289
        return Inventory::NO_SLOT_INDEX;
1290
1291
    if (x < mDimension.width && y < mDimension.height && x >= 0 && y >= 0)
1292
    {
1293
        if (x > mBoxWidth * mGridColumns)
1294
            return Inventory::NO_SLOT_INDEX;
1295
        const int idx = (y / mBoxHeight) * mGridColumns + (x / mBoxWidth);
1296
        if (idx >= 0 && idx < mGridRows * mGridColumns)
1297
            return idx;
1298
    }
1299
1300
    return Inventory::NO_SLOT_INDEX;
1301
}
1302
1303
void ItemContainer::setFilter(const int tag)
1304
{
1305
    mTag = tag;
1306
    adjustHeight();
1307
}
1308
1309
2
void ItemContainer::setSortType(const int sortType)
1310
{
1311
2
    mSortType = sortType;
1312
2
    updateMatrix();
1313
2
}
1314
1315
void ItemContainer::setCellBackgroundImage(const std::string &xmlName)
1316
{
1317
    if (mCellBackgroundImg != nullptr)
1318
        mCellBackgroundImg->decRef();
1319
    mCellBackgroundImg = Theme::getImageFromThemeXml(xmlName, "");
1320
    mRedraw = true;
1321
}
1322
1323
void ItemContainer::setMaxColumns(const int maxColumns)
1324
{
1325
    mMaxColumns = maxColumns;
1326
    updateSize();
1327
2
}