Refactor spell window to use model/view and remove duplicated code in QuickKeysMenu

This should also improve window resizing performance, the widgets are now just resized instead of recreated.
moveref
scrawl 10 years ago
parent 7abbca8be9
commit 79237d16a7

@ -40,7 +40,7 @@ add_openmw_dir (mwgui
merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks
itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview
)
add_openmw_dir (mwdialogue

@ -13,7 +13,6 @@
#include "../mwbase/world.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spells.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwgui/inventorywindow.hpp"
@ -23,7 +22,8 @@
#include "windowmanagerimp.hpp"
#include "itemselection.hpp"
#include "spellwindow.hpp"
#include "spellview.hpp"
#include "itemwidget.hpp"
#include "sortfilteritemmodel.hpp"
@ -495,13 +495,15 @@ namespace MWGui
MagicSelectionDialog::MagicSelectionDialog(QuickKeysMenu* parent)
: WindowModal("openmw_magicselection_dialog.layout")
, mParent(parent)
, mWidth(0)
, mHeight(0)
{
getWidget(mCancelButton, "CancelButton");
getWidget(mMagicList, "MagicList");
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onCancelButtonClicked);
mMagicList->setShowCostColumn(false);
mMagicList->setHighlightSelected(false);
mMagicList->eventSpellClicked += MyGUI::newDelegate(this, &MagicSelectionDialog::onModelIndexSelected);
center();
}
@ -519,211 +521,17 @@ namespace MWGui
{
WindowModal::open();
while (mMagicList->getChildCount ())
MyGUI::Gui::getInstance ().destroyWidget (mMagicList->getChildAt (0));
mHeight = 0;
const int spellHeight = 18;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
/// \todo lots of copy&pasted code from SpellWindow
// retrieve powers & spells, sort by name
std::vector<std::string> spellList;
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
{
spellList.push_back (it->first);
}
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
std::vector<std::string> powers;
std::vector<std::string>::iterator it = spellList.begin();
while (it != spellList.end())
{
const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
if (spell->mData.mType == ESM::Spell::ST_Power)
{
powers.push_back(*it);
it = spellList.erase(it);
}
else if (spell->mData.mType == ESM::Spell::ST_Ability
|| spell->mData.mType == ESM::Spell::ST_Blight
|| spell->mData.mType == ESM::Spell::ST_Curse
|| spell->mData.mType == ESM::Spell::ST_Disease)
{
it = spellList.erase(it);
}
else
++it;
}
std::sort(powers.begin(), powers.end(), sortSpells);
std::sort(spellList.begin(), spellList.end(), sortSpells);
// retrieve usable magic items & sort
std::vector<MWWorld::Ptr> items;
for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it)
{
std::string enchantId = it->getClass().getEnchantment(*it);
if (enchantId != "")
{
// only add items with "Cast once" or "Cast on use"
const ESM::Enchantment* enchant =
esmStore.get<ESM::Enchantment>().find(enchantId);
int type = enchant->mData.mType;
if (type != ESM::Enchantment::CastOnce
&& type != ESM::Enchantment::WhenUsed)
continue;
items.push_back(*it);
}
}
std::sort(items.begin(), items.end(), sortItems);
int height = estimateHeight(items.size() + powers.size() + spellList.size());
bool scrollVisible = height > mMagicList->getHeight();
mWidth = mMagicList->getWidth() - scrollVisible * 18;
// powers
addGroup("#{sPowers}", "");
for (std::vector<std::string>::const_iterator it = powers.begin(); it != powers.end(); ++it)
{
const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
MyGUI::Button* t = mMagicList->createWidget<MyGUI::Button>("SandTextButton",
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
t->setCaption(spell->mName);
t->setTextAlign(MyGUI::Align::Left);
t->setUserString("ToolTipType", "Spell");
t->setUserString("Spell", *it);
t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel);
t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onSpellSelected);
mHeight += spellHeight;
}
// other spells
addGroup("#{sSpells}", "");
for (std::vector<std::string>::const_iterator it = spellList.begin(); it != spellList.end(); ++it)
{
const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
MyGUI::Button* t = mMagicList->createWidget<MyGUI::Button>("SandTextButton",
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
t->setCaption(spell->mName);
t->setTextAlign(MyGUI::Align::Left);
t->setUserString("ToolTipType", "Spell");
t->setUserString("Spell", *it);
t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel);
t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onSpellSelected);
mHeight += spellHeight;
}
// enchanted items
addGroup("#{sMagicItem}", "");
for (std::vector<MWWorld::Ptr>::const_iterator it = items.begin(); it != items.end(); ++it)
{
MWWorld::Ptr item = *it;
// check if the item is currently equipped (will display in a different color)
bool equipped = false;
for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
{
if (store.getSlot(i) != store.end() && *store.getSlot(i) == item)
{
equipped = true;
break;
}
}
MyGUI::Button* t = mMagicList->createWidget<MyGUI::Button>(equipped ? "SandTextButton" : "SpellTextUnequipped",
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
t->setCaption(item.getClass().getName(item));
t->setTextAlign(MyGUI::Align::Left);
t->setUserData(item);
t->setUserString("ToolTipType", "ItemPtr");
t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onEnchantedItemSelected);
t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel);
mHeight += spellHeight;
}
// Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden
mMagicList->setVisibleVScroll(false);
mMagicList->setCanvasSize (mWidth, std::max(mMagicList->getHeight(), mHeight));
mMagicList->setVisibleVScroll(true);
mMagicList->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr()));
mMagicList->update();
}
void MagicSelectionDialog::addGroup(const std::string &label, const std::string& label2)
void MagicSelectionDialog::onModelIndexSelected(SpellModel::ModelIndex index)
{
if (mMagicList->getChildCount() > 0)
{
MyGUI::ImageBox* separator = mMagicList->createWidget<MyGUI::ImageBox>("MW_HLine",
MyGUI::IntCoord(4, mHeight, mWidth-8, 18),
MyGUI::Align::Left | MyGUI::Align::Top);
separator->setNeedMouseFocus(false);
mHeight += 18;
}
MyGUI::TextBox* groupWidget = mMagicList->createWidget<MyGUI::TextBox>("SandBrightText",
MyGUI::IntCoord(0, mHeight, mWidth, 24),
MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
groupWidget->setCaptionWithReplacing(label);
groupWidget->setTextAlign(MyGUI::Align::Left);
groupWidget->setNeedMouseFocus(false);
if (label2 != "")
{
MyGUI::TextBox* groupWidget2 = mMagicList->createWidget<MyGUI::TextBox>("SandBrightText",
MyGUI::IntCoord(0, mHeight, mWidth-4, 24),
MyGUI::Align::Left | MyGUI::Align::Top);
groupWidget2->setCaptionWithReplacing(label2);
groupWidget2->setTextAlign(MyGUI::Align::Right);
groupWidget2->setNeedMouseFocus(false);
}
mHeight += 24;
}
void MagicSelectionDialog::onMouseWheel(MyGUI::Widget* _sender, int _rel)
{
if (mMagicList->getViewOffset().top + _rel*0.3 > 0)
mMagicList->setViewOffset(MyGUI::IntPoint(0, 0));
const Spell& spell = mMagicList->getModel()->getItem(index);
if (spell.mType == Spell::Type_EnchantedItem)
mParent->onAssignMagicItem(spell.mItem);
else
mMagicList->setViewOffset(MyGUI::IntPoint(0, mMagicList->getViewOffset().top + _rel*0.3));
}
void MagicSelectionDialog::onEnchantedItemSelected(MyGUI::Widget* _sender)
{
MWWorld::Ptr item = *_sender->getUserData<MWWorld::Ptr>();
mParent->onAssignMagicItem (item);
}
void MagicSelectionDialog::onSpellSelected(MyGUI::Widget* _sender)
{
mParent->onAssignMagic (_sender->getUserString("Spell"));
}
int MagicSelectionDialog::estimateHeight(int numSpells) const
{
int height = 0;
height += 24 * 3 + 18 * 2; // group headings
height += numSpells * 18;
return height;
mParent->onAssignMagic(spell.mId);
}
}

