/* * The ManaPlus Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2013 The ManaPlus Developers * * This file is part of The ManaPlus Client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "gui/widgets/itemcontainer.h" #include "dragdrop.h" #include "inventory.h" #include "item.h" #include "itemshortcut.h" #include "dropshortcut.h" #include "gui/chatwindow.h" #include "gui/gui.h" #include "gui/itempopup.h" #include "gui/outfitwindow.h" #include "gui/shopwindow.h" #include "gui/shortcutwindow.h" #include "gui/sdlinput.h" #include "gui/viewport.h" #include "net/net.h" #include "net/inventoryhandler.h" #include "utils/gettext.h" #include "resources/image.h" #include #include #include #include "debug.h" namespace { class ItemIdPair final { public: ItemIdPair(const int id, Item *const item) : mId(id), mItem(item) { } int mId; Item* mItem; }; class SortItemAlphaFunctor final { public: bool operator() (const ItemIdPair *const pair1, const ItemIdPair *const pair2) const { if (!pair1 || !pair2) return false; return (pair1->mItem->getInfo().getName() < pair2->mItem->getInfo().getName()); } } itemAlphaInvSorter; class SortItemIdFunctor final { public: bool operator() (const ItemIdPair *const pair1, const ItemIdPair *const pair2) const { if (!pair1 || !pair2) return false; return pair1->mItem->getId() < pair2->mItem->getId(); } } itemIdInvSorter; class SortItemWeightFunctor final { public: bool operator() (const ItemIdPair *const pair1, const ItemIdPair *const pair2) const { if (!pair1 || !pair2) return false; const int w1 = pair1->mItem->getInfo().getWeight(); const int w2 = pair2->mItem->getInfo().getWeight(); if (w1 == w2) { return (pair1->mItem->getInfo().getName() < pair2->mItem->getInfo().getName()); } return w1 < w2; } } itemWeightInvSorter; class SortItemAmountFunctor final { public: bool operator() (const ItemIdPair *const pair1, const ItemIdPair *const pair2) const { if (!pair1 || !pair2) return false; const int c1 = pair1->mItem->getQuantity(); const int c2 = pair2->mItem->getQuantity(); if (c1 == c2) { return (pair1->mItem->getInfo().getName() < pair2->mItem->getInfo().getName()); } return c1 < c2; } } itemAmountInvSorter; class SortItemTypeFunctor final { public: bool operator() (const ItemIdPair *const pair1, const ItemIdPair *const pair2) const { if (!pair1 || !pair2) return false; const int t1 = pair1->mItem->getInfo().getType(); const int t2 = pair2->mItem->getInfo().getType(); if (t1 == t2) { return (pair1->mItem->getInfo().getName() < pair2->mItem->getInfo().getName()); } return t1 < t2; } } itemTypeInvSorter; } // namespace ItemContainer::ItemContainer(const Widget2 *const widget, Inventory *const inventory, const bool forceQuantity) : gcn::Widget(), Widget2(widget), gcn::KeyListener(), gcn::MouseListener(), gcn::WidgetListener(), mInventory(inventory), mGridColumns(1), mGridRows(1), mSelImg(Theme::getImageFromThemeXml("item_selection.xml", "")), mSelectedIndex(-1), mHighlightedIndex(-1), mLastUsedSlot(-1), mSelectionStatus(SEL_NONE), mForceQuantity(forceQuantity), mSwapItems(false), mDescItems(false), mDragPosX(0), mDragPosY(0), mTag(0), mSortType(0), mName(), mItemPopup(new ItemPopup), mShowMatrix(nullptr), mClicks(1), mSkin(Theme::instance() ? Theme::instance()->load( "itemcontainer.xml", "") : nullptr), mBoxWidth(mSkin ? mSkin->getOption("boxWidth", 35) : 35), mBoxHeight(mSkin ? mSkin->getOption("boxHeight", 43) : 43), mEquippedTextPadding(mSkin ? mSkin->getOption( "equippedTextPadding", 29) : 29), mPaddingItemX(mSkin ? mSkin->getOption("paddingItemX", 0) : 0), mPaddingItemY(mSkin ? mSkin->getOption("paddingItemY", 0) : 0), mSelectionListeners(), mEquipedColor(getThemeColor(Theme::ITEM_EQUIPPED)), mEquipedColor2(getThemeColor(Theme::ITEM_EQUIPPED_OUTLINE)), mUnEquipedColor(getThemeColor(Theme::ITEM_NOT_EQUIPPED)), mUnEquipedColor2(getThemeColor(Theme::ITEM_NOT_EQUIPPED_OUTLINE)) { setFocusable(true); addKeyListener(this); addMouseListener(this); addWidgetListener(this); } ItemContainer::~ItemContainer() { if (gui) gui->removeDragged(this); if (mSelImg) { mSelImg->decRef(); mSelImg = nullptr; } if (Theme::instance()) Theme::instance()->unload(mSkin); delete mItemPopup; mItemPopup = nullptr; delete []mShowMatrix; } void ItemContainer::logic() { BLOCK_START("ItemContainer::logic") gcn::Widget::logic(); if (!mInventory) { BLOCK_END("ItemContainer::logic") return; } const int lastUsedSlot = mInventory->getLastUsedSlot(); if (lastUsedSlot != mLastUsedSlot) { mLastUsedSlot = lastUsedSlot; adjustHeight(); } BLOCK_END("ItemContainer::logic") } void ItemContainer::draw(gcn::Graphics *graphics) { if (!mInventory || !mShowMatrix) return; BLOCK_START("ItemContainer::draw") Graphics *const g = static_cast(graphics); gcn::Font *const font = getFont(); for (int j = 0; j < mGridRows; j++) { const int intY0 = j * mBoxHeight; int itemIndex = j * mGridColumns - 1; for (int i = 0; i < mGridColumns; i++) { int itemX = i * mBoxWidth; int itemY = intY0; itemIndex ++; if (mShowMatrix[itemIndex] < 0) continue; const Item *const item = mInventory->getItem( mShowMatrix[itemIndex]); if (!item || item->getId() == 0) continue; Image *const image = item->getImage(); if (image) { if (mShowMatrix[itemIndex] == mSelectedIndex) { if (mSelImg) g->drawImage(mSelImg, itemX, itemY); } image->setAlpha(1.0f); // ensure the image if fully drawn... g->drawImage(image, itemX + mPaddingItemX, itemY + mPaddingItemY); } } } for (int j = 0; j < mGridRows; j++) { const int intY0 = j * mBoxHeight; int itemIndex = j * mGridColumns - 1; for (int i = 0; i < mGridColumns; i++) { int itemX = i * mBoxWidth; int itemY = intY0; itemIndex ++; if (mShowMatrix[itemIndex] < 0) continue; const Item *const item = mInventory->getItem( mShowMatrix[itemIndex]); if (!item || item->getId() == 0) continue; // Draw item caption std::string caption; if (item->getQuantity() > 1 || mForceQuantity) { caption = toString(item->getQuantity()); } else if (item->isEquipped()) { // TRANSLATORS: Text under equipped items (should be small) caption = _("Eq."); } if (item->isEquipped()) g->setColorAll(mEquipedColor, mEquipedColor2); else g->setColorAll(mUnEquipedColor, mUnEquipedColor2); font->drawString(g, caption, itemX + (mBoxWidth - font->getWidth(caption)) / 2, itemY + mEquippedTextPadding); } } BLOCK_END("ItemContainer::draw") } void ItemContainer::selectNone() { dragDrop.clear(); setSelectedIndex(-1); mSelectionStatus = SEL_NONE; /* if (outfitWindow) outfitWindow->setItemSelected(-1); if (shopWindow) shopWindow->setItemSelected(-1); */ } void ItemContainer::setSelectedIndex(const int newIndex) { if (mSelectedIndex != newIndex) { mSelectedIndex = newIndex; distributeValueChangedEvent(); } } Item *ItemContainer::getSelectedItem() const { if (mInventory) return mInventory->getItem(mSelectedIndex); else return nullptr; } void ItemContainer::distributeValueChangedEvent() { FOR_EACH (SelectionListenerIterator, i, mSelectionListeners) { if (*i) { gcn::SelectionEvent event(this); (*i)->valueChanged(event); } } } void ItemContainer::hidePopup() { if (mItemPopup) mItemPopup->setVisible(false); } void ItemContainer::keyPressed(gcn::KeyEvent &event A_UNUSED) { } void ItemContainer::keyReleased(gcn::KeyEvent &event A_UNUSED) { } void ItemContainer::mousePressed(gcn::MouseEvent &event) { if (!mInventory) return; const int button = event.getButton(); mClicks = event.getClickCount(); if (button == gcn::MouseEvent::LEFT || button == gcn::MouseEvent::RIGHT) { const int index = getSlotIndex(event.getX(), event.getY()); if (index == Inventory::NO_SLOT_INDEX) return; Item *const item = mInventory->getItem(index); // put item name into chat window if (item && mDescItems && chatWindow) chatWindow->addItemText(item->getInfo().getName()); if (mSelectedIndex == index && mClicks != 2) { dragDrop.dragItem(item, DRAGDROP_SOURCE_INVENTORY); dragDrop.select(item); mSelectionStatus = SEL_DESELECTING; } else if (item && item->getId()) { if (index >= 0) { dragDrop.dragItem(item, DRAGDROP_SOURCE_INVENTORY); dragDrop.select(item); setSelectedIndex(index); mSelectionStatus = SEL_SELECTING; if (itemShortcutWindow) { const int num = itemShortcutWindow->getTabIndex(); if (num >= 0 && num < static_cast(SHORTCUT_TABS)) { if (itemShortcut[num]) itemShortcut[num]->setItemSelected(item); } } if (shopWindow) shopWindow->setItemSelected(item->getId()); } } else { dragDrop.deselect(); selectNone(); } } } void ItemContainer::mouseDragged(gcn::MouseEvent &event) { if (mSelectionStatus != SEL_NONE) { mSelectionStatus = SEL_DRAGGING; mDragPosX = event.getX(); mDragPosY = event.getY(); } } void ItemContainer::mouseReleased(gcn::MouseEvent &event) { if (mClicks == 2) return; switch (mSelectionStatus) { case SEL_SELECTING: mSelectionStatus = SEL_SELECTED; return; case SEL_DESELECTING: selectNone(); return; case SEL_DRAGGING: mSelectionStatus = SEL_SELECTED; break; case SEL_NONE: case SEL_SELECTED: default: return; }; const int index = getSlotIndex(event.getX(), event.getY()); if (index == Inventory::NO_SLOT_INDEX) return; if (index == mSelectedIndex || mSelectedIndex == -1) return; Net::getInventoryHandler()->moveItem(mSelectedIndex, index); selectNone(); } void ItemContainer::mouseMoved(gcn::MouseEvent &event) { if (!mInventory) return; const Item *const item = mInventory->getItem( getSlotIndex(event.getX(), event.getY())); if (item && viewport) { mItemPopup->setItem(item); mItemPopup->position(viewport->getMouseX(), viewport->getMouseY()); } else { mItemPopup->setVisible(false); } } void ItemContainer::mouseExited(gcn::MouseEvent &event A_UNUSED) { mItemPopup->setVisible(false); } void ItemContainer::widgetResized(const gcn::Event &event A_UNUSED) { mGridColumns = std::max(1, mDimension.width / mBoxWidth); adjustHeight(); } void ItemContainer::adjustHeight() { if (!mGridColumns) return; mGridRows = (mLastUsedSlot + 1) / mGridColumns; if (mGridRows == 0 || (mLastUsedSlot + 1) % mGridColumns > 0) ++mGridRows; setHeight(mGridRows * mBoxHeight); updateMatrix(); } void ItemContainer::updateMatrix() { if (!mInventory) return; delete []mShowMatrix; mShowMatrix = new int[mGridRows * mGridColumns]; std::vector sortedItems; int i = 0; int j = 0; std::string temp = mName; toLower(temp); const unsigned int invSize = mInventory->getSize(); for (unsigned int idx = 0; idx < invSize; idx ++) { Item *const item = mInventory->getItem(idx); if (!item || item->getId() == 0 || !item->isHaveTag(mTag)) continue; if (mName.empty()) { sortedItems.push_back(new ItemIdPair(idx, item)); continue; } std::string name = item->getInfo().getName(); toLower(name); if (name.find(temp) != std::string::npos) sortedItems.push_back(new ItemIdPair(idx, item)); } switch (mSortType) { case 0: default: break; case 1: std::sort(sortedItems.begin(), sortedItems.end(), itemAlphaInvSorter); break; case 2: std::sort(sortedItems.begin(), sortedItems.end(), itemIdInvSorter); break; case 3: std::sort(sortedItems.begin(), sortedItems.end(), itemWeightInvSorter); break; case 4: std::sort(sortedItems.begin(), sortedItems.end(), itemAmountInvSorter); break; case 5: std::sort(sortedItems.begin(), sortedItems.end(), itemTypeInvSorter); break; } int jMult = j * mGridColumns; const int maxSize = mGridRows * mGridColumns; FOR_EACH (std::vector::const_iterator, iter, sortedItems) { if (jMult >= maxSize) break; mShowMatrix[jMult + i] = (*iter)->mId; i ++; if (i >= mGridColumns) { i = 0; j ++; jMult += mGridColumns; } } for (int idx = j * mGridColumns + i; idx < maxSize; idx ++) mShowMatrix[idx] = -1; for (size_t idx = 0, sz = sortedItems.size(); idx < sz; idx ++) delete sortedItems[idx]; } int ItemContainer::getSlotIndex(const int x, const int y) const { if (!mShowMatrix) return Inventory::NO_SLOT_INDEX; if (x < mDimension.width && y < mDimension.height && x >= 0 && y >= 0) { const int idx = (y / mBoxHeight) * mGridColumns + (x / mBoxWidth); if (idx >= 0 && idx < mGridRows * mGridColumns && mShowMatrix[idx] >= 0) { return mShowMatrix[idx]; } } return Inventory::NO_SLOT_INDEX; } void ItemContainer::keyAction() { // If there is no highlight then return. if (mHighlightedIndex == -1) return; // If the highlight is on the selected item, then deselect it. if (mHighlightedIndex == mSelectedIndex) { selectNone(); } // Check and swap items if necessary. else if (mSwapItems && mSelectedIndex != -1 && mHighlightedIndex != -1) { Net::getInventoryHandler()->moveItem( mSelectedIndex, mHighlightedIndex); setSelectedIndex(mHighlightedIndex); } // If the highlight is on an item then select it. else if (mHighlightedIndex != -1) { setSelectedIndex(mHighlightedIndex); mSelectionStatus = SEL_SELECTED; } // If the highlight is on a blank space then move it. else if (mSelectedIndex != -1) { Net::getInventoryHandler()->moveItem( mSelectedIndex, mHighlightedIndex); selectNone(); } } void ItemContainer::moveHighlight(const Direction direction) { if (mHighlightedIndex == -1) { if (mSelectedIndex != -1) mHighlightedIndex = mSelectedIndex; else mHighlightedIndex = 0; return; } switch (direction) { case Left: if (mHighlightedIndex % mGridColumns == 0) mHighlightedIndex += mGridColumns; mHighlightedIndex--; break; case Right: if ((mHighlightedIndex % mGridColumns) == (mGridColumns - 1)) { mHighlightedIndex -= mGridColumns; } mHighlightedIndex++; break; case Up: if (mHighlightedIndex / mGridColumns == 0) mHighlightedIndex += (mGridColumns * mGridRows); mHighlightedIndex -= mGridColumns; break; case Down: if ((mHighlightedIndex / mGridColumns) == (mGridRows - 1)) { mHighlightedIndex -= (mGridColumns * mGridRows); } mHighlightedIndex += mGridColumns; break; default: break; } } void ItemContainer::setFilter(const int tag) { mTag = tag; updateMatrix(); } void ItemContainer::setSortType(const int sortType) { mSortType = sortType; updateMatrix(); }