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