@ -5,6 +5,8 @@
#include "windowbase.hpp"
#include "spellmodel.hpp"
namespace MWGui
{
@ -12,6 +14,7 @@ namespace MWGui
class ItemSelectionDialog;
class MagicSelectionDialog;
class ItemWidget;
class SpellView;
class QuickKeysMenu : public WindowBase
{
@ -94,21 +97,12 @@ namespace MWGui
private:
MyGUI::Button* mCancelButton;
MyGUI::ScrollView* mMagicList;
int mWidth;
int mHeight;
SpellView* mMagicList;
QuickKeysMenu* mParent;
void onCancelButtonClicked (MyGUI::Widget* sender);
void onMouseWheel(MyGUI::Widget* _sender, int _rel);
void onEnchantedItemSelected(MyGUI::Widget* _sender);
void onSpellSelected(MyGUI::Widget* _sender);
int estimateHeight(int numSpells) const;
void addGroup(const std::string& label, const std::string& label2);
void onModelIndexSelected(SpellModel::ModelIndex index);
};
}

@ -0,0 +1,140 @@
#include "spellmodel.hpp"
#include <boost/lexical_cast.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp"
namespace
{
bool sortSpells(const MWGui::Spell& left, const MWGui::Spell& right)
{
if (left.mType != right.mType)
return left.mType < right.mType;
int cmp = left.mName.compare(right.mName);
return cmp < 0;
}
}
namespace MWGui
{
SpellModel::SpellModel(const MWWorld::Ptr &actor)
: mActor(actor)
{
}
void SpellModel::update()
{
mSpells.clear();
MWMechanics::CreatureStats& stats = mActor.getClass().getCreatureStats(mActor);
const MWMechanics::Spells& spells = stats.getSpells();
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
{
const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(it->first);
if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell)
continue;
Spell newSpell;
newSpell.mName = spell->mName;
if (spell->mData.mType == ESM::Spell::ST_Spell)
{
newSpell.mType = Spell::Type_Spell;
std::string cost = boost::lexical_cast<std::string>(spell->mData.mCost);
std::string chance = boost::lexical_cast<std::string>(int(MWMechanics::getSpellSuccessChance(spell, mActor)));
newSpell.mCostColumn = cost + "/" + chance;
}
else
newSpell.mType = Spell::Type_Power;
newSpell.mId = it->first;
newSpell.mSelected = (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == it->first);
newSpell.mActive = true;
mSpells.push_back(newSpell);
}
MWWorld::InventoryStore& invStore = mActor.getClass().getInventoryStore(mActor);
for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it)
{
MWWorld::Ptr item = *it;
const std::string enchantId = item.getClass().getEnchantment(item);
if (enchantId.empty())
continue;
const ESM::Enchantment* enchant =
esmStore.get<ESM::Enchantment>().find(item.getClass().getEnchantment(item));
if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce)
continue;
Spell newSpell;
newSpell.mItem = item;
newSpell.mId = item.getClass().getId(item);
newSpell.mName = item.getClass().getName(item);
newSpell.mType = Spell::Type_EnchantedItem;
newSpell.mSelected = invStore.getSelectedEnchantItem() == it;
// FIXME: move to mwmechanics
if (enchant->mData.mType == ESM::Enchantment::CastOnce)
{
newSpell.mCostColumn = "100/100";
newSpell.mActive = false;
}
else
{
float enchantCost = enchant->mData.mCost;
int eSkill = mActor.getClass().getSkill(mActor, ESM::Skill::Enchant);
int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
std::string cost = boost::lexical_cast<std::string>(castCost);
int currentCharge = int(item.getCellRef().getEnchantmentCharge());
if (currentCharge == -1)
currentCharge = enchant->mData.mCharge;
std::string charge = boost::lexical_cast<std::string>(currentCharge);
newSpell.mCostColumn = cost + "/" + charge;
bool equipped = false;
for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
{
if (invStore.getSlot(i) != invStore.end() && *invStore.getSlot(i) == item)
{
equipped = true;
break;
}
}
newSpell.mActive = equipped;
}
mSpells.push_back(newSpell);
}
std::stable_sort(mSpells.begin(), mSpells.end(), sortSpells);
}
size_t SpellModel::getItemCount() const
{
return mSpells.size();
}
Spell SpellModel::getItem(ModelIndex index) const
{
if (index < 0 || index >= int(mSpells.size()))
throw std::runtime_error("invalid spell index supplied");
return mSpells[index];
}
}

