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/inventorywindow.h"
25 
26 #include "configuration.h"
27 
28 #include "being/playerinfo.h"
29 
30 #include "const/sound.h"
31 
32 #include "enums/gui/layouttype.h"
33 
34 #include "input/inputmanager.h"
35 
36 #include "gui/gui.h"
37 
38 #include "gui/fonts/font.h"
39 
40 #include "gui/models/sortlistmodelinv.h"
41 
42 #include "gui/popups/itempopup.h"
43 #include "gui/popups/popupmenu.h"
44 #include "gui/popups/textpopup.h"
45 
46 #include "gui/windows/confirmdialog.h"
47 #include "gui/windows/itemamountwindow.h"
48 #include "gui/windows/npcdialog.h"
49 #include "gui/windows/setupwindow.h"
50 #include "gui/windows/tradewindow.h"
51 
52 #include "gui/widgets/button.h"
53 #include "gui/widgets/createwidget.h"
54 #include "gui/widgets/containerplacer.h"
55 #include "gui/widgets/dropdown.h"
56 #include "gui/widgets/itemcontainer.h"
57 #include "gui/widgets/layout.h"
58 #include "gui/widgets/progressbar.h"
59 #include "gui/widgets/scrollarea.h"
60 #include "gui/widgets/tabstrip.h"
61 #include "gui/widgets/textfield.h"
62 #include "gui/widgets/windowcontainer.h"
63 
64 #include "listeners/insertcardlistener.h"
65 
66 #include "net/npchandler.h"
67 
68 #include "resources/iteminfo.h"
69 
70 #include "resources/db/unitsdb.h"
71 
72 #include "resources/item/item.h"
73 
74 #include "utils/delete2.h"
75 #include "utils/foreach.h"
76 
77 #include "debug.h"
78 
79 InventoryWindow *inventoryWindow = nullptr;
80 InventoryWindow *storageWindow = nullptr;
81 InventoryWindow *cartWindow = nullptr;
82 InventoryWindow::WindowList InventoryWindow::invInstances;
83 InsertCardListener insertCardListener;
84 
InventoryWindow(Inventory * const inventory)85 InventoryWindow::InventoryWindow(Inventory *const inventory) :
86     Window("Inventory", Modal_false, nullptr, "inventory.xml"),
87     ActionListener(),
88     KeyListener(),
89     SelectionListener(),
90     InventoryListener(),
91     AttributeListener(),
92     mInventory(inventory),
93     mItems(new ItemContainer(this, mInventory, 100000,
94         ShowEmptyRows_false, ForceQuantity_false)),
95     mUseButton(nullptr),
96     mDropButton(nullptr),
97     mOutfitButton(nullptr),
98     mShopButton(nullptr),
99     mCartButton(nullptr),
100     mEquipmentButton(nullptr),
101     mStoreButton(nullptr),
102     mRetrieveButton(nullptr),
103     mInvCloseButton(nullptr),
104     mWeightBar(nullptr),
105     mSlotsBar(new ProgressBar(this, 0.0F, 100, 0,
106         ProgressColorId::PROG_INVY_SLOTS,
107         "slotsprogressbar.xml", "slotsprogressbar_fill.xml")),
108     mFilter(nullptr),
109     mSortModel(new SortListModelInv),
110     mSortDropDown(new DropDown(this, mSortModel, false,
111         Modal_false, this, "sort")),
112     mNameFilter(new TextField(this, "", LoseFocusOnTab_true,
113         this, "namefilter", true)),
114     mSortDropDownCell(nullptr),
115     mNameFilterCell(nullptr),
116     mFilterCell(nullptr),
117     mSlotsBarCell(nullptr),
118     mSplit(false),
119     mCompactMode(false)
120 {
121     mSlotsBar->setColor(getThemeColor(ThemeColorId::SLOTS_BAR, 255U),
122         getThemeColor(ThemeColorId::SLOTS_BAR_OUTLINE, 255U));
123 
124     if (inventory != nullptr)
125     {
126         setCaption(gettext(inventory->getName().c_str()));
127         setWindowName(inventory->getName());
128         switch (inventory->getType())
129         {
130             case InventoryType::Inventory:
131             case InventoryType::Trade:
132             case InventoryType::Npc:
133             case InventoryType::Vending:
134             case InventoryType::MailEdit:
135             case InventoryType::MailView:
136             case InventoryType::Craft:
137             case InventoryType::TypeEnd:
138             default:
139                 mSortDropDown->setSelected(config.getIntValue(
140                     "inventorySortOrder"));
141                 break;
142             case InventoryType::Storage:
143                 mSortDropDown->setSelected(config.getIntValue(
144                     "storageSortOrder"));
145                 break;
146             case InventoryType::Cart:
147                 mSortDropDown->setSelected(config.getIntValue(
148                     "cartSortOrder"));
149                 break;
150         }
151     }
152     else
153     {
154         // TRANSLATORS: inventory window name
155         setCaption(_("Inventory"));
156         setWindowName("Inventory");
157         mSortDropDown->setSelected(0);
158     }
159 
160     if ((setupWindow != nullptr) &&
161         (inventory != nullptr) &&
162         inventory->getType() != InventoryType::Storage)
163     {
164         setupWindow->registerWindowForReset(this);
165     }
166 
167     setResizable(true);
168     setCloseButton(true);
169     setSaveVisible(true);
170     setStickyButtonLock(true);
171 
172     if (mainGraphics->mWidth > 600)
173         setDefaultSize(450, 310, ImagePosition::CENTER, 0, 0);
174     else
175         setDefaultSize(387, 307, ImagePosition::CENTER, 0, 0);
176     setMinWidth(310);
177     setMinHeight(179);
178     addKeyListener(this);
179 
180     mItems->addSelectionListener(this);
181 
182     const int size = config.getIntValue("fontSize");
183     mFilter = new TabStrip(this, "filter_" + getWindowName(), size + 16, 0);
184     mFilter->addActionListener(this);
185     mFilter->setActionEventId("tag_");
186     mFilter->setSelectable(false);
187 
188     StringVect tags = ItemDB::getTags();
189     const size_t sz = tags.size();
190     for (size_t f = 0; f < sz; f ++)
191         mFilter->addButton(tags[f], tags[f], false);
192 
193     if (mInventory == nullptr)
194     {
195         invInstances.push_back(this);
196         return;
197     }
198 
199     ScrollArea *const invenScroll = new ScrollArea(this, mItems,
200         fromBool(getOptionBool("showbackground", false), Opaque),
201         "inventory_background.xml");
202     invenScroll->setHorizontalScrollPolicy(ScrollArea::SHOW_NEVER);
203 
204     switch (mInventory->getType())
205     {
206         case InventoryType::Inventory:
207         {
208             // TRANSLATORS: inventory button
209             const std::string equip = _("Equip");
210             // TRANSLATORS: inventory button
211             const std::string use = _("Use");
212             // TRANSLATORS: inventory button
213             const std::string unequip = _("Unequip");
214 
215             std::string longestUseString = getFont()->getWidth(equip) >
216                 getFont()->getWidth(use) ? equip : use;
217 
218             if (getFont()->getWidth(longestUseString) <
219                 getFont()->getWidth(unequip))
220             {
221                 longestUseString = unequip;
222             }
223 
224             mUseButton = new Button(this,
225                 longestUseString,
226                 "use",
227                 BUTTON_SKIN,
228                 this);
229             mDropButton = new Button(this,
230                 // TRANSLATORS: inventory button
231                 _("Drop..."),
232                 "drop",
233                 BUTTON_SKIN,
234                 this);
235             mOutfitButton = new Button(this,
236                 // TRANSLATORS: inventory outfits button
237                 _("O"),
238                 "outfit",
239                 BUTTON_SKIN,
240                 this);
241             mCartButton = new Button(this,
242                 // TRANSLATORS: inventory cart button
243                 _("C"),
244                 "cart",
245                 BUTTON_SKIN,
246                 this);
247             mShopButton = new Button(this,
248                 // TRANSLATORS: inventory shop button
249                 _("S"),
250                 "shop",
251                 BUTTON_SKIN,
252                 this);
253             mEquipmentButton = new Button(this,
254                 // TRANSLATORS: inventory equipment button
255                 _("E"),
256                 "equipment",
257                 BUTTON_SKIN,
258                 this);
259             mWeightBar = new ProgressBar(this, 0.0F, 100, 0,
260                 ProgressColorId::PROG_WEIGHT,
261                 "weightprogressbar.xml", "weightprogressbar_fill.xml");
262             mWeightBar->setColor(getThemeColor(ThemeColorId::WEIGHT_BAR, 255U),
263                 getThemeColor(ThemeColorId::WEIGHT_BAR_OUTLINE, 255U));
264 
265             // TRANSLATORS: outfits button tooltip
266             mOutfitButton->setDescription(_("Outfits"));
267             // TRANSLATORS: cart button tooltip
268             mCartButton->setDescription(_("Cart"));
269             // TRANSLATORS: shop button tooltip
270             mShopButton->setDescription(_("Shop"));
271             // TRANSLATORS: equipment button tooltip
272             mEquipmentButton->setDescription(_("Equipment"));
273 
274             place(0, 0, mWeightBar, 4, 1);
275             mSlotsBarCell = &place(4, 0, mSlotsBar, 4, 1);
276             mSortDropDownCell = &place(8, 0, mSortDropDown, 3, 1);
277 
278             mFilterCell = &place(0, 1, mFilter, 10, 1).setPadding(3);
279             mNameFilterCell = &place(8, 1, mNameFilter, 3, 1);
280 
281             place(0, 2, invenScroll, 11, 1).setPadding(3);
282             place(0, 3, mUseButton, 1, 1);
283             place(1, 3, mDropButton, 1, 1);
284             ContainerPlacer placer = getPlacer(10, 3);
285             placer(0, 0, mShopButton, 1, 1);
286             placer(1, 0, mOutfitButton, 1, 1);
287             placer(2, 0, mCartButton, 1, 1);
288             placer(3, 0, mEquipmentButton, 1, 1);
289 
290             updateWeight();
291             break;
292         }
293 
294         case InventoryType::Storage:
295         {
296             mStoreButton = new Button(this,
297                 // TRANSLATORS: storage button
298                 _("Store"),
299                 "store",
300                 BUTTON_SKIN,
301                 this);
302             mRetrieveButton = new Button(this,
303                 // TRANSLATORS: storage button
304                 _("Retrieve"),
305                 "retrieve",
306                 BUTTON_SKIN,
307                 this);
308             mInvCloseButton = new Button(this,
309                 // TRANSLATORS: storage button
310                 _("Close"),
311                 "close",
312                 BUTTON_SKIN,
313                 this);
314 
315             mSlotsBarCell = &place(0, 0, mSlotsBar, 6, 1);
316             mSortDropDownCell = &place(6, 0, mSortDropDown, 1, 1);
317 
318             mFilterCell = &place(0, 1, mFilter, 7, 1).setPadding(3);
319             mNameFilterCell = &place(6, 1, mNameFilter, 1, 1);
320 
321             place(0, 2, invenScroll, 7, 4);
322             place(0, 6, mStoreButton, 1, 1);
323             place(1, 6, mRetrieveButton, 1, 1);
324             place(6, 6, mInvCloseButton, 1, 1);
325             break;
326         }
327 
328         case InventoryType::Cart:
329         {
330             mStoreButton = new Button(this,
331                 // TRANSLATORS: storage button
332                 _("Store"),
333                 "store",
334                 BUTTON_SKIN,
335                 this);
336             mRetrieveButton = new Button(this,
337                 // TRANSLATORS: storage button
338                 _("Retrieve"),
339                 "retrieve",
340                 BUTTON_SKIN,
341                 this);
342             mInvCloseButton = new Button(this,
343                 // TRANSLATORS: storage button
344                 _("Close"),
345                 "close",
346                 BUTTON_SKIN,
347                 this);
348 
349             mWeightBar = new ProgressBar(this, 0.0F, 100, 0,
350                 ProgressColorId::PROG_WEIGHT,
351                 "weightprogressbar.xml", "weightprogressbar_fill.xml");
352             mWeightBar->setColor(getThemeColor(ThemeColorId::WEIGHT_BAR, 255U),
353                 getThemeColor(ThemeColorId::WEIGHT_BAR_OUTLINE, 255U));
354 
355             mSlotsBarCell = &place(3, 0, mSlotsBar, 3, 1);
356             mSortDropDownCell = &place(6, 0, mSortDropDown, 1, 1);
357 
358             mFilterCell = &place(0, 1, mFilter, 7, 1).setPadding(3);
359             mNameFilterCell = &place(6, 1, mNameFilter, 1, 1);
360 
361             place(0, 0, mWeightBar, 3, 1);
362             place(0, 2, invenScroll, 7, 4);
363             place(0, 6, mStoreButton, 1, 1);
364             place(1, 6, mRetrieveButton, 1, 1);
365             place(6, 6, mInvCloseButton, 1, 1);
366             break;
367         }
368 
369         default:
370         case InventoryType::Trade:
371         case InventoryType::Npc:
372         case InventoryType::Vending:
373         case InventoryType::MailEdit:
374         case InventoryType::MailView:
375         case InventoryType::Craft:
376         case InventoryType::TypeEnd:
377             break;
378     }
379 
380     Layout &layout = getLayout();
381     layout.setRowHeight(2, LayoutType::SET);
382 
383     mInventory->addInventoyListener(this);
384 
385     invInstances.push_back(this);
386 
387     if (inventory->isMainInventory())
388     {
389         updateDropButton();
390     }
391     else
392     {
393         if (!invInstances.empty())
394             invInstances.front()->updateDropButton();
395     }
396 
397     loadWindowState();
398     enableVisibleSound(true);
399 }
400 
postInit()401 void InventoryWindow::postInit()
402 {
403     Window::postInit();
404     slotsChanged(mInventory);
405 
406     mItems->setSortType(mSortDropDown->getSelected());
407     widgetResized(Event(nullptr));
408     if (mInventory != nullptr &&
409         mInventory->getType() == InventoryType::Storage)
410     {
411         setVisible(Visible_true);
412     }
413 }
414 
~InventoryWindow()415 InventoryWindow::~InventoryWindow()
416 {
417     invInstances.remove(this);
418     if (mInventory != nullptr)
419         mInventory->removeInventoyListener(this);
420     if (!invInstances.empty())
421         invInstances.front()->updateDropButton();
422 
423     mSortDropDown->hideDrop(false);
424     delete2(mSortModel)
425 }
426 
storeSortOrder() const427 void InventoryWindow::storeSortOrder() const
428 {
429     if (mInventory != nullptr)
430     {
431         switch (mInventory->getType())
432         {
433             case InventoryType::Inventory:
434             case InventoryType::Trade:
435             case InventoryType::Npc:
436             case InventoryType::Vending:
437             case InventoryType::MailEdit:
438             case InventoryType::MailView:
439             case InventoryType::Craft:
440             case InventoryType::TypeEnd:
441             default:
442                 config.setValue("inventorySortOrder",
443                     mSortDropDown->getSelected());
444                 break;
445             case InventoryType::Storage:
446                 config.setValue("storageSortOrder",
447                     mSortDropDown->getSelected());
448                 break;
449             case InventoryType::Cart:
450                 config.setValue("cartSortOrder",
451                     mSortDropDown->getSelected());
452                 break;
453         }
454     }
455 }
456 
action(const ActionEvent & event)457 void InventoryWindow::action(const ActionEvent &event)
458 {
459     const std::string &eventId = event.getId();
460     if (eventId == "outfit")
461     {
462         inputManager.executeAction(InputAction::WINDOW_OUTFIT);
463     }
464     else if (eventId == "shop")
465     {
466         inputManager.executeAction(InputAction::WINDOW_SHOP);
467     }
468     else if (eventId == "equipment")
469     {
470         inputManager.executeAction(InputAction::WINDOW_EQUIPMENT);
471     }
472     else if (eventId == "cart")
473     {
474         inputManager.executeAction(InputAction::WINDOW_CART);
475     }
476     else if (eventId == "close")
477     {
478         close();
479     }
480     else if (eventId == "store")
481     {
482         if (inventoryWindow == nullptr || !inventoryWindow->isWindowVisible())
483             return;
484 
485         Item *const item = inventoryWindow->getSelectedItem();
486 
487         if (item == nullptr)
488             return;
489 
490         if (storageWindow != nullptr)
491         {
492             ItemAmountWindow::showWindow(ItemAmountWindowUsage::StoreAdd,
493                 this,
494                 item,
495                 0,
496                 0);
497         }
498         else if ((cartWindow != nullptr) && cartWindow->isWindowVisible())
499         {
500             ItemAmountWindow::showWindow(ItemAmountWindowUsage::CartAdd,
501                 this,
502                 item,
503                 0,
504                 0);
505         }
506     }
507     else if (eventId == "sort")
508     {
509         mItems->setSortType(mSortDropDown->getSelected());
510         storeSortOrder();
511         return;
512     }
513     else if (eventId == "namefilter")
514     {
515         mItems->setName(mNameFilter->getText());
516         mItems->updateMatrix();
517     }
518     else if (eventId.find("tag_") == 0U)
519     {
520         std::string tagName = event.getId().substr(4);
521         mItems->setFilter(ItemDB::getTagId(tagName));
522         return;
523     }
524 
525     Item *const item = mItems->getSelectedItem();
526 
527     if (item == nullptr)
528         return;
529 
530     if (eventId == "use")
531     {
532         PlayerInfo::useEquipItem(item, 0, Sfx_true);
533     }
534     if (eventId == "equip")
535     {
536         PlayerInfo::useEquipItem2(item, 0, Sfx_true);
537     }
538     else if (eventId == "drop")
539     {
540         if (isStorageActive())
541         {
542             inventoryHandler->moveItem2(InventoryType::Inventory,
543                 item->getInvIndex(),
544                 item->getQuantity(),
545                 InventoryType::Storage);
546         }
547         else if ((cartWindow != nullptr) && cartWindow->isWindowVisible())
548         {
549             inventoryHandler->moveItem2(InventoryType::Inventory,
550                 item->getInvIndex(),
551                 item->getQuantity(),
552                 InventoryType::Cart);
553         }
554         else
555         {
556             if (PlayerInfo::isItemProtected(item->getId()))
557                 return;
558 
559             if (inputManager.isActionActive(InputAction::STOP_ATTACK))
560             {
561                 PlayerInfo::dropItem(item, item->getQuantity(), Sfx_true);
562             }
563             else
564             {
565                 ItemAmountWindow::showWindow(ItemAmountWindowUsage::ItemDrop,
566                     this,
567                     item,
568                     0,
569                     0);
570             }
571         }
572     }
573     else if (eventId == "split")
574     {
575         ItemAmountWindow::showWindow(ItemAmountWindowUsage::ItemSplit,
576             this,
577             item,
578             item->getQuantity() - 1,
579             9);
580     }
581     else if (eventId == "retrieve")
582     {
583         if (storageWindow != nullptr)
584         {
585             ItemAmountWindow::showWindow(ItemAmountWindowUsage::StoreRemove,
586                 this,
587                 item,
588                 0,
589                 0);
590         }
591         else if ((cartWindow != nullptr) && cartWindow->isWindowVisible())
592         {
593             ItemAmountWindow::showWindow(ItemAmountWindowUsage::CartRemove,
594                 this,
595                 item,
596                 0,
597                 0);
598         }
599     }
600 }
601 
getSelectedItem() const602 Item *InventoryWindow::getSelectedItem() const
603 {
604     return mItems->getSelectedItem();
605 }
606 
unselectItem()607 void InventoryWindow::unselectItem()
608 {
609     mItems->selectNone();
610 }
611 
widgetHidden(const Event & event)612 void InventoryWindow::widgetHidden(const Event &event)
613 {
614     Window::widgetHidden(event);
615     if (itemPopup != nullptr)
616         itemPopup->setVisible(Visible_false);
617 }
618 
mouseClicked(MouseEvent & event)619 void InventoryWindow::mouseClicked(MouseEvent &event)
620 {
621     Window::mouseClicked(event);
622 
623     const int clicks = event.getClickCount();
624 
625     if (clicks == 2 && (gui != nullptr))
626         gui->resetClickCount();
627 
628     const bool mod = (isStorageActive() &&
629         inputManager.isActionActive(InputAction::STOP_ATTACK));
630 
631     const bool mod2 = (tradeWindow != nullptr &&
632         tradeWindow->isWindowVisible() &&
633         inputManager.isActionActive(InputAction::STOP_ATTACK));
634 
635     if (mInventory != nullptr)
636     {
637         if (!mod && !mod2 && event.getButton() == MouseButton::RIGHT)
638         {
639             Item *const item = mItems->getSelectedItem();
640 
641             if (item == nullptr)
642                 return;
643 
644             /* Convert relative to the window coordinates to absolute screen
645              * coordinates.
646              */
647             if (popupMenu != nullptr)
648             {
649                 const int mx = event.getX() + getX();
650                 const int my = event.getY() + getY();
651 
652                 popupMenu->showPopup(this,
653                     mx, my,
654                     item,
655                     mInventory->getType());
656             }
657         }
658     }
659     else
660     {
661         return;
662     }
663 
664     if (event.getButton() == MouseButton::LEFT ||
665         event.getButton() == MouseButton::RIGHT)
666     {
667         Item *const item = mItems->getSelectedItem();
668 
669         if (item == nullptr)
670             return;
671 
672         if (mod)
673         {
674             if (mInventory->isMainInventory())
675             {
676                 if (event.getButton() == MouseButton::RIGHT)
677                 {
678                     ItemAmountWindow::showWindow(
679                         ItemAmountWindowUsage::StoreAdd,
680                         inventoryWindow,
681                         item,
682                         0,
683                         0);
684                 }
685                 else
686                 {
687                     inventoryHandler->moveItem2(InventoryType::Inventory,
688                         item->getInvIndex(),
689                         item->getQuantity(),
690                         InventoryType::Storage);
691                 }
692             }
693             else
694             {
695                 if (event.getButton() == MouseButton::RIGHT)
696                 {
697                     ItemAmountWindow::showWindow(
698                         ItemAmountWindowUsage::StoreRemove,
699                         inventoryWindow,
700                         item,
701                         0,
702                         0);
703                 }
704                 else
705                 {
706                     inventoryHandler->moveItem2(InventoryType::Storage,
707                         item->getInvIndex(),
708                         item->getQuantity(),
709                         InventoryType::Inventory);
710                 }
711             }
712         }
713         else if (mod2 && mInventory->isMainInventory())
714         {
715             if (PlayerInfo::isItemProtected(item->getId()))
716                 return;
717             if (event.getButton() == MouseButton::RIGHT)
718             {
719                 ItemAmountWindow::showWindow(ItemAmountWindowUsage::TradeAdd,
720                     tradeWindow,
721                     item,
722                     0,
723                     0);
724             }
725             else
726             {
727                 if (tradeWindow != nullptr)
728                     tradeWindow->tradeItem(item, item->getQuantity(), true);
729             }
730         }
731         else if (clicks == 2)
732         {
733             if (mInventory->isMainInventory())
734             {
735                 if (isStorageActive())
736                 {
737                     ItemAmountWindow::showWindow(
738                         ItemAmountWindowUsage::StoreAdd,
739                         inventoryWindow,
740                         item,
741                         0,
742                         0);
743                 }
744                 else if (tradeWindow != nullptr &&
745                          tradeWindow->isWindowVisible())
746                 {
747                     if (PlayerInfo::isItemProtected(item->getId()))
748                         return;
749                     ItemAmountWindow::showWindow(
750                         ItemAmountWindowUsage::TradeAdd,
751                         tradeWindow,
752                         item,
753                         0,
754                         0);
755                 }
756                 else
757                 {
758                     PlayerInfo::useEquipItem(item, 0, Sfx_true);
759                 }
760             }
761             else
762             {
763                 if (isStorageActive())
764                 {
765                     ItemAmountWindow::showWindow(
766                         ItemAmountWindowUsage::StoreRemove,
767                         inventoryWindow,
768                         item,
769                         0,
770                         0);
771                 }
772             }
773         }
774     }
775 }
776 
mouseMoved(MouseEvent & event)777 void InventoryWindow::mouseMoved(MouseEvent &event)
778 {
779     Window::mouseMoved(event);
780     if (textPopup == nullptr)
781         return;
782 
783     const Widget *const src = event.getSource();
784     if (src == nullptr)
785     {
786         textPopup->hide();
787         return;
788     }
789     const int x = event.getX();
790     const int y = event.getY();
791     const Rect &rect = mDimension;
792     if (src == mSlotsBar || src == mWeightBar)
793     {
794         // TRANSLATORS: money label
795         textPopup->show(rect.x + x, rect.y + y, strprintf(_("Money: %s"),
796             UnitsDb::formatCurrency(PlayerInfo::getAttribute(
797             Attributes::MONEY)).c_str()));
798     }
799     else
800     {
801         const Button *const btn = dynamic_cast<const Button *>(src);
802         if (btn == nullptr)
803         {
804             textPopup->hide();
805             return;
806         }
807         const std::string text = btn->getDescription();
808         if (!text.empty())
809             textPopup->show(x + rect.x, y + rect.y, text);
810     }
811 }
812 
mouseExited(MouseEvent & event A_UNUSED)813 void InventoryWindow::mouseExited(MouseEvent &event A_UNUSED)
814 {
815     textPopup->hide();
816 }
817 
keyPressed(KeyEvent & event)818 void InventoryWindow::keyPressed(KeyEvent &event)
819 {
820     if (event.getActionId() == InputAction::GUI_MOD)
821         mSplit = true;
822 }
823 
keyReleased(KeyEvent & event)824 void InventoryWindow::keyReleased(KeyEvent &event)
825 {
826     if (event.getActionId() == InputAction::GUI_MOD)
827         mSplit = false;
828 }
829 
valueChanged(const SelectionEvent & event A_UNUSED)830 void InventoryWindow::valueChanged(const SelectionEvent &event A_UNUSED)
831 {
832     if ((mInventory == nullptr) || !mInventory->isMainInventory())
833         return;
834 
835     Item *const item = mItems->getSelectedItem();
836 
837     if (mSplit && (item != nullptr) && inventoryHandler->
838         canSplit(mItems->getSelectedItem()))
839     {
840         ItemAmountWindow::showWindow(ItemAmountWindowUsage::ItemSplit,
841             this,
842             item,
843             item->getQuantity() - 1,
844             0);
845     }
846     updateButtons(item);
847 }
848 
updateButtons(const Item * item)849 void InventoryWindow::updateButtons(const Item *item)
850 {
851     if ((mInventory == nullptr) || !mInventory->isMainInventory())
852         return;
853 
854     const Item *const selectedItem = mItems->getSelectedItem();
855     if ((item != nullptr) && selectedItem != item)
856         return;
857 
858     if (item == nullptr)
859         item = selectedItem;
860 
861     if ((item == nullptr) || item->getQuantity() == 0)
862     {
863         if (mUseButton != nullptr)
864             mUseButton->setEnabled(false);
865         if (mDropButton != nullptr)
866             mDropButton->setEnabled(false);
867         return;
868     }
869 
870     if (mDropButton != nullptr)
871         mDropButton->setEnabled(true);
872 
873     if (mUseButton != nullptr)
874     {
875         const ItemInfo &info = item->getInfo();
876         const std::string &str = (item->isEquipment() == Equipm_true
877             && item->isEquipped() == Equipped_true)
878             ? info.getUseButton2() : info.getUseButton();
879         if (str.empty())
880         {
881             mUseButton->setEnabled(false);
882             // TRANSLATORS: default use button name
883             mUseButton->setCaption(_("Use"));
884         }
885         else
886         {
887             mUseButton->setEnabled(true);
888             mUseButton->setCaption(str);
889         }
890     }
891 
892     updateDropButton();
893 }
894 
close()895 void InventoryWindow::close()
896 {
897     if (mInventory == nullptr)
898     {
899         Window::close();
900         return;
901     }
902 
903     switch (mInventory->getType())
904     {
905         case InventoryType::Inventory:
906         case InventoryType::Cart:
907             setVisible(Visible_false);
908             break;
909 
910         case InventoryType::Storage:
911             if (inventoryHandler != nullptr)
912             {
913                 inventoryHandler->closeStorage();
914                 inventoryHandler->forgotStorage();
915             }
916             scheduleDelete();
917             break;
918 
919         default:
920         case InventoryType::Trade:
921         case InventoryType::Npc:
922         case InventoryType::Vending:
923         case InventoryType::MailEdit:
924         case InventoryType::MailView:
925         case InventoryType::Craft:
926         case InventoryType::TypeEnd:
927             break;
928     }
929 }
930 
updateWeight()931 void InventoryWindow::updateWeight()
932 {
933     if ((mInventory == nullptr) || (mWeightBar == nullptr))
934         return;
935     const InventoryTypeT type = mInventory->getType();
936     if (type != InventoryType::Inventory &&
937         type != InventoryType::Cart)
938     {
939         return;
940     }
941 
942     const bool isInv = type == InventoryType::Inventory;
943     const int total = PlayerInfo::getAttribute(isInv
944         ? Attributes::TOTAL_WEIGHT : Attributes::CART_TOTAL_WEIGHT);
945     const int max = PlayerInfo::getAttribute(isInv
946         ? Attributes::MAX_WEIGHT : Attributes::CART_MAX_WEIGHT);
947 
948     if (max <= 0)
949         return;
950 
951     // Adjust progress bar
952     mWeightBar->setProgress(static_cast<float>(total)
953         / static_cast<float>(max));
954     mWeightBar->setText(strprintf("%s/%s",
955         UnitsDb::formatWeight(total).c_str(),
956         UnitsDb::formatWeight(max).c_str()));
957 }
958 
slotsChanged(const Inventory * const inventory)959 void InventoryWindow::slotsChanged(const Inventory *const inventory)
960 {
961     if (inventory == mInventory)
962     {
963         const int usedSlots = mInventory->getNumberOfSlotsUsed();
964         const int maxSlots = mInventory->getSize();
965 
966         if (maxSlots != 0)
967         {
968             mSlotsBar->setProgress(static_cast<float>(usedSlots)
969                 / static_cast<float>(maxSlots));
970         }
971 
972         mSlotsBar->setText(strprintf("%d/%d", usedSlots, maxSlots));
973         mItems->updateMatrix();
974     }
975 }
976 
updateDropButton()977 void InventoryWindow::updateDropButton()
978 {
979     if (mDropButton == nullptr)
980         return;
981 
982     if (isStorageActive() ||
983         (cartWindow != nullptr && cartWindow->isWindowVisible()))
984     {
985         // TRANSLATORS: inventory button
986         mDropButton->setCaption(_("Store"));
987     }
988     else
989     {
990         const Item *const item = mItems->getSelectedItem();
991         if ((item != nullptr) && item->getQuantity() > 1)
992         {
993             // TRANSLATORS: inventory button
994             mDropButton->setCaption(_("Drop..."));
995         }
996         else
997         {
998             // TRANSLATORS: inventory button
999             mDropButton->setCaption(_("Drop"));
1000         }
1001     }
1002 }
1003 
isInputFocused() const1004 bool InventoryWindow::isInputFocused() const
1005 {
1006     return (mNameFilter != nullptr) && mNameFilter->isFocused();
1007 }
1008 
isAnyInputFocused()1009 bool InventoryWindow::isAnyInputFocused()
1010 {
1011     FOR_EACH (WindowList::const_iterator, it, invInstances)
1012     {
1013         if (((*it) != nullptr) && (*it)->isInputFocused())
1014             return true;
1015     }
1016     return false;
1017 }
1018 
getFirstVisible()1019 InventoryWindow *InventoryWindow::getFirstVisible()
1020 {
1021     std::set<Widget*> list;
1022     FOR_EACH (WindowList::const_iterator, it, invInstances)
1023     {
1024         if (((*it) != nullptr) && (*it)->isWindowVisible())
1025             list.insert(*it);
1026     }
1027     return dynamic_cast<InventoryWindow*>(
1028         windowContainer->findFirstWidget(list));
1029 }
1030 
nextTab()1031 void InventoryWindow::nextTab()
1032 {
1033     const InventoryWindow *const window = getFirstVisible();
1034     if (window != nullptr)
1035         window->mFilter->nextTab();
1036 }
1037 
prevTab()1038 void InventoryWindow::prevTab()
1039 {
1040     const InventoryWindow *const window = getFirstVisible();
1041     if (window != nullptr)
1042         window->mFilter->prevTab();
1043 }
1044 
widgetResized(const Event & event)1045 void InventoryWindow::widgetResized(const Event &event)
1046 {
1047     Window::widgetResized(event);
1048 
1049     if (mInventory == nullptr)
1050         return;
1051     const InventoryTypeT type = mInventory->getType();
1052     if (type != InventoryType::Inventory &&
1053         type != InventoryType::Cart)
1054     {
1055         return;
1056     }
1057 
1058     if (getWidth() < 600)
1059     {
1060         if (!mCompactMode)
1061         {
1062             mNameFilter->setVisible(Visible_false);
1063             mNameFilterCell->setType(LayoutCell::NONE);
1064             mFilterCell->setWidth(mFilterCell->getWidth() + 3);
1065             mCompactMode = true;
1066         }
1067     }
1068     else if (mCompactMode)
1069     {
1070         mNameFilter->setVisible(Visible_true);
1071         mNameFilterCell->setType(LayoutCell::WIDGET);
1072         mFilterCell->setWidth(mFilterCell->getWidth() - 3);
1073         mCompactMode = false;
1074     }
1075 }
1076 
setVisible(Visible visible)1077 void InventoryWindow::setVisible(Visible visible)
1078 {
1079     if (visible == Visible_false)
1080         mSortDropDown->hideDrop(true);
1081     Window::setVisible(visible);
1082 }
1083 
unsetInventory()1084 void InventoryWindow::unsetInventory()
1085 {
1086     if (mInventory != nullptr)
1087     {
1088         mInventory->removeInventoyListener(this);
1089         if (mItems != nullptr)
1090             mItems->unsetInventory();
1091     }
1092     mInventory = nullptr;
1093 }
1094 
attributeChanged(const AttributesT id,const int64_t oldVal A_UNUSED,const int64_t newVal A_UNUSED)1095 void InventoryWindow::attributeChanged(const AttributesT id,
1096                                        const int64_t oldVal A_UNUSED,
1097                                        const int64_t newVal A_UNUSED)
1098 {
1099     if (id == Attributes::TOTAL_WEIGHT
1100         || id == Attributes::MAX_WEIGHT
1101         || id == Attributes::CART_TOTAL_WEIGHT
1102         || id == Attributes::CART_MAX_WEIGHT)
1103     {
1104         updateWeight();
1105     }
1106 }
1107 
combineItems(const int index1,const int index2)1108 void InventoryWindow::combineItems(const int index1,
1109                                    const int index2)
1110 {
1111     if (mInventory == nullptr)
1112         return;
1113     const Item *item1 = mInventory->getItem(index1);
1114     if (item1 == nullptr)
1115         return;
1116     const Item *item2 = mInventory->getItem(index2);
1117     if (item2 == nullptr)
1118         return;
1119 
1120     if (item1->getType() != ItemType::Card)
1121     {
1122         const Item *tmpItem = item1;
1123         item1 = item2;
1124         item2 = tmpItem;
1125     }
1126 
1127     ConfirmDialog *const confirmDlg = CREATEWIDGETR(ConfirmDialog,
1128         // TRANSLATORS: question dialog title
1129         _("Insert card request"),
1130         // TRANSLATORS: question dialog message
1131         strprintf(_("Insert %s into %s?"),
1132         item1->getName().c_str(),
1133         item2->getName().c_str()),
1134         SOUND_REQUEST,
1135         false,
1136         Modal_true,
1137         nullptr);
1138     insertCardListener.itemIndex = item2->getInvIndex();
1139     insertCardListener.cardIndex = item1->getInvIndex();
1140     confirmDlg->addActionListener(&insertCardListener);
1141 }
1142 
moveItemToCraft(const int craftSlot)1143 void InventoryWindow::moveItemToCraft(const int craftSlot)
1144 {
1145     if (npcHandler == nullptr)
1146         return;
1147 
1148     Item *const item = mItems->getSelectedItem();
1149     if (item == nullptr)
1150         return;
1151 
1152     NpcDialog *const dialog = npcHandler->getCurrentNpcDialog();
1153     if ((dialog != nullptr) &&
1154         dialog->getInputState() == NpcInputState::ITEM_CRAFT)
1155     {
1156         if (item->getQuantity() > 1
1157             && !inputManager.isActionActive(InputAction::STOP_ATTACK))
1158         {
1159             ItemAmountWindow::showWindow(ItemAmountWindowUsage::CraftAdd,
1160                 npcHandler->getCurrentNpcDialog(),
1161                 item,
1162                 0,
1163                 craftSlot);
1164         }
1165         else
1166         {
1167             dialog->addCraftItem(item, 1, craftSlot);
1168         }
1169     }
1170 }
1171