diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 47ee63fbc..306ef4173 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -32,7 +32,8 @@ add_openmw_dir (mwgui itemselection spellbuyingwindow loadingscreen levelupdialog waitdialog spellcreationdialog enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks - keywordsearch + keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview + tradeitemmodel companionitemmodel pickpocketitemmodel ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8d71b37cc..ebbeff851 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -338,6 +338,9 @@ namespace MWBase virtual bool getActorStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is standing on \a object virtual float getWindSpeed() = 0; + virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; + ///< get all containers in active cells owned by this Npc + virtual void setupExternalRendering (MWRender::ExternalRendering& rendering) = 0; virtual int canRest() = 0; diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 32c127731..b56ed118f 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -164,4 +164,11 @@ namespace MWClass { return npcServices & ESM::NPC::Apparatus; } + + float Apparatus::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index d4917c618..17b8b9254 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -13,6 +13,8 @@ namespace MWClass public: + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index fa0d47107..8e4c8f953 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -387,4 +387,11 @@ namespace MWClass { return npcServices & ESM::NPC::Armor; } + + float Armor::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 964ba7146..d8d09d5bb 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -12,6 +12,8 @@ namespace MWClass public: + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 472d17da9..40ba21733 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -188,4 +188,11 @@ namespace MWClass { return npcServices & ESM::NPC::Books; } + + float Book::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index 3d728e506..7fb8a9507 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -60,6 +60,8 @@ namespace MWClass virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 599efa51a..df05a7910 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -307,4 +307,11 @@ namespace MWClass { return npcServices & ESM::NPC::Clothing; } + + float Clothing::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index db4b7a2e9..e2e1188a1 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -73,6 +73,8 @@ namespace MWClass virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 04889360f..9eef1896e 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -225,6 +225,16 @@ namespace MWClass return weight; } + + int Creature::getServices(const MWWorld::Ptr &actor) const + { + MWWorld::LiveCellRef* ref = actor.get(); + if (ref->mBase->mHasAI) + return ref->mBase->mAiData.mServices; + else + return 0; + } + MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 297c2ea61..03b8f5d70 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -62,6 +62,8 @@ namespace MWClass virtual bool isEssential (const MWWorld::Ptr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) + virtual int getServices (const MWWorld::Ptr& actor) const; + static void registerSelf(); virtual std::string getModel(const MWWorld::Ptr &ptr) const; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 0afe60e0e..2cc607f5f 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -202,4 +202,12 @@ namespace MWClass { return npcServices & ESM::NPC::Ingredients; } + + + float Ingredient::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index d27a7cbb0..690dd601a 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -57,6 +57,8 @@ namespace MWClass virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 306c6bbb6..ccc9d1de6 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -208,4 +208,11 @@ namespace MWClass { return npcServices & ESM::NPC::Lights; } + + float Light::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 7d919f75d..7f747ef48 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -58,6 +58,8 @@ namespace MWClass virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 52a22d28e..084490742 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -189,4 +189,11 @@ namespace MWClass return ref->mBase->mData.mUses; } + + float Lockpick::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index df794debd..a7cf3791f 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -60,6 +60,8 @@ namespace MWClass virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health }; diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 02307cc02..6b384be99 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -254,4 +254,11 @@ namespace MWClass return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc); } + float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } + } diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index ba00900bd..16a8e8c05 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -54,6 +54,8 @@ namespace MWClass const; ///< Generate action for using via inventory menu + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d0f1bcfeb..47dae4ec0 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -648,6 +648,15 @@ namespace MWClass scale *= race->mData.mHeight.mFemale; } + int Npc::getServices(const MWWorld::Ptr &actor) const + { + MWWorld::LiveCellRef* ref = actor.get(); + if (ref->mBase->mHasAI) + return ref->mBase->mAiData.mServices; + else + return 0; + } + MWWorld::Ptr Npc::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index d1a9158fd..8f440e7ec 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -127,6 +127,8 @@ namespace MWClass virtual bool isEssential (const MWWorld::Ptr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) + + virtual int getServices (const MWWorld::Ptr& actor) const; static void registerSelf(); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index ad2826ea8..c2d2920d0 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -199,4 +199,11 @@ namespace MWClass { return npcServices & ESM::NPC::Potions; } + + float Potion::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 845795d0e..0f0578ca0 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -53,6 +53,8 @@ namespace MWClass virtual std::string getModel(const MWWorld::Ptr &ptr) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 1c14c894d..6b7e43230 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -188,4 +188,11 @@ namespace MWClass return ref->mBase->mData.mUses; } + + float Probe::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index 70390c85c..832169f8b 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -60,6 +60,8 @@ namespace MWClass virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual int getItemMaxHealth (const MWWorld::Ptr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health }; diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 36e706825..e2993029b 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -180,4 +180,11 @@ namespace MWClass { return npcServices & ESM::NPC::RepairItem; } + + float Repair::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index a545cf4d6..28ca5ad4c 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -62,6 +62,8 @@ namespace MWClass ///< Return item max health or throw an exception, if class does not have item health /// (default implementation: throw an exceoption) + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 306a2d671..2367a0d7f 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -440,4 +440,11 @@ namespace MWClass { return npcServices & ESM::NPC::Weapon; } + + float Weapon::getWeight(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mWeight; + } } diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index ba444ba05..3902ef612 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -79,6 +79,8 @@ namespace MWClass virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 24a4e205b..3b6146a24 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -1,6 +1,7 @@ #include "alchemywindow.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -8,6 +9,11 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" + +#include "inventoryitemmodel.hpp" +#include "sortfilteritemmodel.hpp" +#include "itemview.hpp" namespace { @@ -20,13 +26,25 @@ namespace path.append(".dds"); return path; } + + std::string getCountString(const int count) + { + if (count == 1) + return ""; + if (count > 9999) + return boost::lexical_cast(int(count/1000.f)) + "k"; + else + return boost::lexical_cast(count); + } + } namespace MWGui { AlchemyWindow::AlchemyWindow() : WindowBase("openmw_alchemy_window.layout") - , ContainerBase(0), mApparatus (4), mIngredients (4) + , mApparatus (4) + , mIngredients (4) { getWidget(mCreateButton, "CreateButton"); getWidget(mCancelButton, "CancelButton"); @@ -40,6 +58,13 @@ namespace MWGui getWidget(mApparatus[3], "Apparatus4"); getWidget(mEffectsBox, "CreatedEffects"); getWidget(mNameEdit, "NameEdit"); + getWidget(mItemView, "ItemView"); + + InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + mSortModel = new SortFilterItemModel(model); + mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients); + mItemView->setModel (mSortModel); + mItemView->eventItemClicked += MyGUI::newDelegate(this, &AlchemyWindow::onSelectedItem); mIngredients[0]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); mIngredients[1]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); @@ -49,12 +74,6 @@ namespace MWGui mCreateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCreateButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked); - MyGUI::ScrollView* itemView; - MyGUI::Widget* containerWidget; - getWidget(containerWidget, "Items"); - getWidget(itemView, "ItemView"); - setWidgets(containerWidget, itemView); - center(); } @@ -126,12 +145,9 @@ namespace MWGui void AlchemyWindow::open() { - openContainer (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); // this sets mPtr - setFilter (ContainerBase::Filter_Ingredients); - mNameEdit->setCaption(""); - mAlchemy.setAlchemist (mPtr); + mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); int index = 0; @@ -155,8 +171,9 @@ namespace MWGui update(); } - void AlchemyWindow::onSelectedItemImpl(MWWorld::Ptr item) + void AlchemyWindow::onSelectedItem(int index) { + MWWorld::Ptr item = mSortModel->getItem(index).mBase; int res = mAlchemy.addIngredient(item); if (res != -1) @@ -168,19 +185,10 @@ namespace MWGui } } - std::vector AlchemyWindow::itemsToIgnore() - { - std::vector ignore; - // don't show ingredients that are currently selected in the "available ingredients" box. - for (int i=0; i<4; ++i) - if (mIngredients[i]->isUserString("ToolTipType")) - ignore.push_back(*mIngredients[i]->getUserData()); - - return ignore; - } - void AlchemyWindow::update() { + mSortModel->clearDragItems(); + MWMechanics::Alchemy::TIngredientsIterator it = mAlchemy.beginIngredients (); for (int i=0; i<4; ++i) { @@ -193,6 +201,9 @@ namespace MWGui ++it; } + if (!item.isEmpty()) + mSortModel->addDragItem(item, item.getRefData().getCount()); + if (ingredient->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0)); @@ -214,7 +225,7 @@ namespace MWGui text->setCaption(getCountString(ingredient->getUserData()->getRefData().getCount())); } - drawItems(); + mItemView->update(); std::vector effects; ESM::EffectList list; diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index 9189e4a63..3a58ebf06 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -5,19 +5,25 @@ #include "../mwmechanics/alchemy.hpp" -#include "container.hpp" #include "widgets.hpp" +#include "windowbase.hpp" namespace MWGui { - class AlchemyWindow : public WindowBase, public ContainerBase + class ItemView; + class SortFilterItemModel; + + class AlchemyWindow : public WindowBase { public: AlchemyWindow(); virtual void open(); - protected: + private: + ItemView* mItemView; + SortFilterItemModel* mSortModel; + MyGUI::Button* mCreateButton; MyGUI::Button* mCancelButton; @@ -29,17 +35,12 @@ namespace MWGui void onCreateButtonClicked(MyGUI::Widget* _sender); void onIngredientSelected(MyGUI::Widget* _sender); - virtual void onSelectedItemImpl(MWWorld::Ptr item); - virtual std::vector itemsToIgnore(); + void onSelectedItem(int index); void removeIngredient(MyGUI::Widget* ingredient); - virtual void onReferenceUnavailable() { ; } - void update(); - private: - MWMechanics::Alchemy mAlchemy; std::vector mApparatus; diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp new file mode 100644 index 000000000..3212ed701 --- /dev/null +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -0,0 +1,34 @@ +#include "companionitemmodel.hpp" + +#include "../mwmechanics/npcstats.hpp" +#include "../mwworld/class.hpp" + +namespace MWGui +{ + CompanionItemModel::CompanionItemModel(const MWWorld::Ptr &actor) + : InventoryItemModel(actor) + { + } + + void CompanionItemModel::copyItem (const ItemStack& item, size_t count) + { + if (mActor.getTypeName() == typeid(ESM::NPC).name()) + { + MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor); + stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count); + } + + InventoryItemModel::copyItem(item, count); + } + + void CompanionItemModel::removeItem (const ItemStack& item, size_t count) + { + if (mActor.getTypeName() == typeid(ESM::NPC).name()) + { + MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor); + stats.modifyProfit(-MWWorld::Class::get(item.mBase).getValue(item.mBase) * count); + } + + InventoryItemModel::removeItem(item, count); + } +} diff --git a/apps/openmw/mwgui/companionitemmodel.hpp b/apps/openmw/mwgui/companionitemmodel.hpp new file mode 100644 index 000000000..c11e11c32 --- /dev/null +++ b/apps/openmw/mwgui/companionitemmodel.hpp @@ -0,0 +1,22 @@ +#ifndef MWGUI_COMPANION_ITEM_MODEL_H +#define MWGUI_COMPANION_ITEM_MODEL_H + +#include "inventoryitemmodel.hpp" + +namespace MWGui +{ + + /// @brief The companion item model keeps track of the companion's profit by + /// monitoring which items are being added to and removed from the model. + class CompanionItemModel : public InventoryItemModel + { + public: + CompanionItemModel (const MWWorld::Ptr& actor); + + virtual void copyItem (const ItemStack& item, size_t count); + virtual void removeItem (const ItemStack& item, size_t count); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index 22afc42f6..9698608d6 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -7,51 +7,102 @@ #include "../mwmechanics/npcstats.hpp" +#include "../mwworld/class.hpp" + #include "messagebox.hpp" +#include "itemview.hpp" +#include "sortfilteritemmodel.hpp" +#include "companionitemmodel.hpp" +#include "container.hpp" +#include "countdialog.hpp" namespace MWGui { CompanionWindow::CompanionWindow(DragAndDrop *dragAndDrop, MessageBoxManager* manager) - : ContainerBase(dragAndDrop) - , WindowBase("openmw_companion_window.layout") + : WindowBase("openmw_companion_window.layout") + , mDragAndDrop(dragAndDrop) , mMessageBoxManager(manager) + , mSelectedItem(-1) + , mModel(NULL) + , mSortModel(NULL) { - MyGUI::ScrollView* itemView; - MyGUI::Widget* containerWidget; - getWidget(containerWidget, "Items"); - getWidget(itemView, "ItemView"); - setWidgets(containerWidget, itemView); - getWidget(mCloseButton, "CloseButton"); getWidget(mProfitLabel, "ProfitLabel"); getWidget(mEncumbranceBar, "EncumbranceBar"); + getWidget(mItemView, "ItemView"); + mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &CompanionWindow::onBackgroundSelected); + mItemView->eventItemClicked += MyGUI::newDelegate(this, &CompanionWindow::onItemSelected); mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CompanionWindow::onCloseButtonClicked); setCoord(200,0,600,300); } -void CompanionWindow::open(MWWorld::Ptr npc) +void CompanionWindow::onItemSelected(int index) { - openContainer(npc); - setTitle(MWWorld::Class::get(npc).getName(npc)); - drawItems(); - updateEncumbranceBar(); + if (mDragAndDrop->mIsOnDragAndDrop) + { + mDragAndDrop->drop(mModel, mItemView); + updateEncumbranceBar(); + return; + } + + const ItemStack& item = mSortModel->getItem(index); + + MWWorld::Ptr object = item.mBase; + int count = item.mCount; + bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); + if (MyGUI::InputManager::getInstance().isControlPressed()) + count = 1; + + mSelectedItem = mSortModel->mapToSource(index); + + if (count > 1 && !shift) + { + CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); + dialog->open(MWWorld::Class::get(object).getName(object), "#{sTake}", count); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem); + } + else + dragItem (NULL, count); } -void CompanionWindow::notifyItemDragged(MWWorld::Ptr item, int count) +void CompanionWindow::dragItem(MyGUI::Widget* sender, int count) { - if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count); +} + +void CompanionWindow::onBackgroundSelected() +{ + if (mDragAndDrop->mIsOnDragAndDrop) { - MWMechanics::NpcStats& stats = MWWorld::Class::get(mPtr).getNpcStats(mPtr); - stats.modifyProfit(MWWorld::Class::get(item).getValue(item) * count); + mDragAndDrop->drop(mModel, mItemView); + updateEncumbranceBar(); } +} + +void CompanionWindow::open(const MWWorld::Ptr& npc) +{ + mPtr = npc; + setTitle(MWWorld::Class::get(npc).getName(npc)); + updateEncumbranceBar(); + + mModel = new CompanionItemModel(npc); + mSortModel = new SortFilterItemModel(mModel); + mItemView->setModel(mSortModel); +} + +void CompanionWindow::onFrame() +{ updateEncumbranceBar(); } void CompanionWindow::updateEncumbranceBar() { + if (mPtr.isEmpty()) + return; float capacity = MWWorld::Class::get(mPtr).getCapacity(mPtr); float encumbrance = MWWorld::Class::get(mPtr).getEncumbrance(mPtr); mEncumbranceBar->setValue(encumbrance, capacity); @@ -65,11 +116,6 @@ void CompanionWindow::updateEncumbranceBar() } } -void CompanionWindow::onWindowResize(MyGUI::Window* window) -{ - drawItems(); -} - void CompanionWindow::onCloseButtonClicked(MyGUI::Widget* _sender) { if (mPtr.getTypeName() == typeid(ESM::NPC).name() && MWWorld::Class::get(mPtr).getNpcStats(mPtr).getProfit() < 0) diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index 073a77a91..7fdfc069f 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -1,34 +1,47 @@ #ifndef OPENMW_MWGUI_COMPANIONWINDOW_H #define OPENMW_MWGUI_COMPANIONWINDOW_H -#include "container.hpp" #include "widgets.hpp" +#include "windowbase.hpp" +#include "referenceinterface.hpp" namespace MWGui { class MessageBoxManager; + class ItemView; + class DragAndDrop; + class SortFilterItemModel; + class CompanionItemModel; - class CompanionWindow : public ContainerBase, public WindowBase + class CompanionWindow : public WindowBase, public ReferenceInterface { public: CompanionWindow(DragAndDrop* dragAndDrop, MessageBoxManager* manager); - virtual ~CompanionWindow() {} - void open(MWWorld::Ptr npc); + void open(const MWWorld::Ptr& npc); + void onFrame (); - virtual void notifyItemDragged(MWWorld::Ptr item, int count); + private: + ItemView* mItemView; + SortFilterItemModel* mSortModel; + CompanionItemModel* mModel; + size_t mSelectedItem; + + DragAndDrop* mDragAndDrop; - protected: MyGUI::Button* mCloseButton; MyGUI::TextBox* mProfitLabel; Widgets::MWDynamicStat* mEncumbranceBar; MessageBoxManager* mMessageBoxManager; + void onItemSelected(int index); + void onBackgroundSelected(); + void dragItem(MyGUI::Widget* sender, int count); + void onMessageBoxButtonClicked(int button); void updateEncumbranceBar(); - void onWindowResize(MyGUI::Window* window); void onCloseButtonClicked(MyGUI::Widget* _sender); virtual void onReferenceUnavailable(); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 16f253531..ddfd945b3 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -7,681 +7,216 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "countdialog.hpp" #include "tradewindow.hpp" #include "inventorywindow.hpp" +#include "itemview.hpp" +#include "inventoryitemmodel.hpp" +#include "sortfilteritemmodel.hpp" +#include "pickpocketitemmodel.hpp" + namespace { - bool compareType(std::string type1, std::string type2) - { - // this defines the sorting order of types. types that are first in the vector, appear before other types. - std::vector mapping; - mapping.push_back( typeid(ESM::Weapon).name() ); - mapping.push_back( typeid(ESM::Armor).name() ); - mapping.push_back( typeid(ESM::Clothing).name() ); - mapping.push_back( typeid(ESM::Potion).name() ); - mapping.push_back( typeid(ESM::Ingredient).name() ); - mapping.push_back( typeid(ESM::Apparatus).name() ); - mapping.push_back( typeid(ESM::Book).name() ); - mapping.push_back( typeid(ESM::Light).name() ); - mapping.push_back( typeid(ESM::Miscellaneous).name() ); - mapping.push_back( typeid(ESM::Lockpick).name() ); - mapping.push_back( typeid(ESM::Repair).name() ); - mapping.push_back( typeid(ESM::Probe).name() ); - - assert( std::find(mapping.begin(), mapping.end(), type1) != mapping.end() ); - assert( std::find(mapping.begin(), mapping.end(), type2) != mapping.end() ); - - return std::find(mapping.begin(), mapping.end(), type1) < std::find(mapping.begin(), mapping.end(), type2); - } - - bool sortItems(MWWorld::Ptr left, MWWorld::Ptr right) + std::string getCountString(const int count) { - if (left.getTypeName() == right.getTypeName()) - { - int cmp = MWWorld::Class::get(left).getName(left).compare( - MWWorld::Class::get(right).getName(right)); - return cmp < 0; - } + if (count == 1) + return ""; + if (count > 9999) + return boost::lexical_cast(int(count/1000.f)) + "k"; else - { - return compareType(left.getTypeName(), right.getTypeName()); - } - } - - bool isChargedSoulstone (MWWorld::Ptr ptr) - { - if (ptr.getTypeName() != typeid(ESM::Miscellaneous).name()) - return false; - MWWorld::LiveCellRef *ref = - ptr.get(); - - return (ref->mRef.mSoul != ""); + return boost::lexical_cast(count); } } namespace MWGui { - ContainerBase::ContainerBase(DragAndDrop* dragAndDrop) - : mDragAndDrop(dragAndDrop) - , mFilter(ContainerBase::Filter_All) - , mDisplayEquippedItems(true) - , mHighlightEquippedItems(true) - { - } - - void ContainerBase::setWidgets(MyGUI::Widget* containerWidget, MyGUI::ScrollView* itemView) + void DragAndDrop::startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count) { - mContainerWidget = containerWidget; - mItemView = itemView; - - mContainerWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerBase::onContainerClicked); - mContainerWidget->eventMouseWheel += MyGUI::newDelegate(this, &ContainerWindow::onMouseWheel); - } - - ContainerBase::~ContainerBase() - { - } - - void ContainerBase::onSelectedItem(MyGUI::Widget* _sender) - { - mSelectedItem = _sender; - - if (mDragAndDrop && !isTrading()) - { - if(!mDragAndDrop->mIsOnDragAndDrop) - { - MWWorld::Ptr object = (*_sender->getUserData()); - int count = object.getRefData().getCount(); - - if (MyGUI::InputManager::getInstance().isShiftPressed() || count == 1) - { - startDragItem(_sender, count); - } - else if (MyGUI::InputManager::getInstance().isControlPressed()) - { - startDragItem(_sender, 1); - } - else - { - std::string message = "#{sTake}"; - CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); - dialog->open(MWWorld::Class::get(object).getName(object), message, count); - dialog->eventOkClicked.clear(); - dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerBase::startDragItem); - } - } - else - onContainerClicked(mContainerWidget); - } - else if (isTrading()) - { - MWWorld::Ptr object = (*_sender->getUserData()); - int count = object.getRefData().getCount(); - - if (isInventory()) - { - // the player is trying to sell an item, check if the merchant accepts it - if (!MWBase::Environment::get().getWindowManager()->getTradeWindow()->npcAcceptsItem(object)) - { - // user notification "i don't buy this item" - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sBarterDialog4}"); - return; - } - } - - bool buying = isTradeWindow(); // buying or selling? - std::string message = buying ? "#{sQuanityMenuMessage02}" : "#{sQuanityMenuMessage01}"; - - if (std::find(mBoughtItems.begin(), mBoughtItems.end(), object) != mBoughtItems.end()) - { - if (MyGUI::InputManager::getInstance().isShiftPressed() || count == 1) - { - sellAlreadyBoughtItem(NULL, count); - } - else if (MyGUI::InputManager::getInstance().isControlPressed()) - { - sellAlreadyBoughtItem(NULL, 1); - } - else - { - CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); - dialog->open(MWWorld::Class::get(object).getName(object), message, count); - dialog->eventOkClicked.clear(); - dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerBase::sellAlreadyBoughtItem); - } - } - else - { - if (MyGUI::InputManager::getInstance().isShiftPressed() || count == 1) - { - sellItem(NULL, count); - } - else if (MyGUI::InputManager::getInstance().isControlPressed()) - { - sellItem(NULL, 1); - } - else - { - CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); - dialog->open(MWWorld::Class::get(object).getName(object), message, count); - dialog->eventOkClicked.clear(); - dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerBase::sellItem); - } - } - } - else - { - onSelectedItemImpl(*_sender->getUserData()); - } - } - - void ContainerBase::sellAlreadyBoughtItem(MyGUI::Widget* _sender, int count) - { - MWWorld::Ptr object = *mSelectedItem->getUserData(); - - if (isInventory()) - { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count, true); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->drawItems(); - } - else - { - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->addItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count, true); - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); - } - - std::string sound = MWWorld::Class::get(object).getUpSoundId(object); + mItem = sourceModel->getItem(index); + mDraggedCount = count; + mSourceModel = sourceModel; + mSourceView = sourceView; + mSourceSortModel = sortModel; + mIsOnDragAndDrop = true; + mDragAndDropWidget->setVisible(true); + + std::string sound = MWWorld::Class::get(mItem.mBase).getUpSoundId(mItem.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - drawItems(); - } - - void ContainerBase::sellItem(MyGUI::Widget* _sender, int count) - { - MWWorld::Ptr object = *mSelectedItem->getUserData(); - - if (isInventory()) + if (mSourceSortModel) { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addBarteredItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count, false); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->drawItems(); + mSourceSortModel->clearDragItems(); + mSourceSortModel->addDragItem(mItem.mBase, count); } - else - { - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->addBarteredItem(object, count); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count, false); - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); - } - - std::string sound = MWWorld::Class::get(object).getUpSoundId(object); - MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - - drawItems(); - } - void ContainerBase::startDragItem(MyGUI::Widget* _sender, int count) - { - mDragAndDrop->mIsOnDragAndDrop = true; - mSelectedItem->detachFromWidget(); - mSelectedItem->attachToWidget(mDragAndDrop->mDragAndDropWidget); - - MWWorld::Ptr object = *mSelectedItem->getUserData(); - _unequipItem(object); - - mDragAndDrop->mDraggedCount = count; - - mDragAndDrop->mDraggedFrom = this; - - std::string sound = MWWorld::Class::get(object).getUpSoundId(object); - MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - - mDragAndDrop->mDraggedWidget = mSelectedItem; - static_cast(mSelectedItem)->setImageTexture(""); // remove the background texture (not visible during drag) - static_cast(mSelectedItem->getChildAt(0)->getChildAt(0))->setCaption( - getCountString(mDragAndDrop->mDraggedCount)); - - drawItems(); + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(mItem.mBase).getInventoryIcon(mItem.mBase); + MyGUI::ImageBox* baseWidget = mDragAndDropWidget->createWidget + ("ImageBox", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); + mDraggedWidget = baseWidget; + MyGUI::ImageBox* image = baseWidget->createWidget("ImageBox", + MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + image->setImageTexture(path); + image->setNeedMouseFocus(false); + + // text widget that shows item count + MyGUI::TextBox* text = image->createWidget("SandBrightText", + MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); + text->setTextAlign(MyGUI::Align::Right); + text->setNeedMouseFocus(false); + text->setTextShadow(true); + text->setTextShadowColour(MyGUI::Colour(0,0,0)); + text->setCaption(getCountString(count)); + + sourceView->update(); MWBase::Environment::get().getWindowManager()->setDragDrop(true); } - void ContainerBase::onContainerClicked(MyGUI::Widget* _sender) + void DragAndDrop::drop(ItemModel *targetModel, ItemView *targetView) { - if (mDragAndDrop == NULL) return; - - if(mDragAndDrop->mIsOnDragAndDrop) //drop item here - { - MWWorld::Ptr object = *mDragAndDrop->mDraggedWidget->getUserData(); - MWWorld::ContainerStore& containerStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); - - if (mDragAndDrop->mDraggedFrom != this) - { - assert(object.getContainerStore() && "Item is not in a container!"); - - // check the container's Organic flag (if this is a container). container with Organic flag doesn't allow putting items inside - if (mPtr.getTypeName() == typeid(ESM::Container).name()) - { - MWWorld::LiveCellRef* ref = mPtr.get(); - if (ref->mBase->mFlags & ESM::Container::Organic) - { - // user notification - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sContentsMessage2}"); - - return; - } - } - - int origCount = object.getRefData().getCount(); - - // check that we don't exceed the allowed weight (only for containers, not for inventory) - if (!isInventory()) - { - float capacity = MWWorld::Class::get(mPtr).getCapacity(mPtr); - - // try adding the item, and if weight is exceeded, just remove it again. - object.getRefData().setCount(mDragAndDrop->mDraggedCount); - MWWorld::ContainerStoreIterator it = containerStore.add(object); - - float curWeight = MWWorld::Class::get(mPtr).getEncumbrance(mPtr); - if (curWeight > capacity) - { - it->getRefData().setCount(0); - object.getRefData().setCount(origCount); - // user notification - MWBase::Environment::get().getWindowManager()-> - messageBox("#{sContentsMessage3}"); - - return; - } - else - { - object.getRefData().setCount(origCount - mDragAndDrop->mDraggedCount); - } - } - else - { - object.getRefData().setCount (mDragAndDrop->mDraggedCount); - containerStore.add(object); - object.getRefData().setCount (origCount - mDragAndDrop->mDraggedCount); - } - } + std::string sound = MWWorld::Class::get(mItem.mBase).getDownSoundId(mItem.mBase); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - mDragAndDrop->mIsOnDragAndDrop = false; - MyGUI::Gui::getInstance().destroyWidget(mDragAndDrop->mDraggedWidget); - drawItems(); - mDragAndDrop->mDraggedFrom->drawItems(); + mDragAndDropWidget->setVisible(false); - mDragAndDrop->mDraggedFrom->notifyItemDragged(object, -mDragAndDrop->mDraggedCount); - notifyItemDragged(object, mDragAndDrop->mDraggedCount); + targetModel->copyItem(mItem, mDraggedCount); + mSourceModel->removeItem(mItem, mDraggedCount); - MWBase::Environment::get().getWindowManager()->setDragDrop(false); + mSourceModel->update(); - std::string sound = MWWorld::Class::get(object).getDownSoundId(object); - MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - } + finish(); + targetView->update(); } - void ContainerBase::onMouseWheel(MyGUI::Widget* _sender, int _rel) + void DragAndDrop::finish() { - if (mItemView->getViewOffset().left + _rel*0.3 > 0) - mItemView->setViewOffset(MyGUI::IntPoint(0, 0)); - else - mItemView->setViewOffset(MyGUI::IntPoint(mItemView->getViewOffset().left + _rel*0.3, 0)); - } + mIsOnDragAndDrop = false; + mSourceSortModel->clearDragItems(); - void ContainerBase::setFilter(int filter) - { - mFilter = filter; - drawItems(); + MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget); + mDraggedWidget = 0; + MWBase::Environment::get().getWindowManager()->setDragDrop(false); } - void ContainerBase::openContainer(MWWorld::Ptr container) - { - mPtr = container; - } - void ContainerBase::drawItems() + ContainerWindow::ContainerWindow(DragAndDrop* dragAndDrop) + : WindowBase("openmw_container_window.layout") + , mDragAndDrop(dragAndDrop) + , mSelectedItem(-1) + , mModel(NULL) + , mSortModel(NULL) { - while (mContainerWidget->getChildCount()) - { - MyGUI::Gui::getInstance().destroyWidget(mContainerWidget->getChildAt(0)); - } - MWWorld::ContainerStore& containerStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); - - int x = 0; - int y = 0; - int maxHeight = mItemView->getSize().height - 58; - - bool onlyMagic = false; - bool noMagic = false; - int categories = 0; - if (mFilter & Filter_All) - categories |= MWWorld::ContainerStore::Type_All; - if (mFilter & Filter_Weapon) - categories |= MWWorld::ContainerStore::Type_Weapon; - if (mFilter & Filter_Apparel) - categories |= MWWorld::ContainerStore::Type_Clothing | MWWorld::ContainerStore::Type_Armor; - if (mFilter & Filter_Magic) - { - categories |= MWWorld::ContainerStore::Type_Clothing | MWWorld::ContainerStore::Type_Armor - | MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Book - | MWWorld::ContainerStore::Type_Potion; - onlyMagic = true; - } - if (mFilter & Filter_Misc) - { - categories |= MWWorld::ContainerStore::Type_Miscellaneous | MWWorld::ContainerStore::Type_Book - | MWWorld::ContainerStore::Type_Ingredient | MWWorld::ContainerStore::Type_Repair - | MWWorld::ContainerStore::Type_Lockpick | MWWorld::ContainerStore::Type_Light - | MWWorld::ContainerStore::Type_Apparatus | MWWorld::ContainerStore::Type_Probe; - } - if (mFilter & Filter_Ingredients) - categories |= MWWorld::ContainerStore::Type_Ingredient; - if (mFilter & Filter_ChargedSoulstones) - categories |= MWWorld::ContainerStore::Type_Miscellaneous; - if (mFilter & Filter_NoMagic) - noMagic = true; + getWidget(mDisposeCorpseButton, "DisposeCorpseButton"); + getWidget(mTakeButton, "TakeButton"); + getWidget(mCloseButton, "CloseButton"); - /// \todo performance improvement: don't create/destroy all the widgets everytime the container window changes size, only reposition them + getWidget(mItemView, "ItemView"); + mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &ContainerWindow::onBackgroundSelected); + mItemView->eventItemClicked += MyGUI::newDelegate(this, &ContainerWindow::onItemSelected); - std::vector< std::pair > items; + mDisposeCorpseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onDisposeCorpseButtonClicked); + mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onCloseButtonClicked); + mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked); - std::vector equippedItems = getEquippedItems(); + setCoord(200,0,600,300); + } - // add bought items (always at the beginning) - std::vector boughtItems; - for (MWWorld::ContainerStoreIterator it (mBoughtItems.begin()); it!=mBoughtItems.end(); ++it) + void ContainerWindow::onItemSelected(int index) + { + if (mDragAndDrop->mIsOnDragAndDrop) { - boughtItems.push_back(*it); + if (!dynamic_cast(mModel)) + dropItem(); + return; } - std::sort(boughtItems.begin(), boughtItems.end(), sortItems); - for (std::vector::iterator it=boughtItems.begin(); - it != boughtItems.end(); ++it) - { - items.push_back( std::make_pair(*it, ItemState_Barter) ); - } + const ItemStack& item = mSortModel->getItem(index); - // filter out the equipped items of categories we don't want - std::vector unwantedItems = equippedItems; - for (MWWorld::ContainerStoreIterator iter (containerStore.begin(categories)); iter!=containerStore.end(); ++iter) - { - std::vector::iterator found = std::find(unwantedItems.begin(), unwantedItems.end(), *iter); - if (found != unwantedItems.end()) - { - unwantedItems.erase(found); - } - } - // now erase everything that's still in unwantedItems. - for (std::vector::iterator it=unwantedItems.begin(); - it != unwantedItems.end(); ++it) - { - std::vector::iterator found = std::find(equippedItems.begin(), equippedItems.end(), *it); - assert(found != equippedItems.end()); - equippedItems.erase(found); - } - // and add the items that are left (= have the correct category) - if (mDisplayEquippedItems && mHighlightEquippedItems) - { - for (std::vector::const_iterator it=equippedItems.begin(); - it != equippedItems.end(); ++it) - { - items.push_back( std::make_pair(*it, ItemState_Equipped) ); - } - } + MWWorld::Ptr object = item.mBase; + int count = item.mCount; + bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); + if (MyGUI::InputManager::getInstance().isControlPressed()) + count = 1; - std::vector ignoreItems = itemsToIgnore(); + mSelectedItem = mSortModel->mapToSource(index); - // now add the regular items - std::vector regularItems; - for (MWWorld::ContainerStoreIterator iter (containerStore.begin(categories)); iter!=containerStore.end(); ++iter) + if (count > 1 && !shift) { - if ( (std::find(equippedItems.begin(), equippedItems.end(), *iter) == equippedItems.end() - || (!mHighlightEquippedItems && mDisplayEquippedItems)) - && std::find(ignoreItems.begin(), ignoreItems.end(), *iter) == ignoreItems.end() - && std::find(mBoughtItems.begin(), mBoughtItems.end(), *iter) == mBoughtItems.end()) - regularItems.push_back(*iter); + CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); + dialog->open(MWWorld::Class::get(object).getName(object), "#{sTake}", count); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::dragItem); } - - // sort them and add - std::sort(regularItems.begin(), regularItems.end(), sortItems); - for (std::vector::const_iterator it=regularItems.begin(); it!=regularItems.end(); ++it) - { - items.push_back( std::make_pair(*it, ItemState_Normal) ); - } - - for (std::vector< std::pair >::const_iterator it=items.begin(); - it != items.end(); ++it) - { - const MWWorld::Ptr* iter = &((*it).first); - - - if (onlyMagic - && it->second != ItemState_Barter - && MWWorld::Class::get(*iter).getEnchantment(*iter) == "" - && iter->getTypeName() != typeid(ESM::Potion).name()) - continue; - - if (noMagic - && it->second != ItemState_Barter - && (MWWorld::Class::get(*iter).getEnchantment(*iter) != "" - || iter->getTypeName() == typeid(ESM::Potion).name())) - continue; - - if ( (mFilter & Filter_ChargedSoulstones) - && !isChargedSoulstone(*iter)) - continue; - - int displayCount = iter->getRefData().getCount(); - if (mDragAndDrop != NULL && mDragAndDrop->mIsOnDragAndDrop && *iter == *mDragAndDrop->mDraggedWidget->getUserData()) - { - displayCount -= mDragAndDrop->mDraggedCount; - } - if(displayCount > 0) - { - std::string path = std::string("icons\\"); - path += MWWorld::Class::get(*iter).getInventoryIcon(*iter); - - // background widget (for the "equipped" frame and magic item background image) - bool isMagic = (MWWorld::Class::get(*iter).getEnchantment(*iter) != ""); - MyGUI::ImageBox* backgroundWidget = mContainerWidget->createWidget("ImageBox", MyGUI::IntCoord(x, y, 42, 42), MyGUI::Align::Default); - backgroundWidget->setUserString("ToolTipType", "ItemPtr"); - backgroundWidget->setUserData(*iter); - - std::string backgroundTex = "textures\\menu_icon"; - if (isMagic) - backgroundTex += "_magic"; - if (it->second == ItemState_Normal) - { - if (!isMagic) - backgroundTex = ""; - } - else if (it->second == ItemState_Equipped) - { - backgroundTex += "_equip"; - } - else if (it->second == ItemState_Barter) - { - backgroundTex += "_barter"; - } - if (backgroundTex != "") - backgroundTex += ".dds"; - - backgroundWidget->setImageTexture(backgroundTex); - if (it->second == ItemState_Barter && !isMagic) - backgroundWidget->setProperty("ImageCoord", "2 2 42 42"); - else - backgroundWidget->setProperty("ImageCoord", "0 0 42 42"); - backgroundWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerBase::onSelectedItem); - backgroundWidget->eventMouseWheel += MyGUI::newDelegate(this, &ContainerBase::onMouseWheel); - - // image - MyGUI::ImageBox* image = backgroundWidget->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - image->setImageTexture(path); - image->setNeedMouseFocus(false); - - // text widget that shows item count - MyGUI::TextBox* text = image->createWidget("SandBrightText", MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); - text->setTextAlign(MyGUI::Align::Right); - text->setNeedMouseFocus(false); - text->setTextShadow(true); - text->setTextShadowColour(MyGUI::Colour(0,0,0)); - text->setCaption(getCountString(displayCount)); - - y += 42; - if (y > maxHeight) - { - x += 42; - y = 0; - } - - } - } - - MyGUI::IntSize size = MyGUI::IntSize(std::max(mItemView->getSize().width, x+42), mItemView->getSize().height); - mItemView->setCanvasSize(size); - mContainerWidget->setSize(size); - - notifyContentChanged(); - } - - std::string ContainerBase::getCountString(const int count) - { - if (count == 1) - return ""; - if (count > 9999) - return boost::lexical_cast(int(count/1000.f)) + "k"; else - return boost::lexical_cast(count); + dragItem (NULL, count); } - void ContainerBase::addBarteredItem(MWWorld::Ptr item, int count) + void ContainerWindow::dragItem(MyGUI::Widget* sender, int count) { - int origCount = item.getRefData().getCount(); - item.getRefData().setCount(count); - MWWorld::ContainerStoreIterator it = mBoughtItems.add(item); - item.getRefData().setCount(origCount - count); + mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count); } - void ContainerBase::addItem(MWWorld::Ptr item, int count) + void ContainerWindow::dropItem() { - MWWorld::ContainerStore& containerStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); - - int origCount = item.getRefData().getCount(); - - item.getRefData().setCount(count); - MWWorld::ContainerStoreIterator it = containerStore.add(item); - - item.getRefData().setCount(origCount - count); - } - - void ContainerBase::transferBoughtItems() - { - MWWorld::ContainerStore& containerStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); - - for (MWWorld::ContainerStoreIterator it(mBoughtItems.begin()); it != mBoughtItems.end(); ++it) + if (mPtr.getTypeName() == typeid(ESM::Container).name()) { - containerStore.add(*it); - } - } - - void ContainerBase::returnBoughtItems(MWWorld::ContainerStore& store) - { - for (MWWorld::ContainerStoreIterator it(mBoughtItems.begin()); it != mBoughtItems.end(); ++it) - { - store.add(*it); - } - } - - std::vector ContainerBase::getEquippedItems() - { - if (mPtr.getTypeName() != typeid(ESM::NPC).name()) - return std::vector(); - - MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - - std::vector items; + // check that we don't exceed container capacity + MWWorld::Ptr item = mDragAndDrop->mItem.mBase; + float weight = MWWorld::Class::get(item).getWeight(item) * mDragAndDrop->mDraggedCount; + if (MWWorld::Class::get(mPtr).getCapacity(mPtr) < MWWorld::Class::get(mPtr).getEncumbrance(mPtr) + weight) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); + return; + } - for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); - if (it != invStore.end()) + // check container organic flag + MWWorld::LiveCellRef* ref = mPtr.get(); + if (ref->mBase->mFlags & ESM::Container::Organic) { - items.push_back(*it); + MWBase::Environment::get().getWindowManager()-> + messageBox("#{sContentsMessage2}"); + return; } } - return items; - } - - MWWorld::ContainerStore& ContainerBase::getContainerStore() - { - MWWorld::ContainerStore& store = MWWorld::Class::get(mPtr).getContainerStore(mPtr); - return store; - } - - // ------------------------------------------------------------------------------------------------ - - ContainerWindow::ContainerWindow(DragAndDrop* dragAndDrop) - : ContainerBase(dragAndDrop) - , WindowBase("openmw_container_window.layout") - { - getWidget(mDisposeCorpseButton, "DisposeCorpseButton"); - getWidget(mTakeButton, "TakeButton"); - getWidget(mCloseButton, "CloseButton"); - - MyGUI::ScrollView* itemView; - MyGUI::Widget* containerWidget; - getWidget(containerWidget, "Items"); - getWidget(itemView, "ItemView"); - setWidgets(containerWidget, itemView); - - mDisposeCorpseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onDisposeCorpseButtonClicked); - mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onCloseButtonClicked); - mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked); - - static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &ContainerWindow::onWindowResize); - - setCoord(200,0,600,300); + mDragAndDrop->drop(mModel, mItemView); } - ContainerWindow::~ContainerWindow() + void ContainerWindow::onBackgroundSelected() { + if (mDragAndDrop->mIsOnDragAndDrop && !dynamic_cast(mModel)) + dropItem(); } - void ContainerWindow::onWindowResize(MyGUI::Window* window) + void ContainerWindow::open(const MWWorld::Ptr& container, bool loot) { - drawItems(); - } + mPtr = container; - void ContainerWindow::open(MWWorld::Ptr container, bool loot) - { - mDisplayEquippedItems = true; - mHighlightEquippedItems = false; if (container.getTypeName() == typeid(ESM::NPC).name() && !loot) { // we are stealing stuff - mDisplayEquippedItems = false; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + mModel = new PickpocketItemModel(player, new InventoryItemModel(container)); } + else + mModel = new InventoryItemModel(container); mDisposeCorpseButton->setVisible(loot); - openContainer(container); setTitle(MWWorld::Class::get(container).getName(container)); - drawItems(); + + mSortModel = new SortFilterItemModel(mModel); + + mItemView->setModel (mSortModel); } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) @@ -697,32 +232,19 @@ namespace MWGui if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop) { // transfer everything into the player's inventory - MWWorld::ContainerStore& containerStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); - - std::vector equippedItems = getEquippedItems(); - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWWorld::ContainerStore& playerStore = MWWorld::Class::get(player).getContainerStore(player); - - int i=0; - for (MWWorld::ContainerStoreIterator iter (containerStore.begin()); iter!=containerStore.end(); ++iter) + ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); + for (size_t i=0; igetItemCount(); ++i) { - if (std::find(equippedItems.begin(), equippedItems.end(), *iter) != equippedItems.end() - && !mDisplayEquippedItems) - continue; - - playerStore.add(*iter); - if (i==0) { // play the sound of the first object - std::string sound = MWWorld::Class::get(*iter).getUpSoundId(*iter); + MWWorld::Ptr item = mModel->getItem(i).mBase; + std::string sound = MWWorld::Class::get(item).getUpSoundId(item); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); } - iter->getRefData().setCount(0); - - ++i; + playerModel->copyItem(mModel->getItem(i), mModel->getItem(i).mCount); + mModel->removeItem(mModel->getItem(i), mModel->getItem(i).mCount); } MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); @@ -735,10 +257,10 @@ namespace MWGui { onTakeAllButtonClicked(mTakeButton); - /// \todo I don't think this is the correct flag to check - if (MWWorld::Class::get(mPtr).isEssential(mPtr)) - MWBase::Environment::get().getWindowManager()->messageBox("#{sDisposeCorpseFail}"); - else + /// \todo if corpse is non-disposable: messagebox #{sDisposeCorpseFail} + //if () + // MWBase::Environment::get().getWindowManager()->messageBox("#{sDisposeCorpseFail}"); + //else MWBase::Environment::get().getWorld()->deleteObject(mPtr); mPtr = MWWorld::Ptr(); diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 8440c6444..243f77aa5 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -4,9 +4,7 @@ #include "windowbase.hpp" #include "referenceinterface.hpp" -#include "../mwclass/container.hpp" -#include "../mwworld/containerstore.hpp" - +#include "itemmodel.hpp" namespace MWWorld { @@ -23,7 +21,8 @@ namespace MWGui { class WindowManager; class ContainerWindow; - class ContainerBase; + class ItemView; + class SortFilterItemModel; } @@ -35,114 +34,41 @@ namespace MWGui bool mIsOnDragAndDrop; MyGUI::Widget* mDraggedWidget; MyGUI::Widget* mDragAndDropWidget; - ContainerBase* mDraggedFrom; + ItemModel* mSourceModel; + ItemView* mSourceView; + SortFilterItemModel* mSourceSortModel; + ItemStack mItem; int mDraggedCount; - }; - - class ContainerBase : public ReferenceInterface - { - public: - ContainerBase(DragAndDrop* dragAndDrop); - virtual ~ContainerBase(); - - // basic types (inclusive) - static const int Filter_All = (1<<0); - static const int Filter_Weapon = (1<<1); - static const int Filter_Apparel = (1<<2); - static const int Filter_Ingredients = (1<<3); - static const int Filter_Misc = (1<<4); - - // special filtering (exclusive) - static const int Filter_Magic = (1<<5); - static const int Filter_NoMagic = (1<<6); - static const int Filter_ChargedSoulstones = (1<<7); - - enum ItemState - { - ItemState_Normal = 0x01, - ItemState_Equipped = 0x02, - ItemState_Barter = 0x03 - }; - - void setWidgets(MyGUI::Widget* containerWidget, MyGUI::ScrollView* itemView); ///< only call once - - void addBarteredItem(MWWorld::Ptr item, int count); - void addItem(MWWorld::Ptr item, int count); - - void transferBoughtItems(); ///< transfer bought items into the inventory - void returnBoughtItems(MWWorld::ContainerStore& store); ///< return bought items into the specified ContainerStore - - MWWorld::ContainerStore& getContainerStore(); - MWWorld::ContainerStore& getBoughtItems() { return mBoughtItems; } - - void openContainer(MWWorld::Ptr container); - void setFilter(int filter); ///< set category filter - void drawItems(); - - /// fired when an item was moved by drag&drop. \n - /// if it was removed from this container, count will be negative. - virtual void notifyItemDragged(MWWorld::Ptr item, int count) {} - - protected: - bool mDisplayEquippedItems; - bool mHighlightEquippedItems; - - MyGUI::ScrollView* mItemView; - MyGUI::Widget* mContainerWidget; - MyGUI::Widget* mSelectedItem; + void startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count); + void drop (ItemModel* targetModel, ItemView* targetView); - DragAndDrop* mDragAndDrop; - - int mFilter; - - // bought items are put in a separate ContainerStore so that they don't stack with other (not bought) items. - MWWorld::ContainerStore mBoughtItems; - - void onSelectedItem(MyGUI::Widget* _sender); - void onContainerClicked(MyGUI::Widget* _sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); - - /// start dragging an item (drag & drop) - void startDragItem(MyGUI::Widget* _sender, int count); - - /// sell an item from this container - void sellItem(MyGUI::Widget* _sender, int count); - - /// sell an item from this container, that was previously just bought - void sellAlreadyBoughtItem(MyGUI::Widget* _sender, int count); - - std::string getCountString(const int count); - - virtual bool isTradeWindow() { return false; } - virtual bool isInventory() { return false; } - virtual std::vector getEquippedItems(); - virtual void _unequipItem(MWWorld::Ptr item) { ; } - - virtual bool isTrading() { return false; } - - virtual void onSelectedItemImpl(MWWorld::Ptr item) { ; } - - virtual std::vector itemsToIgnore() { return std::vector(); } - - virtual void notifyContentChanged() { ; } + void finish(); }; - class ContainerWindow : public ContainerBase, public WindowBase + class ContainerWindow : public WindowBase, public ReferenceInterface { public: ContainerWindow(DragAndDrop* dragAndDrop); - virtual ~ContainerWindow(); + void open(const MWWorld::Ptr& container, bool loot=false); + + private: + DragAndDrop* mDragAndDrop; - void open(MWWorld::Ptr container, bool loot=false); + MWGui::ItemView* mItemView; + SortFilterItemModel* mSortModel; + ItemModel* mModel; + size_t mSelectedItem; - protected: MyGUI::Button* mDisposeCorpseButton; MyGUI::Button* mTakeButton; MyGUI::Button* mCloseButton; - void onWindowResize(MyGUI::Window* window); + void onItemSelected(int index); + void onBackgroundSelected(); + void dragItem(MyGUI::Widget* sender, int count); + void dropItem(); void onCloseButtonClicked(MyGUI::Widget* _sender); void onTakeAllButtonClicked(MyGUI::Widget* _sender); void onDisposeCorpseButtonClicked(MyGUI::Widget* sender); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp new file mode 100644 index 000000000..79351c6ca --- /dev/null +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -0,0 +1,113 @@ +#include "containeritemmodel.hpp" + +#include "../mwworld/containerstore.hpp" +#include "../mwworld/class.hpp" + +namespace MWGui +{ + +ContainerItemModel::ContainerItemModel(const std::vector& itemSources) + : mItemSources(itemSources) +{ + assert (mItemSources.size()); +} + +ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) +{ + mItemSources.push_back(source); +} + +ItemStack ContainerItemModel::getItem (ModelIndex index) +{ + if (index < 0) + throw std::runtime_error("Invalid index supplied"); + if (mItems.size() <= static_cast(index)) + throw std::runtime_error("Item index out of range"); + return mItems[index]; +} + +size_t ContainerItemModel::getItemCount() +{ + return mItems.size(); +} + +ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item) +{ + size_t i = 0; + for (std::vector::iterator it = mItems.begin(); it != mItems.end(); ++it) + { + if (*it == item) + return i; + ++i; + } + return -1; +} + +void ContainerItemModel::copyItem (const ItemStack& item, size_t count) +{ + const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1]; + int origCount = item.mBase.getRefData().getCount(); + item.mBase.getRefData().setCount(count); + MWWorld::ContainerStoreIterator it = MWWorld::Class::get(source).getContainerStore(source).add(item.mBase); + if (*it != item.mBase) + item.mBase.getRefData().setCount(origCount); + else + item.mBase.getRefData().setCount(origCount + count); // item copied onto itself +} + +void ContainerItemModel::removeItem (const ItemStack& item, size_t count) +{ + int toRemove = count; + + for (std::vector::iterator source = mItemSources.begin(); source != mItemSources.end(); ++source) + { + MWWorld::ContainerStore& store = MWWorld::Class::get(*source).getContainerStore(*source); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure + if (*it == item.mBase || (store.stacks(*it, item.mBase) && item.mBase.getContainerStore()->stacks(*it, item.mBase))) + { + int refCount = it->getRefData().getCount(); + it->getRefData().setCount(std::max(0, refCount - toRemove)); + toRemove -= refCount; + if (toRemove <= 0) + return; + } + } + } + throw std::runtime_error("Not enough items to remove could be found"); +} + +void ContainerItemModel::update() +{ + mItems.clear(); + for (std::vector::iterator source = mItemSources.begin(); source != mItemSources.end(); ++source) + { + MWWorld::ContainerStore& store = MWWorld::Class::get(*source).getContainerStore(*source); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + std::vector::iterator itemStack = mItems.begin(); + for (; itemStack != mItems.end(); ++itemStack) + { + // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure + if (store.stacks(itemStack->mBase, *it) && it->getContainerStore()->stacks(itemStack->mBase, *it)) + { + // we already have an item stack of this kind, add to it + itemStack->mCount += it->getRefData().getCount(); + break; + } + } + + if (itemStack == mItems.end()) + { + // no stack yet, create one + ItemStack newItem (*it, this, it->getRefData().getCount()); + mItems.push_back(newItem); + } + } + } +} + +} diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp new file mode 100644 index 000000000..2f4c708ba --- /dev/null +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -0,0 +1,37 @@ +#ifndef MWGUI_CONTAINER_ITEM_MODEL_H +#define MWGUI_CONTAINER_ITEM_MODEL_H + +#include "itemmodel.hpp" + +namespace MWGui +{ + + /// @brief The container item model supports multiple item sources, which are needed for + /// making NPCs sell items from containers owned by them + class ContainerItemModel : public ItemModel + { + public: + ContainerItemModel (const std::vector& itemSources); + ///< @note The order of elements \a itemSources matters here. The first element has the highest priority for removal, + /// while the last element will be used to add new items to. + + ContainerItemModel (const MWWorld::Ptr& source); + + virtual ItemStack getItem (ModelIndex index); + virtual ModelIndex getIndex (ItemStack item); + virtual size_t getItemCount(); + + virtual void copyItem (const ItemStack& item, size_t count); + virtual void removeItem (const ItemStack& item, size_t count); + + virtual void update(); + + private: + std::vector mItemSources; + + std::vector mItems; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index d5a17d3cc..0e4ca9ab6 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -9,6 +9,8 @@ #include "../mwmechanics/npcstats.hpp" +#include "../mwworld/class.hpp" + #include "../mwdialogue/dialoguemanagerimp.hpp" #include "widgets.hpp" diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 6f7cfbf40..799a89ab5 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -6,11 +6,14 @@ #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/class.hpp" #include "itemselection.hpp" #include "container.hpp" #include "inventorywindow.hpp" +#include "sortfilteritemmodel.hpp" + namespace MWGui { @@ -139,13 +142,12 @@ namespace MWGui void EnchantingDialog::onSelectItem(MyGUI::Widget *sender) { delete mItemSelectionDialog; - mItemSelectionDialog = new ItemSelectionDialog("#{sEnchantItems}", - ContainerBase::Filter_Apparel|ContainerBase::Filter_Weapon|ContainerBase::Filter_NoMagic); + mItemSelectionDialog = new ItemSelectionDialog("#{sEnchantItems}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onItemSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onItemCancel); mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - mItemSelectionDialog->drawItems (); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyEnchantable); } void EnchantingDialog::onItemSelected(MWWorld::Ptr item) @@ -227,13 +229,12 @@ namespace MWGui void EnchantingDialog::onSelectSoul(MyGUI::Widget *sender) { delete mItemSelectionDialog; - mItemSelectionDialog = new ItemSelectionDialog("#{sSoulGemsWithSouls}", - ContainerBase::Filter_Misc|ContainerBase::Filter_ChargedSoulstones); + mItemSelectionDialog = new ItemSelectionDialog("#{sSoulGemsWithSouls}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onSoulSelected); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onSoulCancel); mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - mItemSelectionDialog->drawItems (); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); //MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}"); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 59e00bf69..469c45936 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -7,10 +7,13 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" #include "inventorywindow.hpp" #include "console.hpp" #include "spellicons.hpp" +#include "itemmodel.hpp" +#include "container.hpp" namespace MWGui { @@ -195,7 +198,7 @@ namespace MWGui if (mDragAndDrop->mIsOnDragAndDrop) { // drop item into the gameworld - MWWorld::Ptr object = *mDragAndDrop->mDraggedWidget->getUserData(); + MWWorld::Ptr object = mDragAndDrop->mItem.mBase; MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -217,16 +220,11 @@ namespace MWGui std::string sound = MWWorld::Class::get(object).getDownSoundId(object); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - // remove object from the container it was coming from - object.getRefData().setCount(origCount - mDragAndDrop->mDraggedCount); - - mDragAndDrop->mIsOnDragAndDrop = false; - MyGUI::Gui::getInstance().destroyWidget(mDragAndDrop->mDraggedWidget); - mDragAndDrop->mDraggedWidget = 0; + object.getRefData().setCount(origCount); - MWBase::Environment::get().getWindowManager()->setDragDrop(false); - mDragAndDrop->mDraggedFrom->drawItems(); - mDragAndDrop->mDraggedFrom->notifyItemDragged(object, -mDragAndDrop->mDraggedCount); + // remove object from the container it was coming from + mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount); + mDragAndDrop->finish(); } else { diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp new file mode 100644 index 000000000..c78bc2c00 --- /dev/null +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -0,0 +1,97 @@ +#include "inventoryitemmodel.hpp" + +#include "../mwworld/containerstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" + +namespace MWGui +{ + +InventoryItemModel::InventoryItemModel(const MWWorld::Ptr &actor) + : mActor(actor) +{ +} + +ItemStack InventoryItemModel::getItem (ModelIndex index) +{ + if (index < 0) + throw std::runtime_error("Invalid index supplied"); + if (mItems.size() <= static_cast(index)) + throw std::runtime_error("Item index out of range"); + return mItems[index]; +} + +size_t InventoryItemModel::getItemCount() +{ + return mItems.size(); +} + +ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item) +{ + size_t i = 0; + for (std::vector::iterator it = mItems.begin(); it != mItems.end(); ++it) + { + if (*it == item) + return i; + ++i; + } + return -1; +} + +void InventoryItemModel::copyItem (const ItemStack& item, size_t count) +{ + int origCount = item.mBase.getRefData().getCount(); + item.mBase.getRefData().setCount(count); + MWWorld::ContainerStoreIterator it = MWWorld::Class::get(mActor).getContainerStore(mActor).add(item.mBase); + if (*it != item.mBase) + item.mBase.getRefData().setCount(origCount); + else + item.mBase.getRefData().setCount(origCount + count); // item copied onto itself +} + + +void InventoryItemModel::removeItem (const ItemStack& item, size_t count) +{ + MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + if (*it == item.mBase) + { + if (it->getRefData().getCount() < static_cast(count)) + throw std::runtime_error("Not enough items in the stack to remove"); + it->getRefData().setCount(it->getRefData().getCount() - count); + return; + } + } + throw std::runtime_error("Item to remove not found in container store"); +} + +void InventoryItemModel::update() +{ + MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor); + + mItems.clear(); + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + ItemStack newItem (*it, this, it->getRefData().getCount()); + + if (mActor.getTypeName() == typeid(ESM::NPC).name()) + { + MWWorld::InventoryStore& store = MWWorld::Class::get(mActor).getInventoryStore(mActor); + for (int slot=0; slot mItems; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index e5e20b778..ea963f393 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -1,5 +1,7 @@ #include "inventorywindow.hpp" +#include + #include #include "../mwbase/world.hpp" @@ -9,22 +11,31 @@ #include "../mwworld/player.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/action.hpp" #include "bookwindow.hpp" #include "scrollwindow.hpp" #include "spellwindow.hpp" +#include "itemview.hpp" +#include "inventoryitemmodel.hpp" +#include "sortfilteritemmodel.hpp" +#include "tradeitemmodel.hpp" +#include "countdialog.hpp" +#include "tradewindow.hpp" +#include "container.hpp" namespace MWGui { InventoryWindow::InventoryWindow(DragAndDrop* dragAndDrop) - : ContainerBase(dragAndDrop) - , WindowPinnableBase("openmw_inventory_window.layout") + : WindowPinnableBase("openmw_inventory_window.layout") , mTrading(false) , mLastXSize(0) , mLastYSize(0) , mPreview(MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ()) , mPreviewDirty(true) + , mDragAndDrop(dragAndDrop) { static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); @@ -42,11 +53,14 @@ namespace MWGui mAvatar->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); - MyGUI::ScrollView* itemView; - MyGUI::Widget* containerWidget; - getWidget(containerWidget, "Items"); - getWidget(itemView, "ItemView"); - setWidgets(containerWidget, itemView); + mPtr = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer (); + + getWidget(mItemView, "ItemView"); + mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr()); + mSortModel = new SortFilterItemModel(mTradeModel); + mItemView->setModel(mSortModel); + mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected); + mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &InventoryWindow::onBackgroundSelected); mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterWeapon->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); @@ -57,23 +71,127 @@ namespace MWGui mFilterAll->setStateSelected(true); setCoord(0, 342, 498, 258); + onWindowResize(static_cast(mMainWidget)); mPreview.setup(); + } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - openContainer(player); + TradeItemModel* InventoryWindow::getTradeModel() + { + return mTradeModel; + } + + ItemModel* InventoryWindow::getModel() + { + return mTradeModel; + } + + void InventoryWindow::onBackgroundSelected() + { + if (mDragAndDrop->mIsOnDragAndDrop) + mDragAndDrop->drop(mTradeModel, mItemView); + } + + void InventoryWindow::onItemSelected (int index) + { + onItemSelectedFromSourceModel (mSortModel->mapToSource(index)); + } + + void InventoryWindow::onItemSelectedFromSourceModel (int index) + { + if (mDragAndDrop->mIsOnDragAndDrop) + { + mDragAndDrop->drop(mTradeModel, mItemView); + return; + } + + const ItemStack& item = mTradeModel->getItem(index); + + unequipItem(item.mBase); + + MWWorld::Ptr object = item.mBase; + int count = item.mCount; + bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); + if (MyGUI::InputManager::getInstance().isControlPressed()) + count = 1; + + if (mTrading) + { + // check if merchant accepts item + int services = MWBase::Environment::get().getWindowManager()->getTradeWindow()->getMerchantServices(); + if (!MWWorld::Class::get(object).canSell(object, services)) + { + MWBase::Environment::get().getWindowManager()-> + messageBox("#{sBarterDialog4}"); + return; + } + } + + if (count > 1 && !shift) + { + CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); + std::string message = mTrading ? "#{sQuanityMenuMessage01}" : "#{sTake}"; + dialog->open(MWWorld::Class::get(object).getName(object), message, count); + dialog->eventOkClicked.clear(); + if (mTrading) + dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::sellItem); + else + dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::dragItem); + mSelectedItem = index; + } + else + { + mSelectedItem = index; + if (mTrading) + sellItem (NULL, count); + else + dragItem (NULL, count); + } + + // item might have been unequipped + notifyContentChanged(); + } + + void InventoryWindow::dragItem(MyGUI::Widget* sender, int count) + { + mDragAndDrop->startDrag(mSelectedItem, mSortModel, mTradeModel, mItemView, count); + } + + void InventoryWindow::sellItem(MyGUI::Widget* sender, int count) + { + const ItemStack& item = mTradeModel->getItem(mSelectedItem); + std::string sound = MWWorld::Class::get(item.mBase).getDownSoundId(item.mBase); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + + if (item.mType == ItemStack::Type_Barter) + { + // this was an item borrowed to us by the merchant + MWBase::Environment::get().getWindowManager()->getTradeWindow()->returnItem(mSelectedItem, count); + mTradeModel->returnItemBorrowedToUs(mSelectedItem, count); + } + else + { + // borrow item to the merchant + MWBase::Environment::get().getWindowManager()->getTradeWindow()->borrowItem(mSelectedItem, count); + mTradeModel->borrowItemFromUs(mSelectedItem, count); + } + + mItemView->update(); + } + + void InventoryWindow::updateItemView() + { + mItemView->update(); + mPreviewDirty = true; } void InventoryWindow::open() { updateEncumbranceBar(); - mTrading = false; + mItemView->update(); - mBoughtItems.clear(); - - onWindowResize(static_cast(mMainWidget)); - drawItems(); + notifyContentChanged(); } void InventoryWindow::onWindowResize(MyGUI::Window* _sender) @@ -87,24 +205,24 @@ namespace MWGui if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize) { - drawItems(); mLastXSize = mMainWidget->getSize().width; mLastYSize = mMainWidget->getSize().height; + mPreviewDirty = true; } } void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender) { if (_sender == mFilterAll) - setFilter(ContainerBase::Filter_All); + mSortModel->setCategory(SortFilterItemModel::Category_All); else if (_sender == mFilterWeapon) - setFilter(ContainerBase::Filter_Weapon); + mSortModel->setCategory(SortFilterItemModel::Category_Weapon); else if (_sender == mFilterApparel) - setFilter(ContainerBase::Filter_Apparel); + mSortModel->setCategory(SortFilterItemModel::Category_Apparel); else if (_sender == mFilterMagic) - setFilter(ContainerBase::Filter_Magic); + mSortModel->setCategory(SortFilterItemModel::Category_Magic); else if (_sender == mFilterMisc) - setFilter(ContainerBase::Filter_Misc); + mSortModel->setCategory(SortFilterItemModel::Category_Misc); mFilterAll->setStateSelected(false); mFilterWeapon->setStateSelected(false); @@ -112,6 +230,8 @@ namespace MWGui mFilterMagic->setStateSelected(false); mFilterMisc->setStateSelected(false); + mItemView->update(); + static_cast(_sender)->setStateSelected(true); } @@ -124,24 +244,24 @@ namespace MWGui { if (mDragAndDrop->mIsOnDragAndDrop) { - MWWorld::Ptr ptr = *mDragAndDrop->mDraggedWidget->getUserData(); + MWWorld::Ptr ptr = mDragAndDrop->mItem.mBase; + mDragAndDrop->finish(); - if (mDragAndDrop->mDraggedFrom != this) + if (mDragAndDrop->mSourceModel != mTradeModel) { // add item to the player's inventory MWWorld::ContainerStore& invStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); MWWorld::ContainerStoreIterator it = invStore.begin(); int origCount = ptr.getRefData().getCount(); - ptr.getRefData().setCount(origCount - mDragAndDrop->mDraggedCount); + ptr.getRefData().setCount(mDragAndDrop->mDraggedCount); it = invStore.add(ptr); - (*it).getRefData().setCount(mDragAndDrop->mDraggedCount); + ptr.getRefData().setCount(origCount); + + mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount); ptr = *it; - mDragAndDrop->mDraggedFrom->notifyItemDragged(ptr, -mDragAndDrop->mDraggedCount); } - /// \todo scripts - boost::shared_ptr action = MWWorld::Class::get(ptr).use(ptr); action->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); @@ -153,12 +273,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->getBookWindow()->setTakeButtonShow(false); MWBase::Environment::get().getWindowManager()->getScrollWindow()->setTakeButtonShow(false); - mDragAndDrop->mIsOnDragAndDrop = false; - MyGUI::Gui::getInstance().destroyWidget(mDragAndDrop->mDraggedWidget); - - MWBase::Environment::get().getWindowManager()->setDragDrop(false); - - drawItems(); + mItemView->update(); notifyContentChanged(); } @@ -173,16 +288,15 @@ namespace MWGui if (itemSelected.isEmpty ()) return; - for (unsigned int i=0; i < mContainerWidget->getChildCount (); ++i) + for (size_t i=0; i < mTradeModel->getItemCount (); ++i) { - MyGUI::Widget* w = mContainerWidget->getChildAt (i); - - if (*w->getUserData() == itemSelected) + if (mTradeModel->getItem(i).mBase == itemSelected) { - onSelectedItem(w); + onItemSelectedFromSourceModel(i); return; } } + throw std::runtime_error("Can't find clicked item"); } } @@ -201,7 +315,7 @@ namespace MWGui return MWWorld::Ptr(); } - void InventoryWindow::_unequipItem(MWWorld::Ptr item) + void InventoryWindow::unequipItem(const MWWorld::Ptr& item) { MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); @@ -252,9 +366,9 @@ namespace MWGui return 0; } - void InventoryWindow::startTrade() + void InventoryWindow::setTrading(bool trading) { - mTrading = true; + mTrading = trading; } void InventoryWindow::doRenderUpdate () @@ -282,7 +396,7 @@ namespace MWGui if (weaponSlot == invStore.end()) MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon(); else - MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*weaponSlot); /// \todo track weapon durability + MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*weaponSlot); mPreviewDirty = true; @@ -292,8 +406,6 @@ namespace MWGui void InventoryWindow::pickUpObject (MWWorld::Ptr object) { - /// \todo scripts - // make sure the object is of a type that can be picked up std::string type = object.getTypeName(); if ( (type != typeid(ESM::Apparatus).name()) @@ -313,11 +425,9 @@ namespace MWGui if (MWWorld::Class::get(object).getName(object) == "") // objects without name presented to user can never be picked up return; - // sound - std::string sound = MWWorld::Class::get(object).getUpSoundId(object); - MWBase::Environment::get().getSoundManager()->playSound(sound, 1, 1); - int count = object.getRefData().getCount(); + if (object.getCellRef().mGoldValue > 1) + count = object.getCellRef().mGoldValue; // add to player inventory // can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object @@ -326,31 +436,17 @@ namespace MWGui // remove from world MWBase::Environment::get().getWorld()->deleteObject (object); - mDragAndDrop->mIsOnDragAndDrop = true; - mDragAndDrop->mDraggedCount = count; - - std::string path = std::string("icons\\"); - path += MWWorld::Class::get(newObject).getInventoryIcon(newObject); - MyGUI::ImageBox* baseWidget = mContainerWidget->createWidget("ImageBox", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); - baseWidget->detachFromWidget(); - baseWidget->attachToWidget(mDragAndDrop->mDragAndDropWidget); - baseWidget->setUserData(newObject); - mDragAndDrop->mDraggedWidget = baseWidget; - MyGUI::ImageBox* image = baseWidget->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - image->setImageTexture(path); - image->setNeedMouseFocus(false); - - // text widget that shows item count - MyGUI::TextBox* text = image->createWidget("SandBrightText", MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); - text->setTextAlign(MyGUI::Align::Right); - text->setNeedMouseFocus(false); - text->setTextShadow(true); - text->setTextShadowColour(MyGUI::Colour(0,0,0)); - text->setCaption(getCountString(count)); - mDragAndDrop->mDraggedFrom = this; + // get ModelIndex to the item + mTradeModel->update(); + size_t i=0; + for (; igetItemCount(); ++i) + { + if (mTradeModel->getItem(i).mBase == newObject) + break; + } + if (i == mTradeModel->getItemCount()) + throw std::runtime_error("Added item not found"); + mDragAndDrop->startDrag(i, mSortModel, mTradeModel, mItemView, count); } MyGUI::IntCoord InventoryWindow::getAvatarScreenCoord () diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index a4ed89d40..4bd1ef501 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -3,13 +3,18 @@ #include "../mwrender/characterpreview.hpp" -#include "container.hpp" #include "windowpinnablebase.hpp" #include "widgets.hpp" namespace MWGui { - class InventoryWindow : public ContainerBase, public WindowPinnableBase + class ItemView; + class SortFilterItemModel; + class TradeItemModel; + class DragAndDrop; + class ItemModel; + + class InventoryWindow : public WindowPinnableBase { public: InventoryWindow(DragAndDrop* dragAndDrop); @@ -19,7 +24,7 @@ namespace MWGui void doRenderUpdate(); /// start trading, disables item drag&drop - void startTrade(); + void setTrading(bool trading); void onFrame(); @@ -35,8 +40,22 @@ namespace MWGui mPreview.rebuild(); } - protected: + TradeItemModel* getTradeModel(); + ItemModel* getModel(); + + void updateItemView(); + + private: + DragAndDrop* mDragAndDrop; + bool mPreviewDirty; + size_t mSelectedItem; + + MWWorld::Ptr mPtr; + + MWGui::ItemView* mItemView; + SortFilterItemModel* mSortModel; + TradeItemModel* mTradeModel; MyGUI::Widget* mAvatar; MyGUI::ImageBox* mAvatarImage; @@ -59,20 +78,22 @@ namespace MWGui bool mTrading; + void onItemSelected(int index); + void onItemSelectedFromSourceModel(int index); + + void onBackgroundSelected(); + + void sellItem(MyGUI::Widget* sender, int count); + void dragItem(MyGUI::Widget* sender, int count); + void onWindowResize(MyGUI::Window* _sender); void onFilterChanged(MyGUI::Widget* _sender); void onAvatarClicked(MyGUI::Widget* _sender); void onPinToggled(); + void unequipItem(const MWWorld::Ptr& item); void updateEncumbranceBar(); - - virtual bool isTrading() { return mTrading; } - virtual bool isInventory() { return true; } - virtual void _unequipItem(MWWorld::Ptr item); - - virtual void onReferenceUnavailable() { ; } - - virtual void notifyContentChanged(); + void notifyContentChanged(); }; } diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp new file mode 100644 index 000000000..0dc2bb62c --- /dev/null +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -0,0 +1,98 @@ +#include "itemmodel.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" + +namespace MWGui +{ + + ItemStack::ItemStack(const MWWorld::Ptr &base, ItemModel *creator, size_t count) + : mCreator(creator) + , mCount(count) + , mFlags(0) + , mType(Type_Normal) + , mBase(base) + { + assert(base.getContainerStore()); + + if (MWWorld::Class::get(base).getEnchantment(base) != "") + mFlags |= Flag_Enchanted; + } + + ItemStack::ItemStack() + : mCreator(NULL) + , mCount(0) + , mFlags(0) + , mType(Type_Normal) + { + } + + bool ItemStack::stacks(const ItemStack &other) + { + if(mBase == other.mBase) + return true; + return mBase.getContainerStore()->stacks(mBase, other.mBase) + && other.mBase.getContainerStore()->stacks(mBase, other.mBase); + } + + bool operator == (const ItemStack& left, const ItemStack& right) + { + if (left.mType != right.mType) + return false; + if(left.mBase == right.mBase) + return true; + return left.mBase.getContainerStore()->stacks(left.mBase, right.mBase) + && right.mBase.getContainerStore()->stacks(left.mBase, right.mBase); + } + + ItemModel::ItemModel() + { + } + + + ProxyItemModel::~ProxyItemModel() + { + delete mSourceModel; + } + + void ProxyItemModel::copyItem (const ItemStack& item, size_t count) + { + // no need to use mapToSource since itemIndex refers to an index in the sourceModel + mSourceModel->copyItem (item, count); + } + + void ProxyItemModel::removeItem (const ItemStack& item, size_t count) + { + mSourceModel->removeItem (item, count); + } + + ItemModel::ModelIndex ProxyItemModel::mapToSource (ModelIndex index) + { + const ItemStack& itemToSearch = getItem(index); + for (size_t i=0; igetItemCount(); ++i) + { + const ItemStack& item = mSourceModel->getItem(i); + if (item == itemToSearch) + return i; + } + return -1; + } + + ItemModel::ModelIndex ProxyItemModel::mapFromSource (ModelIndex index) + { + const ItemStack& itemToSearch = mSourceModel->getItem(index); + for (size_t i=0; igetIndex(item); + } + +} diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp new file mode 100644 index 000000000..47aaf79e8 --- /dev/null +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -0,0 +1,84 @@ +#ifndef MWGUI_ITEM_MODEL_H +#define MWGUI_ITEM_MODEL_H + +#include "../mwworld/ptr.hpp" + +namespace MWGui +{ + + class ItemModel; + + /// @brief A single item stack managed by an item model + struct ItemStack + { + ItemStack (const MWWorld::Ptr& base, ItemModel* creator, size_t count); + ItemStack(); + bool stacks (const ItemStack& other); + ///< like operator==, only without checking mType + + enum Type + { + Type_Barter, + Type_Equipped, + Type_Normal + }; + Type mType; + + enum Flags + { + Flag_Enchanted = (1<<0) + }; + int mFlags; + + ItemModel* mCreator; + size_t mCount; + MWWorld::Ptr mBase; + }; + + bool operator == (const ItemStack& left, const ItemStack& right); + + + /// @brief The base class that all item models should derive from. + class ItemModel + { + public: + ItemModel(); + virtual ~ItemModel() {} + + typedef int ModelIndex; + + virtual ItemStack getItem (ModelIndex index) = 0; + ///< throws for invalid index + virtual size_t getItemCount() = 0; + + virtual ModelIndex getIndex (ItemStack item) = 0; + + virtual void update() = 0; + + virtual void copyItem (const ItemStack& item, size_t count) = 0; + virtual void removeItem (const ItemStack& item, size_t count) = 0; + + private: + ItemModel(const ItemModel&); + ItemModel& operator=(const ItemModel&); + }; + + /// @brief A proxy item model can be used to filter or rearrange items from a source model (or even add new items to it). + /// The neat thing is that this does not actually alter the source model. + class ProxyItemModel : public ItemModel + { + public: + virtual ~ProxyItemModel(); + virtual void copyItem (const ItemStack& item, size_t count); + virtual void removeItem (const ItemStack& item, size_t count); + virtual ModelIndex getIndex (ItemStack item); + + ModelIndex mapToSource (ModelIndex index); + ModelIndex mapFromSource (ModelIndex index); + protected: + ItemModel* mSourceModel; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/itemselection.cpp b/apps/openmw/mwgui/itemselection.cpp index ed8b47b08..a57c6d81f 100644 --- a/apps/openmw/mwgui/itemselection.cpp +++ b/apps/openmw/mwgui/itemselection.cpp @@ -1,19 +1,17 @@ #include "itemselection.hpp" +#include "itemview.hpp" +#include "inventoryitemmodel.hpp" +#include "sortfilteritemmodel.hpp" + namespace MWGui { - ItemSelectionDialog::ItemSelectionDialog(const std::string &label, int filter) - : ContainerBase(NULL) - , WindowModal("openmw_itemselection_dialog.layout") + ItemSelectionDialog::ItemSelectionDialog(const std::string &label) + : WindowModal("openmw_itemselection_dialog.layout") { - mFilter = filter; - - MyGUI::ScrollView* itemView; - MyGUI::Widget* containerWidget; - getWidget(containerWidget, "Items"); - getWidget(itemView, "ItemView"); - setWidgets(containerWidget, itemView); + getWidget(mItemView, "ItemView"); + mItemView->eventItemClicked += MyGUI::newDelegate(this, &ItemSelectionDialog::onSelectedItem); MyGUI::TextBox* l; getWidget(l, "Label"); @@ -26,9 +24,29 @@ namespace MWGui center(); } - void ItemSelectionDialog::onSelectedItemImpl(MWWorld::Ptr item) + void ItemSelectionDialog::openContainer(const MWWorld::Ptr& container) + { + mModel = new InventoryItemModel(container); + mSortModel = new SortFilterItemModel(mModel); + mItemView->setModel(mSortModel); + } + + void ItemSelectionDialog::setCategory(int category) + { + mSortModel->setCategory(category); + mItemView->update(); + } + + void ItemSelectionDialog::setFilter(int filter) + { + mSortModel->setFilter(filter); + mItemView->update(); + } + + void ItemSelectionDialog::onSelectedItem(int index) { - eventItemSelected(item); + ItemStack item = mSortModel->getItem(index); + eventItemSelected(item.mBase); } void ItemSelectionDialog::onCancelButtonClicked(MyGUI::Widget* sender) diff --git a/apps/openmw/mwgui/itemselection.hpp b/apps/openmw/mwgui/itemselection.hpp index 19007de6b..d6d19d9e1 100644 --- a/apps/openmw/mwgui/itemselection.hpp +++ b/apps/openmw/mwgui/itemselection.hpp @@ -2,11 +2,14 @@ namespace MWGui { + class ItemView; + class SortFilterItemModel; + class InventoryItemModel; - class ItemSelectionDialog : public ContainerBase, public WindowModal + class ItemSelectionDialog : public WindowModal { public: - ItemSelectionDialog(const std::string& label, int filter); + ItemSelectionDialog(const std::string& label); typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Item; @@ -14,11 +17,16 @@ namespace MWGui EventHandle_Item eventItemSelected; EventHandle_Void eventDialogCanceled; + void openContainer (const MWWorld::Ptr& container); + void setCategory(int category); + void setFilter(int filter); private: - virtual void onReferenceUnavailable() { ; } + ItemView* mItemView; + SortFilterItemModel* mSortModel; + InventoryItemModel* mModel; - virtual void onSelectedItemImpl(MWWorld::Ptr item); + void onSelectedItem(int index); void onCancelButtonClicked(MyGUI::Widget* sender); }; diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp new file mode 100644 index 000000000..5155d2712 --- /dev/null +++ b/apps/openmw/mwgui/itemview.cpp @@ -0,0 +1,200 @@ +#include "itemview.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "../mwworld/class.hpp" + +#include "itemmodel.hpp" + +namespace +{ + std::string getCountString(const int count) + { + if (count == 1) + return ""; + if (count > 9999) + return boost::lexical_cast(int(count/1000.f)) + "k"; + else + return boost::lexical_cast(count); + } +} + + +namespace MWGui +{ + +ItemView::ItemView() + : mModel(NULL) +{ +} + +ItemView::~ItemView() +{ + delete mModel; +} + +void ItemView::setModel(ItemModel *model) +{ + delete mModel; + mModel = model; + update(); +} + +void ItemView::initialiseOverride() +{ + Base::initialiseOverride(); + + assignWidget(mScrollView, "ScrollView"); + if (mScrollView == NULL) + throw std::runtime_error("Item view needs a scroll view"); + + mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); +} + +void ItemView::update() +{ + while (mScrollView->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); + + if (!mModel) + return; + + int x = 0; + int y = 0; + int maxHeight = mScrollView->getSize().height - 58; + + mModel->update(); + + MyGUI::Widget* dragArea = mScrollView->createWidget("",0,0,mScrollView->getWidth(),mScrollView->getHeight(), + MyGUI::Align::Stretch); + dragArea->setNeedMouseFocus(true); + dragArea->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedBackground); + dragArea->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheel); + + for (ItemModel::ModelIndex i=0; i(mModel->getItemCount()); ++i) + { + const ItemStack& item = mModel->getItem(i); + + /// \todo performance improvement: don't create/destroy all the widgets everytime the container window changes size, only reposition them + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(item.mBase).getInventoryIcon(item.mBase); + + // background widget (for the "equipped" frame and magic item background image) + bool isMagic = (item.mFlags & ItemStack::Flag_Enchanted); + MyGUI::ImageBox* backgroundWidget = dragArea->createWidget("ImageBox", + MyGUI::IntCoord(x, y, 42, 42), MyGUI::Align::Default); + backgroundWidget->setUserString("ToolTipType", "ItemModelIndex"); + backgroundWidget->setUserData(std::make_pair(i, mModel)); + + std::string backgroundTex = "textures\\menu_icon"; + if (isMagic) + backgroundTex += "_magic"; + if (item.mType == ItemStack::Type_Normal) + { + if (!isMagic) + backgroundTex = ""; + } + else if (item.mType == ItemStack::Type_Equipped) + backgroundTex += "_equip"; + else if (item.mType == ItemStack::Type_Barter) + backgroundTex += "_barter"; + + if (backgroundTex != "") + backgroundTex += ".dds"; + + backgroundWidget->setImageTexture(backgroundTex); + if ((item.mType == ItemStack::Type_Barter) && !isMagic) + backgroundWidget->setProperty("ImageCoord", "2 2 42 42"); + else + backgroundWidget->setProperty("ImageCoord", "0 0 42 42"); + backgroundWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedItem); + backgroundWidget->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheel); + + // image + MyGUI::ImageBox* image = backgroundWidget->createWidget("ImageBox", + MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + image->setImageTexture(path); + image->setNeedMouseFocus(false); + + // text widget that shows item count + MyGUI::TextBox* text = image->createWidget("SandBrightText", + MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); + text->setTextAlign(MyGUI::Align::Right); + text->setNeedMouseFocus(false); + text->setTextShadow(true); + text->setTextShadowColour(MyGUI::Colour(0,0,0)); + text->setCaption(getCountString(item.mCount)); + + y += 42; + if (y > maxHeight) + { + x += 42; + y = 0; + } + + } + x += 42; + MyGUI::IntSize size = MyGUI::IntSize(std::max(mScrollView->getSize().width, x), mScrollView->getSize().height); + mScrollView->setCanvasSize(size); + dragArea->setSize(size); +} + +void ItemView::onSelectedItem(MyGUI::Widget *sender) +{ + ItemModel::ModelIndex index = (*sender->getUserData >()).first; + eventItemClicked(index); +} + +void ItemView::onSelectedBackground(MyGUI::Widget *sender) +{ + eventBackgroundClicked(); +} + +void ItemView::onMouseWheel(MyGUI::Widget *_sender, int _rel) +{ + if (mScrollView->getViewOffset().left + _rel*0.3 > 0) + mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mScrollView->setViewOffset(MyGUI::IntPoint(mScrollView->getViewOffset().left + _rel*0.3, 0)); +} + +void ItemView::setSize(const MyGUI::IntSize &_value) +{ + Base::setSize(_value); + update(); +} + +void ItemView::setSize(int _width, int _height) +{ + Base::setSize(_width, _height); + update(); +} + +void ItemView::setCoord(const MyGUI::IntCoord &_value) +{ + Base::setCoord(_value); + update(); +} + +void ItemView::setCoord(int _left, int _top, int _width, int _height) +{ + Base::setCoord(_left, _top, _width, _height); + update(); +} + +void ItemView::registerComponents() +{ + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); +} + +} diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp new file mode 100644 index 000000000..17f609f2b --- /dev/null +++ b/apps/openmw/mwgui/itemview.hpp @@ -0,0 +1,52 @@ +#ifndef MWGUI_ITEMVIEW_H +#define MWGUI_ITEMVIEW_H + +#include + +#include "itemmodel.hpp" + +namespace MWGui +{ + + class ItemView : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(ItemView) + public: + ItemView(); + virtual ~ItemView(); + + /// Register needed components with MyGUI's factory manager + static void registerComponents (); + + /// Takes ownership of \a model + void setModel (ItemModel* model); + + typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ModelIndex; + typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; + /// Fired when an item was clicked + EventHandle_ModelIndex eventItemClicked; + /// Fired when the background was clicked (useful for drag and drop) + EventHandle_Void eventBackgroundClicked; + + void update(); + + private: + virtual void initialiseOverride(); + + virtual void setSize(const MyGUI::IntSize& _value); + virtual void setCoord(const MyGUI::IntCoord& _value); + void setSize(int _width, int _height); + void setCoord(int _left, int _top, int _width, int _height); + + void onSelectedItem (MyGUI::Widget* sender); + void onSelectedBackground (MyGUI::Widget* sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + + ItemModel* mModel; + MyGUI::ScrollView* mScrollView; + + }; + +} + +#endif diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index a98051c28..530594dda 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -9,6 +9,8 @@ #include "../mwbase/soundmanager.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "inventorywindow.hpp" #include "tradewindow.hpp" diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp new file mode 100644 index 000000000..16be5f6cc --- /dev/null +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -0,0 +1,55 @@ +#include "pickpocketitemmodel.hpp" + +#include "../mwmechanics/npcstats.hpp" +#include "../mwworld/class.hpp" + +namespace MWGui +{ + + PickpocketItemModel::PickpocketItemModel(const MWWorld::Ptr& thief, ItemModel *sourceModel) + { + mSourceModel = sourceModel; + int chance = MWWorld::Class::get(thief).getNpcStats(thief).getSkill(ESM::Skill::Sneak).getModified(); + + mSourceModel->update(); + for (size_t i = 0; igetItemCount(); ++i) + { + if (std::rand() / static_cast(RAND_MAX) * 100 > chance) + mHiddenItems.push_back(mSourceModel->getItem(i)); + } + } + + ItemStack PickpocketItemModel::getItem (ModelIndex index) + { + if (index < 0) + throw std::runtime_error("Invalid index supplied"); + if (mItems.size() <= static_cast(index)) + throw std::runtime_error("Item index out of range"); + return mItems[index]; + } + + size_t PickpocketItemModel::getItemCount() + { + return mItems.size(); + } + + void PickpocketItemModel::update() + { + mSourceModel->update(); + mItems.clear(); + for (size_t i = 0; igetItemCount(); ++i) + { + const ItemStack& item = mSourceModel->getItem(i); + if (std::find(mHiddenItems.begin(), mHiddenItems.end(), item) == mHiddenItems.end() + && item.mType != ItemStack::Type_Equipped) + mItems.push_back(item); + } + } + + void PickpocketItemModel::removeItem (const ItemStack &item, size_t count) + { + ProxyItemModel::removeItem(item, count); + /// \todo check if player is detected + } + +} diff --git a/apps/openmw/mwgui/pickpocketitemmodel.hpp b/apps/openmw/mwgui/pickpocketitemmodel.hpp new file mode 100644 index 000000000..090d48d0e --- /dev/null +++ b/apps/openmw/mwgui/pickpocketitemmodel.hpp @@ -0,0 +1,26 @@ +#ifndef MWGUI_PICKPOCKET_ITEM_MODEL_H +#define MWGUI_PICKPOCKET_ITEM_MODEL_H + +#include "itemmodel.hpp" + +namespace MWGui +{ + + /// @brief The pickpocket item model randomly hides item stacks based on a specified chance. Equipped items are always hidden. + class PickpocketItemModel : public ProxyItemModel + { + public: + PickpocketItemModel (const MWWorld::Ptr& thief, ItemModel* sourceModel); + virtual ItemStack getItem (ModelIndex index); + virtual size_t getItemCount(); + virtual void update(); + virtual void removeItem (const ItemStack& item, size_t count); + + private: + std::vector mHiddenItems; + std::vector mItems; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index a44f1791c..877f7b700 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -120,13 +120,12 @@ namespace MWGui { if (!mItemSelectionDialog ) { - mItemSelectionDialog = new ItemSelectionDialog("#{sQuickMenu6}", ContainerBase::Filter_All); + mItemSelectionDialog = new ItemSelectionDialog("#{sQuickMenu6}"); mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItem); mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &QuickKeysMenu::onAssignItemCancel); } mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - mItemSelectionDialog->drawItems (); mAssignDialog->setVisible (false); } @@ -303,7 +302,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->getScrollWindow()->setTakeButtonShow(false); // since we changed equipping status, update the inventory window - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); } else if (type == Type_MagicItem) { @@ -337,7 +336,7 @@ namespace MWGui action.execute (MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ()); // since we changed equipping status, update the inventory window - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); } store.setSelectedEnchantItem(it); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp new file mode 100644 index 000000000..60cc2fb0e --- /dev/null +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -0,0 +1,170 @@ +#include "sortfilteritemmodel.hpp" + +#include "../mwworld/class.hpp" + +namespace +{ + bool compareType(const std::string& type1, const std::string& type2) + { + // this defines the sorting order of types. types that are first in the vector appear before other types. + std::vector mapping; + mapping.push_back( typeid(ESM::Weapon).name() ); + mapping.push_back( typeid(ESM::Armor).name() ); + mapping.push_back( typeid(ESM::Clothing).name() ); + mapping.push_back( typeid(ESM::Potion).name() ); + mapping.push_back( typeid(ESM::Ingredient).name() ); + mapping.push_back( typeid(ESM::Apparatus).name() ); + mapping.push_back( typeid(ESM::Book).name() ); + mapping.push_back( typeid(ESM::Light).name() ); + mapping.push_back( typeid(ESM::Miscellaneous).name() ); + mapping.push_back( typeid(ESM::Lockpick).name() ); + mapping.push_back( typeid(ESM::Repair).name() ); + mapping.push_back( typeid(ESM::Probe).name() ); + + assert( std::find(mapping.begin(), mapping.end(), type1) != mapping.end() ); + assert( std::find(mapping.begin(), mapping.end(), type2) != mapping.end() ); + + return std::find(mapping.begin(), mapping.end(), type1) < std::find(mapping.begin(), mapping.end(), type2); + } + + bool compare (const MWGui::ItemStack& left, const MWGui::ItemStack& right) + { + if (left.mType != right.mType) + return left.mType < right.mType; + + if (left.mBase.getTypeName() == right.mBase.getTypeName()) + { + int cmp = MWWorld::Class::get(left.mBase).getName(left.mBase).compare( + MWWorld::Class::get(right.mBase).getName(right.mBase)); + return cmp < 0; + } + else + return compareType(left.mBase.getTypeName(), right.mBase.getTypeName()); + } +} + +namespace MWGui +{ + + SortFilterItemModel::SortFilterItemModel(ItemModel *sourceModel) + : mCategory(Category_All) + , mShowEquipped(true) + , mFilter(0) + { + mSourceModel = sourceModel; + } + + void SortFilterItemModel::addDragItem (const MWWorld::Ptr& dragItem, size_t count) + { + mDragItems.push_back(std::make_pair(dragItem, count)); + } + + void SortFilterItemModel::clearDragItems() + { + mDragItems.clear(); + } + + bool SortFilterItemModel::filterAccepts (const ItemStack& item) + { + MWWorld::Ptr base = item.mBase; + + if (item.mType == ItemStack::Type_Equipped && !mShowEquipped) + return false; + + int category; + if (base.getTypeName() == typeid(ESM::Armor).name() + || base.getTypeName() == typeid(ESM::Clothing).name()) + category = Category_Apparel; + else if (base.getTypeName() == typeid(ESM::Weapon).name()) + category = Category_Weapon; + else if (base.getTypeName() == typeid(ESM::Ingredient).name() + || base.getTypeName() == typeid(ESM::Potion).name()) + category = Category_Magic; + else if (base.getTypeName() == typeid(ESM::Miscellaneous).name() + || base.getTypeName() == typeid(ESM::Ingredient).name() + || base.getTypeName() == typeid(ESM::Repair).name() + || base.getTypeName() == typeid(ESM::Lockpick).name() + || base.getTypeName() == typeid(ESM::Light).name() + || base.getTypeName() == typeid(ESM::Apparatus).name() + || base.getTypeName() == typeid(ESM::Book).name() + || base.getTypeName() == typeid(ESM::Probe).name()) + category = Category_Misc; + + if (item.mFlags & ItemStack::Flag_Enchanted) + category |= Category_Magic; + + if (!(category & mCategory)) + return false; + + if ((mFilter & Filter_OnlyIngredients) && base.getTypeName() != typeid(ESM::Ingredient).name()) + return false; + if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted)) + return false; + if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name() + || base.getCellRef().mSoul == "")) + return false; + if ((mFilter & Filter_OnlyEnchantable) && (item.mFlags & ItemStack::Flag_Enchanted + || (base.getTypeName() != typeid(ESM::Armor).name() + && base.getTypeName() != typeid(ESM::Clothing).name() + && base.getTypeName() != typeid(ESM::Book).name()))) + return false; + if ((mFilter & Filter_OnlyEnchantable) && base.getTypeName() == typeid(ESM::Book).name() + && !base.get()->mBase->mData.mIsScroll) + return false; + + return true; + } + + ItemStack SortFilterItemModel::getItem (ModelIndex index) + { + if (index < 0) + throw std::runtime_error("Invalid index supplied"); + if (mItems.size() <= static_cast(index)) + throw std::runtime_error("Item index out of range"); + return mItems[index]; + } + + size_t SortFilterItemModel::getItemCount() + { + return mItems.size(); + } + + void SortFilterItemModel::setCategory (int category) + { + mCategory = category; + } + + void SortFilterItemModel::setFilter (int filter) + { + mFilter = filter; + } + + void SortFilterItemModel::update() + { + mSourceModel->update(); + + size_t count = mSourceModel->getItemCount(); + + mItems.clear(); + for (size_t i=0; igetItem(i); + + for (std::vector >::iterator it = mDragItems.begin(); it != mDragItems.end(); ++it) + { + if (item.mBase == it->first) + { + if (item.mCount < it->second) + throw std::runtime_error("Dragging more than present in the model"); + item.mCount -= it->second; + } + } + + if (item.mCount > 0 && filterAccepts(item)) + mItems.push_back(item); + } + + std::sort(mItems.begin(), mItems.end(), compare); + } + +} diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp new file mode 100644 index 000000000..c7feaa3b9 --- /dev/null +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -0,0 +1,53 @@ +#ifndef MWGUI_SORT_FILTER_ITEM_MODEL_H +#define MWGUI_SORT_FILTER_ITEM_MODEL_H + +#include "itemmodel.hpp" + +namespace MWGui +{ + + class SortFilterItemModel : public ProxyItemModel + { + public: + SortFilterItemModel (ItemModel* sourceModel); + + virtual void update(); + + bool filterAccepts (const ItemStack& item); + + virtual ItemStack getItem (ModelIndex index); + virtual size_t getItemCount(); + + /// Dragged items are not displayed. + void addDragItem (const MWWorld::Ptr& dragItem, size_t count); + void clearDragItems(); + + void setCategory (int category); + void setFilter (int filter); + void setShowEquipped (bool show) { mShowEquipped = show; } + + static const int Category_Weapon = (1<<1); + static const int Category_Apparel = (1<<2); + static const int Category_Misc = (1<<3); + static const int Category_Magic = (1<<4); + static const int Category_All = 255; + + static const int Filter_OnlyIngredients = (1<<0); + static const int Filter_OnlyEnchanted = (1<<1); + static const int Filter_OnlyEnchantable = (1<<2); + static const int Filter_OnlyChargedSoulstones = (1<<3); + + + private: + std::vector mItems; + + std::vector > mDragItems; + + int mCategory; + int mFilter; + bool mShowEquipped; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index cb2007565..a7fcfdd02 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -9,6 +9,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" #include "../mwmechanics/creaturestats.hpp" diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index bd1fef12b..d5e3abc11 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -369,7 +369,7 @@ namespace MWGui action.execute (MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ()); // since we changed equipping status, update the inventory window - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); } store.setSelectedEnchantItem(it); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index d86aa6941..e73ed6b5e 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -6,9 +6,13 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwworld/class.hpp" + #include "mapwindow.hpp" #include "inventorywindow.hpp" +#include "itemmodel.hpp" + namespace MWGui { @@ -176,6 +180,16 @@ namespace MWGui mFocusObject = *focus->getUserData(); tooltipSize = getToolTipViaPtr(false); } + else if (type == "ItemModelIndex") + { + std::pair pair = *focus->getUserData >(); + mFocusObject = pair.second->getItem(pair.first).mBase; + // HACK: To get the correct count for multiple item stack sources + int oldCount = mFocusObject.getRefData().getCount(); + mFocusObject.getRefData().setCount(pair.second->getItem(pair.first).mCount); + tooltipSize = getToolTipViaPtr(false); + mFocusObject.getRefData().setCount(oldCount); + } else if (type == "ToolTipInfo") { tooltipSize = createToolTip(*focus->getUserData()); diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp new file mode 100644 index 000000000..b4fa4e16f --- /dev/null +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -0,0 +1,198 @@ +#include "tradeitemmodel.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/inventorystore.hpp" + +namespace MWGui +{ + + TradeItemModel::TradeItemModel(ItemModel *sourceModel, const MWWorld::Ptr& merchant) + : mMerchant(merchant) + { + mSourceModel = sourceModel; + } + + ItemStack TradeItemModel::getItem (ModelIndex index) + { + if (index < 0) + throw std::runtime_error("Invalid index supplied"); + if (mItems.size() <= static_cast(index)) + throw std::runtime_error("Item index out of range"); + return mItems[index]; + } + + size_t TradeItemModel::getItemCount() + { + return mItems.size(); + } + + void TradeItemModel::borrowImpl(const ItemStack &item, std::vector &out) + { + std::vector::iterator it = out.begin(); + bool found = false; + for (; it != out.end(); ++it) + { + if (it->stacks(item)) + { + it->mCount += item.mCount; + found = true; + break; + } + } + if (!found) + out.push_back(item); + } + + void TradeItemModel::unborrowImpl(const ItemStack &item, size_t count, std::vector &out) + { + std::vector::iterator it = out.begin(); + bool found = false; + for (; it != out.end(); ++it) + { + if (it->stacks(item)) + { + if (it->mCount < count) + throw std::runtime_error("Not enough borrowed items to return"); + it->mCount -= count; + if (it->mCount == 0) + out.erase(it); + found = true; + break; + } + } + if (!found) + throw std::runtime_error("Can't find borrowed item to return"); + } + + void TradeItemModel::borrowItemFromUs (ModelIndex itemIndex, size_t count) + { + ItemStack item = getItem(itemIndex); + item.mCount = count; + borrowImpl(item, mBorrowedFromUs); + } + + void TradeItemModel::borrowItemToUs (ModelIndex itemIndex, ItemModel* source, size_t count) + { + ItemStack item = source->getItem(itemIndex); + item.mCount = count; + borrowImpl(item, mBorrowedToUs); + } + + void TradeItemModel::returnItemBorrowedToUs (ModelIndex itemIndex, size_t count) + { + ItemStack item = getItem(itemIndex); + unborrowImpl(item, count, mBorrowedToUs); + } + + void TradeItemModel::returnItemBorrowedFromUs (ModelIndex itemIndex, ItemModel* source, size_t count) + { + ItemStack item = source->getItem(itemIndex); + unborrowImpl(item, count, mBorrowedFromUs); + } + + void TradeItemModel::abort() + { + mBorrowedFromUs.clear(); + mBorrowedToUs.clear(); + } + + std::vector TradeItemModel::getItemsBorrowedToUs() + { + return mBorrowedToUs; + } + + void TradeItemModel::transferItems() + { + std::vector::iterator it = mBorrowedToUs.begin(); + for (; it != mBorrowedToUs.end(); ++it) + { + // get index in the source model + ItemModel* sourceModel = it->mCreator; + size_t i=0; + for (; igetItemCount(); ++i) + { + if (it->stacks(sourceModel->getItem(i))) + break; + } + if (i == sourceModel->getItemCount()) + throw std::runtime_error("The borrowed item disappeared"); + + // reset owner before copying + const ItemStack& item = sourceModel->getItem(i); + std::string owner = item.mBase.getCellRef().mOwner; + if (mMerchant.isEmpty()) // only for items bought by player + item.mBase.getCellRef().mOwner = ""; + // copy the borrowed items to our model + copyItem(item, it->mCount); + item.mBase.getCellRef().mOwner = owner; + // then remove them from the source model + sourceModel->removeItem(item, it->mCount); + } + mBorrowedToUs.clear(); + mBorrowedFromUs.clear(); + } + + void TradeItemModel::update() + { + mSourceModel->update(); + + int services = 0; + if (!mMerchant.isEmpty()) + services = MWWorld::Class::get(mMerchant).getServices(mMerchant); + + mItems.clear(); + // add regular items + for (size_t i=0; igetItemCount(); ++i) + { + ItemStack item = mSourceModel->getItem(i); + MWWorld::Ptr base = item.mBase; + if (!mMerchant.isEmpty() && Misc::StringUtils::ciEqual(base.getCellRef().mRefID, "gold_001")) + continue; + if (!mMerchant.isEmpty() && !MWWorld::Class::get(base).canSell(base, services)) + continue; + + // don't show equipped items + if (mMerchant.getTypeName() == typeid(ESM::NPC).name()) + { + bool isEquipped = false; + MWWorld::InventoryStore& store = MWWorld::Class::get(mMerchant).getInventoryStore(mMerchant); + for (int slot=0; slot::iterator it = mBorrowedFromUs.begin(); + for (; it != mBorrowedFromUs.end(); ++it) + { + if (it->stacks(item)) + { + if (item.mCount < it->mCount) + throw std::runtime_error("Lent more items than present"); + item.mCount -= it->mCount; + } + } + + if (item.mCount > 0) + mItems.push_back(item); + } + + // add items borrowed to us + std::vector::iterator it = mBorrowedToUs.begin(); + for (; it != mBorrowedToUs.end(); ++it) + { + ItemStack item = *it; + item.mType = ItemStack::Type_Barter; + mItems.push_back(item); + } + } + +} diff --git a/apps/openmw/mwgui/tradeitemmodel.hpp b/apps/openmw/mwgui/tradeitemmodel.hpp new file mode 100644 index 000000000..5cfaaafc7 --- /dev/null +++ b/apps/openmw/mwgui/tradeitemmodel.hpp @@ -0,0 +1,53 @@ +#ifndef MWGUI_TRADE_ITEM_MODEL_H +#define MWGUI_TRADE_ITEM_MODEL_H + +#include "itemmodel.hpp" + +namespace MWGui +{ + + class ItemModel; + + /// @brief An item model that allows 'borrowing' items from another item model. Used for previewing barter offers. + /// Also filters items that the merchant does not sell. + class TradeItemModel : public ProxyItemModel + { + public: + TradeItemModel (ItemModel* sourceModel, const MWWorld::Ptr& merchant); + + virtual ItemStack getItem (ModelIndex index); + virtual size_t getItemCount(); + + virtual void update(); + + void borrowItemFromUs (ModelIndex itemIndex, size_t count); + + void borrowItemToUs (ModelIndex itemIndex, ItemModel* source, size_t count); + ///< @note itemIndex points to an item in \a source + + void returnItemBorrowedToUs (ModelIndex itemIndex, size_t count); + + void returnItemBorrowedFromUs (ModelIndex itemIndex, ItemModel* source, size_t count); + + /// Permanently transfers items that were borrowed to us from another model to this model + void transferItems (); + /// Aborts trade + void abort(); + + std::vector getItemsBorrowedToUs(); + + private: + void borrowImpl(const ItemStack& item, std::vector& out); + void unborrowImpl(const ItemStack& item, size_t count, std::vector& out); + + std::vector mItems; + + std::vector mBorrowedToUs; + std::vector mBorrowedFromUs; + + MWWorld::Ptr mMerchant; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 23bb6554f..42e92ff0c 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -10,6 +10,8 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" @@ -17,28 +19,24 @@ #include "../mwworld/player.hpp" #include "inventorywindow.hpp" +#include "itemview.hpp" +#include "sortfilteritemmodel.hpp" +#include "containeritemmodel.hpp" +#include "tradeitemmodel.hpp" +#include "countdialog.hpp" namespace MWGui { const float TradeWindow::sBalanceChangeInitialPause = 0.5; const float TradeWindow::sBalanceChangeInterval = 0.1; - TradeWindow::TradeWindow() : - WindowBase("openmw_trade_window.layout") - , ContainerBase(NULL) // no drag&drop + TradeWindow::TradeWindow() + : WindowBase("openmw_trade_window.layout") , mCurrentBalance(0) , mBalanceButtonsState(BBS_None) , mBalanceChangePause(0.0) + , mItemToSell(-1) { - // items the NPC is wearing should not be for trade - mDisplayEquippedItems = false; - - MyGUI::ScrollView* itemView; - MyGUI::Widget* containerWidget; - getWidget(containerWidget, "Items"); - getWidget(itemView, "ItemView"); - setWidgets(containerWidget, itemView); - getWidget(mFilterAll, "AllButton"); getWidget(mFilterWeapon, "WeaponButton"); getWidget(mFilterApparel, "ApparelButton"); @@ -56,6 +54,9 @@ namespace MWGui getWidget(mTotalBalanceLabel, "TotalBalanceLabel"); getWidget(mBottomPane, "BottomPane"); + getWidget(mItemView, "ItemView"); + mItemView->eventItemClicked += MyGUI::newDelegate(this, &TradeWindow::onItemSelected); + mFilterAll->setStateSelected(true); mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); @@ -73,40 +74,40 @@ namespace MWGui mDecreaseButton->eventMouseButtonReleased += MyGUI::newDelegate(this, &TradeWindow::onBalanceButtonReleased); setCoord(400, 0, 400, 300); - - static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &TradeWindow::onWindowResize); } - void TradeWindow::startTrade(MWWorld::Ptr actor) + void TradeWindow::startTrade(const MWWorld::Ptr& actor) { + mPtr = actor; setTitle(MWWorld::Class::get(actor).getName(actor)); mCurrentBalance = 0; mCurrentMerchantOffer = 0; - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->startTrade(); + std::vector itemSources; + MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); + // Important: actor goes last, so that items purchased by the merchant go into his inventory + itemSources.push_back(actor); - mBoughtItems.clear(); - - ContainerBase::openContainer(actor); + mTradeModel = new TradeItemModel(new ContainerItemModel(itemSources), mPtr); + mSortModel = new SortFilterItemModel(mTradeModel); + mItemView->setModel (mSortModel); updateLabels(); - - drawItems(); } void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) { if (_sender == mFilterAll) - setFilter(ContainerBase::Filter_All); + mSortModel->setCategory(SortFilterItemModel::Category_All); else if (_sender == mFilterWeapon) - setFilter(ContainerBase::Filter_Weapon); + mSortModel->setCategory(SortFilterItemModel::Category_Weapon); else if (_sender == mFilterApparel) - setFilter(ContainerBase::Filter_Apparel); + mSortModel->setCategory(SortFilterItemModel::Category_Apparel); else if (_sender == mFilterMagic) - setFilter(ContainerBase::Filter_Magic); + mSortModel->setCategory(SortFilterItemModel::Category_Magic); else if (_sender == mFilterMisc) - setFilter(ContainerBase::Filter_Misc); + mSortModel->setCategory(SortFilterItemModel::Category_Misc); mFilterAll->setStateSelected(false); mFilterWeapon->setStateSelected(false); @@ -115,18 +116,91 @@ namespace MWGui mFilterMisc->setStateSelected(false); static_cast(_sender)->setStateSelected(true); + + mItemView->update(); + } + + int TradeWindow::getMerchantServices() + { + return MWWorld::Class::get(mPtr).getServices(mPtr); + } + + void TradeWindow::onItemSelected (int index) + { + const ItemStack& item = mSortModel->getItem(index); + + MWWorld::Ptr object = item.mBase; + int count = item.mCount; + bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); + if (MyGUI::InputManager::getInstance().isControlPressed()) + count = 1; + + if (count > 1 && !shift) + { + CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); + std::string message = "#{sQuanityMenuMessage02}"; + dialog->open(MWWorld::Class::get(object).getName(object), message, count); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &TradeWindow::sellItem); + mItemToSell = mSortModel->mapToSource(index); + } + else + { + mItemToSell = mSortModel->mapToSource(index); + sellItem (NULL, count); + } + } + + void TradeWindow::sellItem(MyGUI::Widget* sender, int count) + { + const ItemStack& item = mTradeModel->getItem(mItemToSell); + std::string sound = MWWorld::Class::get(item.mBase).getDownSoundId(item.mBase); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + + TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + + if (item.mType == ItemStack::Type_Barter) + { + // this was an item borrowed to us by the player + mTradeModel->returnItemBorrowedToUs(mItemToSell, count); + playerTradeModel->returnItemBorrowedFromUs(mItemToSell, mTradeModel, count); + buyFromNpc(item.mBase, count, true); + } + else + { + // borrow item to player + playerTradeModel->borrowItemToUs(mItemToSell, mTradeModel, count); + mTradeModel->borrowItemFromUs(mItemToSell, count); + buyFromNpc(item.mBase, count, false); + } + + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); + mItemView->update(); + } + + void TradeWindow::borrowItem (int index, size_t count) + { + TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + mTradeModel->borrowItemToUs(index, playerTradeModel, count); + mItemView->update(); + sellToNpc(playerTradeModel->getItem(index).mBase, count, false); } - void TradeWindow::onWindowResize(MyGUI::Window* _sender) + void TradeWindow::returnItem (int index, size_t count) { - drawItems(); + TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + const ItemStack& item = playerTradeModel->getItem(index); + mTradeModel->returnItemBorrowedFromUs(index, playerTradeModel, count); + mItemView->update(); + sellToNpc(item.mBase, count, true); } void TradeWindow::addOrRemoveGold(int amount) { bool goldFound = false; MWWorld::Ptr gold; - MWWorld::ContainerStore& playerStore = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getContainerStore(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::ContainerStore& playerStore = MWWorld::Class::get(player).getContainerStore(player); for (MWWorld::ContainerStoreIterator it = playerStore.begin(); it != playerStore.end(); ++it) @@ -167,13 +241,15 @@ namespace MWGui void TradeWindow::onOfferButtonClicked(MyGUI::Widget* _sender) { + TradeItemModel* playerItemModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); + const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); // were there any items traded at all? - MWWorld::ContainerStore& playerBought = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getBoughtItems(); - MWWorld::ContainerStore& merchantBought = getBoughtItems(); - if (playerBought.begin() == playerBought.end() && merchantBought.begin() == merchantBought.end()) + std::vector playerBought = playerItemModel->getItemsBorrowedToUs(); + std::vector merchantBought = mTradeModel->getItemsBorrowedToUs(); + if (!playerBought.size() && !merchantBought.size()) { // user notification MWBase::Environment::get().getWindowManager()-> @@ -250,21 +326,14 @@ namespace MWGui //skill use! MWWorld::Class::get(playerPtr).skillUsageSucceeded(playerPtr, ESM::Skill::Mercantile, 0); - } + } int iBarterSuccessDisposition = gmst.find("iBarterSuccessDisposition")->getInt(); MWBase::Environment::get().getDialogueManager()->applyTemporaryDispositionChange(iBarterSuccessDisposition); - // success! make the item transfer. - MWWorld::ContainerStore& playerBoughtItems = - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getBoughtItems(); - for (MWWorld::ContainerStoreIterator it = playerBoughtItems.begin(); it != playerBoughtItems.end(); ++it) - { - if (Misc::StringUtils::ciEqual(it->getCellRef().mOwner, MWWorld::Class::get(mPtr).getId(mPtr))) - it->getCellRef().mOwner = ""; - } - transferBoughtItems(); - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->transferBoughtItems(); + // make the item transfer + mTradeModel->transferItems(); + playerItemModel->transferItems(); // add or remove gold from the player. if (mCurrentBalance != 0) @@ -278,11 +347,8 @@ namespace MWGui void TradeWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { - // i give you back your stuff! - returnBoughtItems(MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getContainerStore()); - // now gimme back my stuff! - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->returnBoughtItems(MWWorld::Class::get(mPtr).getContainerStore(mPtr)); - + mTradeModel->abort(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel()->abort(); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); } @@ -343,44 +409,7 @@ namespace MWGui mMerchantGold->setCaptionWithReplacing("#{sSellerGold} " + boost::lexical_cast(getMerchantGold())); } - bool TradeWindow::npcAcceptsItem(MWWorld::Ptr item) - { - if (Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_001")) - return false; - - int services = 0; - if (mPtr.getTypeName() == typeid(ESM::NPC).name()) - { - MWWorld::LiveCellRef* ref = mPtr.get(); - if (ref->mBase->mHasAI) - services = ref->mBase->mAiData.mServices; - } - else if (mPtr.getTypeName() == typeid(ESM::Creature).name()) - { - MWWorld::LiveCellRef* ref = mPtr.get(); - if (ref->mBase->mHasAI) - services = ref->mBase->mAiData.mServices; - } - - return MWWorld::Class::get(item).canSell(item, services); - } - - std::vector TradeWindow::itemsToIgnore() - { - std::vector items; - MWWorld::ContainerStore& invStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); - - for (MWWorld::ContainerStoreIterator it = invStore.begin(); - it != invStore.end(); ++it) - { - if (!npcAcceptsItem(*it)) - items.push_back(*it); - } - - return items; - } - - void TradeWindow::sellToNpc(MWWorld::Ptr item, int count, bool boughtItem) + void TradeWindow::sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem) { int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count, boughtItem); @@ -390,7 +419,7 @@ namespace MWGui updateLabels(); } - void TradeWindow::buyFromNpc(MWWorld::Ptr item, int count, bool soldItem) + void TradeWindow::buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem) { int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count, !soldItem); diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 892ce0297..4e905915a 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -17,23 +17,32 @@ namespace MWGui namespace MWGui { - class TradeWindow : public ContainerBase, public WindowBase + class ItemView; + class SortFilterItemModel; + class TradeItemModel; + + class TradeWindow : public WindowBase, public ReferenceInterface { public: TradeWindow(); - void startTrade(MWWorld::Ptr actor); - - void sellToNpc(MWWorld::Ptr item, int count, bool boughtItem); ///< only used for adjusting the gold balance - void buyFromNpc(MWWorld::Ptr item, int count, bool soldItem); ///< only used for adjusting the gold balance - - bool npcAcceptsItem(MWWorld::Ptr item); + void startTrade(const MWWorld::Ptr& actor); void addOrRemoveGold(int gold); void onFrame(float frameDuration); - protected: + void borrowItem (int index, size_t count); + void returnItem (int index, size_t count); + + int getMerchantServices(); + + + private: + ItemView* mItemView; + SortFilterItemModel* mSortModel; + TradeItemModel* mTradeModel; + static const float sBalanceChangeInitialPause; // in seconds static const float sBalanceChangeInterval; // in seconds @@ -56,6 +65,8 @@ namespace MWGui MyGUI::TextBox* mPlayerGold; MyGUI::TextBox* mMerchantGold; + int mItemToSell; + int mCurrentBalance; int mCurrentMerchantOffer; @@ -67,7 +78,12 @@ namespace MWGui /// pause before next balance change will trigger while user holds +/- button pressed float mBalanceChangePause; - void onWindowResize(MyGUI::Window* _sender); + void sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem); ///< only used for adjusting the gold balance + void buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem); ///< only used for adjusting the gold balance + + void onItemSelected (int index); + void sellItem (MyGUI::Widget* sender, int count); + void onFilterChanged(MyGUI::Widget* _sender); void onOfferButtonClicked(MyGUI::Widget* _sender); void onCancelButtonClicked(MyGUI::Widget* _sender); @@ -79,16 +95,10 @@ namespace MWGui void onIncreaseButtonTriggered(); void onDecreaseButtonTriggered(); - virtual bool isTrading() { return true; } - virtual bool isTradeWindow() { return true; } - - virtual std::vector itemsToIgnore(); - void updateLabels(); virtual void onReferenceUnavailable(); - private: int getMerchantGold(); }; } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index f85a8be59..7ddac38f5 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -10,6 +10,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" #include "../mwmechanics/npcstats.hpp" diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 95b2bb8ca..83e158e4a 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -10,6 +10,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" #include "inventorywindow.hpp" #include "tradewindow.hpp" diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index dc05b8a9b..47e69517e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -5,6 +5,8 @@ #include "../mwbase/inputmanager.hpp" +#include "../mwworld/class.hpp" + #include "console.hpp" #include "journalwindow.hpp" #include "journalviewmodel.hpp" @@ -39,6 +41,7 @@ #include "companionwindow.hpp" #include "inventorywindow.hpp" #include "bookpage.hpp" +#include "itemview.hpp" namespace MWGui { @@ -123,6 +126,7 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); BookPage::registerMyGUIComponents (); + ItemView::registerComponents(); MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::ResourceManager::getInstance().load("core.xml"); @@ -302,6 +306,7 @@ namespace MWGui mMerchantRepair->setVisible(false); mRepair->setVisible(false); mCompanionWindow->setVisible(false); + mInventoryWindow->setTrading(false); mHud->setVisible(mHudEnabled); @@ -411,6 +416,7 @@ namespace MWGui break; case GM_Barter: mInventoryWindow->setVisible(true); + mInventoryWindow->setTrading(true); mTradeWindow->setVisible(true); break; case GM_SpellBuying: @@ -632,17 +638,6 @@ namespace MWGui return default_; } - void WindowManager::onDialogueWindowBye() - { - if (mDialogueWindow) - { - //FIXME set some state and stuff? - //removeDialog(dialogueWindow); - mDialogueWindow->setVisible(false); - } - removeGuiMode(GM_Dialogue); - } - void WindowManager::onFrame (float frameDuration) { mMessageBoxManager->onFrame(frameDuration); @@ -677,6 +672,7 @@ namespace MWGui mContainerWindow->checkReferenceAvailable(); mCompanionWindow->checkReferenceAvailable(); mConsole->checkReferenceAvailable(); + mCompanionWindow->onFrame(); } void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index f3413ca2f..48dd5309c 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -327,8 +327,6 @@ namespace MWGui unsigned int mTriangleCount; unsigned int mBatchCount; - void onDialogueWindowBye(); - /** * Called when MyGUI tries to retrieve a tag. This usually corresponds to a GMST string, * so this method will retrieve the GMST with the name \a _tag and place the result in \a _result diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 03a55c7f0..e9038f481 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -52,6 +52,11 @@ namespace MWWorld return false; } + int Class::getServices(const Ptr &actor) const + { + throw std::runtime_error ("class does not have services"); + } + MWMechanics::CreatureStats& Class::getCreatureStats (const Ptr& ptr) const { throw std::runtime_error ("class does not have creature stats"); @@ -172,6 +177,11 @@ namespace MWWorld throw std::runtime_error ("capacity not supported by this class"); } + float Class::getWeight(const Ptr &ptr) const + { + throw std::runtime_error ("weight not supported by this class"); + } + float Class::getEncumbrance (const MWWorld::Ptr& ptr) const { throw std::runtime_error ("encumbrance not supported by class"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index e203fedc3..ef8838c80 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -241,6 +241,8 @@ namespace MWWorld virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; ///< Determine whether or not \a item can be sold to an npc with the given \a npcServices + virtual int getServices (const MWWorld::Ptr& actor) const; + virtual std::string getModel(const MWWorld::Ptr &ptr) const; virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; @@ -249,6 +251,8 @@ namespace MWWorld ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. /// Second item in the pair specifies the error message + virtual float getWeight (const MWWorld::Ptr& ptr) const; + virtual Ptr copyToCell(const Ptr &ptr, CellStore &cell) const; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 5cf534239..6682f072a 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -60,8 +60,8 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { /// \todo add current enchantment charge here when it is implemented if ( ptr1.mCellRef->mRefID == ptr2.mCellRef->mRefID - && MWWorld::Class::get(ptr1).getScript(ptr1) == "" // item with a script never stacks - && MWWorld::Class::get(ptr1).getEnchantment(ptr1) == "" // item with enchantment never stacks (we could revisit this later, but for now it makes selecting items in the spell window much easier) + && MWWorld::Class::get(ptr1).getScript(ptr1) == "" // item with a script never stacks + && MWWorld::Class::get(ptr1).getEnchantment(ptr1) == "" // item with enchantment never stacks (we could revisit this later, but for now it makes selecting items in the spell window much easier) && ptr1.mCellRef->mOwner == ptr2.mCellRef->mOwner && ptr1.mCellRef->mSoul == ptr2.mCellRef->mSoul // item that is already partly used up never stacks diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index edd09411c..559463d46 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -79,11 +79,10 @@ namespace MWWorld ContainerStoreIterator addImpl (const Ptr& ptr); ///< Add the item to this container (no stacking) + public: + virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); ///< @return true if the two specified objects can stack with each other - /// @note ptr1 is the item that is already in this container - - public: void fill (const ESM::InventoryList& items, const std::string& owner, const MWWorld::ESMStore& store); ///< Insert items into *this. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index ac5586266..9d07ecb76 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -276,6 +276,8 @@ bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { if (*iter != end() && ptr1 == **iter) return false; + if (*iter != end() && ptr2 == **iter) + return false; } return true; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index cc28a0a0c..0801e04bc 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -90,8 +90,6 @@ namespace MWWorld ///< \attention This function is internal to the world model and should not be called from /// outside. - protected: - virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); ///< @return true if the two specified objects can stack with each other /// @note ptr1 is the item that is already in this container diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index facc4925d..9814ce53c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1653,4 +1653,22 @@ namespace MWWorld else return 0.f; } + + void World::getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) + { + std::string refId = npc.getCellRef().mRefID; + + const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); + for (Scene::CellStoreCollection::const_iterator cellIt = collection.begin(); cellIt != collection.end(); ++cellIt) + { + MWWorld::CellRefList& containers = (*cellIt)->mContainers; + CellRefList::List& refList = containers.mList; + for (CellRefList::List::iterator container = refList.begin(); container != refList.end(); ++container) + { + MWWorld::Ptr ptr (&*container, *cellIt); + if (Misc::StringUtils::ciEqual(ptr.getCellRef().mOwner, npc.getCellRef().mRefID)) + out.push_back(ptr); + } + } + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 2466edd0f..124f473e7 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -390,6 +390,9 @@ namespace MWWorld virtual bool getActorStandingOn (const MWWorld::Ptr& object); ///< @return true if any actor is standing on \a object virtual float getWindSpeed(); + virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out); + ///< get all containers in active cells owned by this Npc + virtual void setupExternalRendering (MWRender::ExternalRendering& rendering); virtual int canRest(); diff --git a/files/mygui/openmw_alchemy_window.layout b/files/mygui/openmw_alchemy_window.layout index 8471f69df..076a9293a 100644 --- a/files/mygui/openmw_alchemy_window.layout +++ b/files/mygui/openmw_alchemy_window.layout @@ -59,11 +59,7 @@ - - - - - + diff --git a/files/mygui/openmw_companion_window.layout b/files/mygui/openmw_companion_window.layout index 41a97a1ae..87aa01fd4 100644 --- a/files/mygui/openmw_companion_window.layout +++ b/files/mygui/openmw_companion_window.layout @@ -4,11 +4,7 @@ - - - - - + diff --git a/files/mygui/openmw_container_window.layout b/files/mygui/openmw_container_window.layout index 69961e9be..5c9327b1d 100644 --- a/files/mygui/openmw_container_window.layout +++ b/files/mygui/openmw_container_window.layout @@ -4,11 +4,7 @@ - - - - - + diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout index 09842f108..0f315b239 100644 --- a/files/mygui/openmw_inventory_window.layout +++ b/files/mygui/openmw_inventory_window.layout @@ -25,11 +25,7 @@ - - - - - + diff --git a/files/mygui/openmw_itemselection_dialog.layout b/files/mygui/openmw_itemselection_dialog.layout index 39c048303..003eb1c6a 100644 --- a/files/mygui/openmw_itemselection_dialog.layout +++ b/files/mygui/openmw_itemselection_dialog.layout @@ -4,11 +4,7 @@ - - - - - + diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 33a3ca40c..84ec6f7c5 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -7,7 +7,7 @@ - + diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 6631424cc..a5065c7ca 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -126,6 +126,15 @@ + + + + + + + + + diff --git a/files/mygui/openmw_trade_window.layout b/files/mygui/openmw_trade_window.layout index ecc794c92..a540c387e 100644 --- a/files/mygui/openmw_trade_window.layout +++ b/files/mygui/openmw_trade_window.layout @@ -24,11 +24,7 @@ - - - - - + diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 9537deb28..bbe633847 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -612,10 +612,13 @@ namespace Physic float d1 = 10000.; btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); - if(raycastingObjectOnly) resultCallback1.m_collisionFilterMask = CollisionType_Raycasting; - else resultCallback1.m_collisionFilterMask = CollisionType_World; + if(raycastingObjectOnly) + resultCallback1.m_collisionFilterMask = CollisionType_Raycasting; + else + resultCallback1.m_collisionFilterMask = CollisionType_World; - if(!ignoreHeightMap) resultCallback1.m_collisionFilterMask = resultCallback1.m_collisionFilterMask|| CollisionType_HeightMap; + if(!ignoreHeightMap) + resultCallback1.m_collisionFilterMask = resultCallback1.m_collisionFilterMask | CollisionType_HeightMap; dynamicsWorld->rayTest(from, to, resultCallback1); if (resultCallback1.hasHit()) {