@ -0,0 +1,56 @@
#ifndef OPENMW_GUI_SPELLMODEL_H
#define OPENMW_GUI_SPELLMODEL_H
#include "../mwworld/ptr.hpp"
namespace MWGui
{
struct Spell
{
enum Type
{
Type_Power,
Type_Spell,
Type_EnchantedItem
};
Type mType;
std::string mName;
std::string mCostColumn; // Cost/chance or Cost/charge
std::string mId; // Item ID or spell ID
MWWorld::Ptr mItem; // Only for Type_EnchantedItem
bool mSelected; // Is this the currently selected spell/item (only one can be selected at a time)
bool mActive; // (Items only) is the item equipped?
Spell()
: mSelected(false)
, mActive(false)
{
}
};
///@brief Model that lists all usable powers, spells and enchanted items for an actor.
class SpellModel
{
public:
SpellModel(const MWWorld::Ptr& actor);
typedef int ModelIndex;
void update();
Spell getItem (ModelIndex index) const;
///< throws for invalid index
size_t getItemCount() const;
private:
MWWorld::Ptr mActor;
std::vector<Spell> mSpells;
};
}
#endif

@ -0,0 +1,255 @@
#include "spellview.hpp"
#include <MyGUI_FactoryManager.h>
#include <MyGUI_ScrollView.h>
#include <MyGUI_ImageBox.h>
#include <MyGUI_Gui.h>
#include <components/widgets/sharedstatebutton.hpp>
namespace MWGui
{
SpellView::SpellView()
: mShowCostColumn(true)
, mHighlightSelected(true)
, mScrollView(NULL)
{
}
void SpellView::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 SpellView::registerComponents()
{
MyGUI::FactoryManager::getInstance().registerFactory<SpellView>("Widget");
}
void SpellView::setModel(SpellModel *model)
{
mModel.reset(model);
update();
}
SpellModel* SpellView::getModel()
{
return mModel.get();
}
void SpellView::setShowCostColumn(bool show)
{
if (show != mShowCostColumn)
{
mShowCostColumn = show;
update();
}
}
void SpellView::setHighlightSelected(bool highlight)
{
if (highlight != mHighlightSelected)
{
mHighlightSelected = highlight;
update();
}
}
void SpellView::update()
{
if (!mModel.get())
return;
mModel->update();
int curType = -1;
const int spellHeight = 18;
mLines.clear();
while (mScrollView->getChildCount())
MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0));
for (SpellModel::ModelIndex i = 0; i<int(mModel->getItemCount()); ++i)
{
const Spell& spell = mModel->getItem(i);
if (curType != spell.mType)
{
if (spell.mType == Spell::Type_Power)
addGroup("#{sPowers}", "");
else if (spell.mType == Spell::Type_Spell)
addGroup("#{sSpells}", "#{sCostChance}");
else
addGroup("#{sMagicItem}", "#{sCostCharge}");
curType = spell.mType;
}
const std::string skin = spell.mActive ? "SandTextButton" : "SpellTextUnequipped";
Gui::SharedStateButton* t = mScrollView->createWidget<Gui::SharedStateButton>(skin,
MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
t->setCaption(spell.mName);
t->setTextAlign(MyGUI::Align::Left);
adjustSpellWidget(spell, i, t);
if (!spell.mCostColumn.empty() && mShowCostColumn)
{
Gui::SharedStateButton* costChance = mScrollView->createWidget<Gui::SharedStateButton>(skin,
MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
costChance->setCaption(spell.mCostColumn);
costChance->setTextAlign(MyGUI::Align::Right);
adjustSpellWidget(spell, i, costChance);
Gui::ButtonGroup group;
group.push_back(t);
group.push_back(costChance);
Gui::SharedStateButton::createButtonGroup(group);
mLines.push_back(std::make_pair(t, costChance));
}
else
mLines.push_back(std::make_pair(t, (MyGUI::Widget*)NULL));
t->setStateSelected(spell.mSelected);
}
layoutWidgets();
}
void SpellView::layoutWidgets()
{
int height = 0;
for (std::vector< std::pair<MyGUI::Widget*, MyGUI::Widget*> >::iterator it = mLines.begin();
it != mLines.end(); ++it)
{
height += (it->first)->getHeight();
}
bool scrollVisible = height > mScrollView->getHeight();
int width = mScrollView->getWidth() - (scrollVisible ? 18 : 0);
height = 0;
for (std::vector< std::pair<MyGUI::Widget*, MyGUI::Widget*> >::iterator it = mLines.begin();
it != mLines.end(); ++it)
{
int lineHeight = (it->first)->getHeight();
(it->first)->setCoord(4, height, width-8, lineHeight);
if (it->second)
{
(it->second)->setCoord(4, height, width-8, lineHeight);
MyGUI::TextBox* second = (it->second)->castType<MyGUI::TextBox>(false);
if (second)
(it->first)->setSize(width-8-second->getTextSize().width, lineHeight);
}
height += lineHeight;
}
// Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden
mScrollView->setVisibleVScroll(false);
mScrollView->setCanvasSize(mScrollView->getWidth(), std::max(mScrollView->getHeight(), height));
mScrollView->setVisibleVScroll(true);
}
void SpellView::addGroup(const std::string &label, const std::string& label2)
{
if (mScrollView->getChildCount() > 0)
{
MyGUI::ImageBox* separator = mScrollView->createWidget<MyGUI::ImageBox>("MW_HLine",
MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 18),
MyGUI::Align::Left | MyGUI::Align::Top);
separator->setNeedMouseFocus(false);
mLines.push_back(std::make_pair(separator, (MyGUI::Widget*)NULL));
}
MyGUI::TextBox* groupWidget = mScrollView->createWidget<MyGUI::TextBox>("SandBrightText",
MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24),
MyGUI::Align::Left | MyGUI::Align::Top);
groupWidget->setCaptionWithReplacing(label);
groupWidget->setTextAlign(MyGUI::Align::Left);
groupWidget->setNeedMouseFocus(false);
if (label2 != "")
{
MyGUI::TextBox* groupWidget2 = mScrollView->createWidget<MyGUI::TextBox>("SandBrightText",
MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24),
MyGUI::Align::Left | MyGUI::Align::Top);
groupWidget2->setCaptionWithReplacing(label2);
groupWidget2->setTextAlign(MyGUI::Align::Right);
groupWidget2->setNeedMouseFocus(false);
mLines.push_back(std::make_pair(groupWidget, groupWidget2));
}
else
mLines.push_back(std::make_pair(groupWidget, (MyGUI::Widget*)NULL));
}
void SpellView::setSize(const MyGUI::IntSize &_value)
{
bool changed = (_value.width != getWidth() || _value.height != getHeight());
Base::setSize(_value);
if (changed)
layoutWidgets();
}
void SpellView::setSize(int _width, int _height)
{
setSize(MyGUI::IntSize(_width, _height));
}
void SpellView::setCoord(const MyGUI::IntCoord &_value)
{
bool changed = (_value.width != getWidth() || _value.height != getHeight());
Base::setCoord(_value);
if (changed)
layoutWidgets();
}
void SpellView::setCoord(int _left, int _top, int _width, int _height)
{
setCoord(MyGUI::IntCoord(_left, _top, _width, _height));
}
void SpellView::adjustSpellWidget(const Spell &spell, SpellModel::ModelIndex index, MyGUI::Widget *widget)
{
if (spell.mType == Spell::Type_EnchantedItem)
{
widget->setUserData(spell.mItem);
widget->setUserString("ToolTipType", "ItemPtr");
}
else
{
widget->setUserString("ToolTipType", "Spell");
widget->setUserString("Spell", spell.mId);
}
widget->setUserString("SpellModelIndex", MyGUI::utility::toString(index));
widget->eventMouseWheel += MyGUI::newDelegate(this, &SpellView::onMouseWheel);
widget->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellView::onSpellSelected);
}
void SpellView::onSpellSelected(MyGUI::Widget* _sender)
{
SpellModel::ModelIndex i = MyGUI::utility::parseInt(_sender->getUserString("SpellModelIndex"));
eventSpellClicked(i);
}
void SpellView::onMouseWheel(MyGUI::Widget* _sender, int _rel)
{
if (mScrollView->getViewOffset().top + _rel*0.3 > 0)
mScrollView->setViewOffset(MyGUI::IntPoint(0, 0));
else
mScrollView->setViewOffset(MyGUI::IntPoint(0, mScrollView->getViewOffset().top + _rel*0.3));
}
}

@ -0,0 +1,71 @@
#ifndef OPENMW_GUI_SPELLVIEW_H
#define OPENMW_GUI_SPELLVIEW_H
#include <MyGUI_Widget.h>
#include "spellmodel.hpp"
namespace MyGUI
{
class ScrollView;
}
namespace MWGui
{
class SpellModel;
///@brief Displays a SpellModel in a list widget
class SpellView : public MyGUI::Widget
{
MYGUI_RTTI_DERIVED(SpellView)
public:
SpellView();
/// Register needed components with MyGUI's factory manager
static void registerComponents ();
/// Should the cost/chance column be shown?
void setShowCostColumn(bool show);
void setHighlightSelected(bool highlight);
/// Takes ownership of \a model
void setModel (SpellModel* model);
SpellModel* getModel();
void update();
typedef MyGUI::delegates::CMultiDelegate1<SpellModel::ModelIndex> EventHandle_ModelIndex;
/// Fired when a spell was clicked
EventHandle_ModelIndex eventSpellClicked;
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);
private:
MyGUI::ScrollView* mScrollView;
std::auto_ptr<SpellModel> mModel;
std::vector< std::pair<MyGUI::Widget*, MyGUI::Widget*> > mLines;
bool mShowCostColumn;
bool mHighlightSelected;
void layoutWidgets();
void addGroup(const std::string& label1, const std::string& label2);
void adjustSpellWidget(const Spell& spell, SpellModel::ModelIndex index, MyGUI::Widget* widget);
void onSpellSelected(MyGUI::Widget* _sender);
void onMouseWheel(MyGUI::Widget* _sender, int _rel);
};
}
#endif

@ -1,10 +1,7 @@
#include "spellwindow.hpp"
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
#include <components/widgets/sharedstatebutton.hpp>
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -19,44 +16,24 @@
#include "spellicons.hpp"
#include "inventorywindow.hpp"
#include "confirmationdialog.hpp"
#include "spellview.hpp"
namespace MWGui
{
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
{
int cmp = left.getClass().getName(left).compare(
right.getClass().getName(right));
return cmp < 0;
}
bool sortSpells(const std::string& left, const std::string& right)
{
const MWWorld::Store<ESM::Spell> &spells =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>();
const ESM::Spell* a = spells.find(left);
const ESM::Spell* b = spells.find(right);
int cmp = a->mName.compare(b->mName);
return cmp < 0;
}
SpellWindow::SpellWindow(DragAndDrop* drag)
: WindowPinnableBase("openmw_spell_window.layout")
, NoDrop(drag, mMainWidget)
, mHeight(0)
, mWidth(0)
, mWindowSize(mMainWidget->getSize())
, mSpellView(NULL)
{
mSpellIcons = new SpellIcons();
getWidget(mSpellView, "SpellView");
getWidget(mEffectBox, "EffectsBox");
setCoord(498, 300, 302, 300);
mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected);
mMainWidget->castType<MyGUI::Window>()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize);
setCoord(498, 300, 302, 300);
}
SpellWindow::~SpellWindow()
@ -84,261 +61,14 @@ namespace MWGui
{
mSpellIcons->updateWidgets(mEffectBox, false);
const int spellHeight = 18;
mHeight = 0;
while (mSpellView->getChildCount())
MyGUI::Gui::getInstance().destroyWidget(mSpellView->getChildAt(0));
// retrieve all player spells, divide them into Powers and Spells and sort them
std::vector<std::string> spellList;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
MWMechanics::Spells& spells = stats.getSpells();
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
spellList.push_back (it->first);
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();
std::vector<std::string> powers;
std::vector<std::string>::iterator it = spellList.begin();
while (it != spellList.end())
{
const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
if (spell->mData.mType == ESM::Spell::ST_Power)
{
powers.push_back(*it);
it = spellList.erase(it);
}
else if (spell->mData.mType == ESM::Spell::ST_Ability
|| spell->mData.mType == ESM::Spell::ST_Blight
|| spell->mData.mType == ESM::Spell::ST_Curse
|| spell->mData.mType == ESM::Spell::ST_Disease)
{
it = spellList.erase(it);
}
else
++it;
}
std::sort(powers.begin(), powers.end(), sortSpells);
std::sort(spellList.begin(), spellList.end(), sortSpells);
// retrieve player's enchanted items
std::vector<MWWorld::Ptr> items;
for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it)
{
std::string enchantId = it->getClass().getEnchantment(*it);
if (enchantId != "")
{
// only add items with "Cast once" or "Cast on use"
const ESM::Enchantment* enchant =
esmStore.get<ESM::Enchantment>().find(enchantId);
int type = enchant->mData.mType;
if (type != ESM::Enchantment::CastOnce
&& type != ESM::Enchantment::WhenUsed)
continue;
items.push_back(*it);
}
}
std::sort(items.begin(), items.end(), sortItems);
int height = estimateHeight(items.size() + powers.size() + spellList.size());
bool scrollVisible = height > mSpellView->getHeight();
mWidth = mSpellView->getWidth() - (scrollVisible ? 18 : 0);
// powers
addGroup("#{sPowers}", "");
for (std::vector<std::string>::const_iterator it = powers.begin(); it != powers.end(); ++it)
{
const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
MyGUI::Button* t = mSpellView->createWidget<MyGUI::Button>("SandTextButton",
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
t->setCaption(spell->mName);
t->setTextAlign(MyGUI::Align::Left);
t->setUserString("ToolTipType", "Spell");
t->setUserString("Spell", *it);
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
if (*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell())
t->setStateSelected(true);
mHeight += spellHeight;
}
// other spells
addGroup("#{sSpells}", "#{sCostChance}");
for (std::vector<std::string>::const_iterator it = spellList.begin(); it != spellList.end(); ++it)
{
const ESM::Spell* spell = esmStore.get<ESM::Spell>().find(*it);
Gui::SharedStateButton* t = mSpellView->createWidget<Gui::SharedStateButton>("SandTextButton",
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
t->setCaption(spell->mName);
t->setTextAlign(MyGUI::Align::Left);
t->setUserString("ToolTipType", "Spell");
t->setUserString("Spell", *it);
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
// cost / success chance
Gui::SharedStateButton* costChance = mSpellView->createWidget<Gui::SharedStateButton>("SandTextButton",
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
std::string cost = boost::lexical_cast<std::string>(spell->mData.mCost);
std::string chance = boost::lexical_cast<std::string>(int(MWMechanics::getSpellSuccessChance(*it, player)));
costChance->setCaption(cost + "/" + chance);
costChance->setTextAlign(MyGUI::Align::Right);
costChance->setUserString("ToolTipType", "Spell");
costChance->setUserString("Spell", *it);
costChance->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
costChance->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected);
t->setSize(mWidth-12-costChance->getTextSize().width, t->getHeight());
Gui::ButtonGroup group;
group.push_back(t);
group.push_back(costChance);
Gui::SharedStateButton::createButtonGroup(group);
t->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell());
mHeight += spellHeight;
}
// enchanted items
addGroup("#{sMagicItem}", "#{sCostCharge}");
for (std::vector<MWWorld::Ptr>::const_iterator it = items.begin(); it != items.end(); ++it)
{
MWWorld::Ptr item = *it;
const ESM::Enchantment* enchant =
esmStore.get<ESM::Enchantment>().find(item.getClass().getEnchantment(item));
// check if the item is currently equipped (will display in a different color)
bool equipped = false;
for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
{
if (store.getSlot(i) != store.end() && *store.getSlot(i) == item)
{
equipped = true;
break;
}
}
Gui::SharedStateButton* t = mSpellView->createWidget<Gui::SharedStateButton>(equipped ? "SandTextButton" : "SpellTextUnequipped",
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
t->setCaption(item.getClass().getName(item));
t->setTextAlign(MyGUI::Align::Left);
t->setUserData(item);
t->setUserString("ToolTipType", "ItemPtr");
t->setUserString("Equipped", equipped ? "true" : "false");
t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected);
t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
// cost / charge
Gui::SharedStateButton* costCharge = mSpellView->createWidget<Gui::SharedStateButton>(equipped ? "SandTextButton" : "SpellTextUnequipped",
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
float enchantCost = enchant->mData.mCost;
int eSkill = player.getClass().getSkill(player, ESM::Skill::Enchant);
int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
std::string cost = boost::lexical_cast<std::string>(castCost);
int currentCharge = int(item.getCellRef().getEnchantmentCharge());
if (currentCharge == -1)
currentCharge = enchant->mData.mCharge;
std::string charge = boost::lexical_cast<std::string>(currentCharge);
if (enchant->mData.mType == ESM::Enchantment::CastOnce)
{
// this is Morrowind behaviour
cost = "100";
charge = "100";
}
costCharge->setUserData(item);
costCharge->setUserString("ToolTipType", "ItemPtr");
costCharge->setUserString("Equipped", equipped ? "true" : "false");
costCharge->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected);
costCharge->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel);
costCharge->setCaption(cost + "/" + charge);
costCharge->setTextAlign(MyGUI::Align::Right);
Gui::ButtonGroup group;
group.push_back(t);
group.push_back(costCharge);
Gui::SharedStateButton::createButtonGroup(group);
if (store.getSelectedEnchantItem() != store.end())
t->setStateSelected(item == *store.getSelectedEnchantItem());
t->setSize(mWidth-12-costCharge->getTextSize().width, t->getHeight());
mHeight += spellHeight;
}
// Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden
mSpellView->setVisibleVScroll(false);
mSpellView->setCanvasSize(mSpellView->getWidth(), std::max(mSpellView->getHeight(), mHeight));
mSpellView->setVisibleVScroll(true);
}
void SpellWindow::addGroup(const std::string &label, const std::string& label2)
{
if (mSpellView->getChildCount() > 0)
{
MyGUI::ImageBox* separator = mSpellView->createWidget<MyGUI::ImageBox>("MW_HLine",
MyGUI::IntCoord(4, mHeight, mWidth-8, 18),
MyGUI::Align::Left | MyGUI::Align::Top);
separator->setNeedMouseFocus(false);
mHeight += 18;
}
MyGUI::TextBox* groupWidget = mSpellView->createWidget<MyGUI::TextBox>("SandBrightText",
MyGUI::IntCoord(0, mHeight, mWidth, 24),
MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch);
groupWidget->setCaptionWithReplacing(label);
groupWidget->setTextAlign(MyGUI::Align::Left);
groupWidget->setNeedMouseFocus(false);
if (label2 != "")
{
MyGUI::TextBox* groupWidget2 = mSpellView->createWidget<MyGUI::TextBox>("SandBrightText",
MyGUI::IntCoord(0, mHeight, mWidth-4, 24),
MyGUI::Align::Left | MyGUI::Align::Top);
groupWidget2->setCaptionWithReplacing(label2);
groupWidget2->setTextAlign(MyGUI::Align::Right);
groupWidget2->setNeedMouseFocus(false);
groupWidget->setSize(mWidth-8-groupWidget2->getTextSize().width, groupWidget->getHeight());
}
mHeight += 24;
mSpellView->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr()));
mSpellView->update();
}
void SpellWindow::onWindowResize(MyGUI::Window* _sender)
{
if (mMainWidget->getSize() != mWindowSize)
{
mWindowSize = mMainWidget->getSize();
updateSpells();
}
}
void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender)
void SpellWindow::onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped)
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
MWWorld::Ptr item = *_sender->getUserData<MWWorld::Ptr>();
// retrieve ContainerStoreIterator to the item
MWWorld::ContainerStoreIterator it = store.begin();
@ -352,7 +82,7 @@ namespace MWGui
assert(it != store.end());
// equip, if it can be equipped and is not already equipped
if (_sender->getUserString("Equipped") == "false"
if (!alreadyEquipped
&& !item.getClass().getEquipmentSlots(item).first.empty())
{
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item);
@ -364,12 +94,21 @@ namespace MWGui
updateSpells();
}
void SpellWindow::onSpellSelected(MyGUI::Widget* _sender)
void SpellWindow::onModelIndexSelected(SpellModel::ModelIndex index)
{
std::string spellId = _sender->getUserString("Spell");
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
const Spell& spell = mSpellView->getModel()->getItem(index);
if (spell.mType == Spell::Type_EnchantedItem)
{
onEnchantedItemSelected(spell.mItem, spell.mActive);
}
else
{
onSpellSelected(spell.mId);
}
}
void SpellWindow::onSpellSelected(const std::string& spellId)
{
if (MyGUI::InputManager::getInstance().isShiftPressed())
{
// delete spell, if allowed
@ -396,6 +135,8 @@ namespace MWGui
}
else
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
store.setSelectedEnchantItem(store.end());
MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player)));
}
@ -403,22 +144,6 @@ namespace MWGui
updateSpells();
}
int SpellWindow::estimateHeight(int numSpells) const
{
int height = 0;
height += 24 * 3 + 18 * 2; // group headings
height += numSpells * 18;
return height;
}
void SpellWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel)
{
if (mSpellView->getViewOffset().top + _rel*0.3 > 0)
mSpellView->setViewOffset(MyGUI::IntPoint(0, 0));
else
mSpellView->setViewOffset(MyGUI::IntPoint(0, mSpellView->getViewOffset().top + _rel*0.3));
}
void SpellWindow::onDeleteSpellAccept()
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();

@ -4,13 +4,12 @@
#include "windowpinnablebase.hpp"
#include "../mwworld/ptr.hpp"
#include "spellmodel.hpp"
namespace MWGui
{
class SpellIcons;
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right);
bool sortSpells(const std::string& left, const std::string& right);
class SpellView;
class SpellWindow : public WindowPinnableBase, public NoDrop
{
@ -23,30 +22,20 @@ namespace MWGui
void onFrame(float dt) { NoDrop::onFrame(dt); }
protected:
MyGUI::ScrollView* mSpellView;
MyGUI::Widget* mEffectBox;
int mHeight;
int mWidth;
MyGUI::IntSize mWindowSize;
std::string mSpellToDelete;
void addGroup(const std::string& label, const std::string& label2);
int estimateHeight(int numSpells) const;
void onWindowResize(MyGUI::Window* _sender);
void onEnchantedItemSelected(MyGUI::Widget* _sender);
void onSpellSelected(MyGUI::Widget* _sender);
void onMouseWheel(MyGUI::Widget* _sender, int _rel);
void onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped);
void onSpellSelected(const std::string& spellId);
void onModelIndexSelected(SpellModel::ModelIndex index);
void onDeleteSpellAccept();
virtual void onPinToggled();
virtual void onTitleDoubleClicked();
virtual void open();
SpellView* mSpellView;
SpellIcons* mSpellIcons;
};
}

@ -73,6 +73,7 @@
#include "itemwidget.hpp"
#include "screenfader.hpp"
#include "debugwindow.hpp"
#include "spellview.hpp"
namespace MWGui
{
@ -179,6 +180,7 @@ namespace MWGui
BookPage::registerMyGUIComponents ();
ItemView::registerComponents();
ItemWidget::registerComponents();
SpellView::registerComponents();
Gui::registerAllWidgets();
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Controllers::ControllerRepeatEvent>("Controller");

@ -157,6 +157,15 @@
</Child>
</Resource>
<Resource type="ResourceSkin" name="MW_SpellView" size="516 516" align="Left Top">
<Child type="Widget" skin="MW_Box" offset="0 0 516 516" align="Stretch"/>
<Child type="ScrollView" skin="MW_ScrollView" offset="3 3 509 509" align="Stretch" name="ScrollView">
<Property key="CanvasAlign" value="Left Top"/>
<Property key="NeedMouse" value="true"/>
</Child>
</Resource>
<Resource type="ResourceSkin" name="MW_SimpleList" size="516 516" align="Left Top">
<Property key="ListItemSkin" value="MW_ListLine"/>

@ -3,12 +3,10 @@
<Widget type="Window" skin="MW_Dialog" position="0 0 330 370" layer="Windows" name="_Main">
<Widget type="TextBox" skin="SandText" position="8 8 292 24">
<Property key="Caption" value="Select a magic to quick key."/>
<Property key="Caption" value="#{sMagicSelectTitle}"/>
</Widget>
<Widget type="Widget" skin="MW_Box" position="8 38 306 285" name="box" align="Left Top Stretch">
<Widget type="ScrollView" skin="MW_ScrollView" position="4 4 298 277" name="MagicList" align="Left Top Stretch">
</Widget>
<Widget type="SpellView" skin="MW_SpellView" position="8 38 306 285" align="Left Top Stretch" name="MagicList">
</Widget>
<Widget type="AutoSizedButton" skin="MW_Button" name="CancelButton" position="284 330 32 24" align="Right Bottom">

@ -10,10 +10,7 @@
</Widget>
<!-- Spell list -->
<Widget type="Widget" skin="MW_Box" position="8 38 268 518" align="Left Top Stretch">
<Widget type="ScrollView" skin="MW_ScrollView" position="4 4 260 510" align="Left Top Stretch" name="SpellView">
<Property key="CanvasAlign" value="Left Top"/>
</Widget>
<Widget type="SpellView" skin="MW_SpellView" position="8 38 268 518" align="Left Top Stretch" name="SpellView">
</Widget>
</Widget>

Loading…
Cancel
Save