diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 4ef4f93c7..5a9e1e99c 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -5,7 +5,7 @@ set (OPENCS_SRC model/doc/documentmanager.cpp model/doc/document.cpp model/world/universalid.cpp model/world/idcollection.cpp model/world/data.cpp model/world/idtable.cpp - model/world/commands.cpp model/world/idtableproxymodel.cpp + model/world/commands.cpp model/world/idtableproxymodel.cpp model/world/record.cpp view/doc/viewmanager.cpp view/doc/view.cpp view/doc/operations.cpp view/doc/operation.cpp diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 483ce929d..188d3a2ac 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -57,9 +57,14 @@ namespace CSMWorld return static_cast (record.mState); } + virtual void set (Record& record, const QVariant& data) + { + record.mState = static_cast (data.toInt()); + } + virtual bool isEditable() const { - return false; + return true; } }; } diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 7bb76acd0..96a1b639c 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -4,6 +4,7 @@ #include #include "idtableproxymodel.hpp" +#include "idtable.hpp" CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand *parent) @@ -38,4 +39,37 @@ void CSMWorld::CreateCommand::redo() void CSMWorld::CreateCommand::undo() { mModel.removeRow (mModel.getModelIndex (mId, 0).row()); +} + +CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +: QUndoCommand (parent), mModel (model), mId (id), mOld (0) +{ + setText (("Revert record " + id).c_str()); + + mOld = model.getRecord (id).clone(); +} + +CSMWorld::RevertCommand::~RevertCommand() +{ + delete mOld; +} + +void CSMWorld::RevertCommand::redo() +{ + QModelIndex index = mModel.getModelIndex (mId, 1); + RecordBase::State state = static_cast (mModel.data (index).toInt()); + + if (state==RecordBase::State_ModifiedOnly) + { + mModel.removeRows (index.row(), 1); + } + else + { + mModel.setData (index, static_cast (RecordBase::State_BaseOnly)); + } +} + +void CSMWorld::RevertCommand::undo() +{ + mModel.setRecord (*mOld); } \ No newline at end of file diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 50b9045c6..e916d05c9 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -15,6 +15,8 @@ class QAbstractItemModel; namespace CSMWorld { class IdTableProxyModel; + class IdTable; + class RecordBase; class ModifyCommand : public QUndoCommand { @@ -46,6 +48,27 @@ namespace CSMWorld virtual void undo(); }; + + class RevertCommand : public QUndoCommand + { + IdTable& mModel; + std::string mId; + RecordBase *mOld; + + // not implemented + RevertCommand (const RevertCommand&); + RevertCommand& operator= (const RevertCommand&); + + public: + + RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent = 0); + + virtual ~RevertCommand(); + + virtual void redo(); + + virtual void undo(); + }; } #endif \ No newline at end of file diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index e4bb31dbe..50afa715e 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -71,6 +71,25 @@ namespace CSMWorld virtual void removeRows (int index, int count) = 0; virtual void appendBlankRecord (const std::string& id) = 0; + + virtual int searchId (const std::string& id) const = 0; + ////< Search record with \a id. + /// \return index of record (if found) or -1 (not found) + + virtual void replace (int index, const RecordBase& record) = 0; + ///< If the record type does not match, an exception is thrown. + /// + /// \attention \a record must not change the ID. + + virtual void appendRecord (const RecordBase& record) = 0; + ///< If the record type does not match, an exception is thrown. + + virtual std::string getId (const RecordBase& record) const = 0; + ///< Return ID for \a record. + /// + /// \attention Throw san exception, if the type of \a record does not match. + + virtual const RecordBase& getRecord (const std::string& id) const = 0; }; ///< \brief Collection of ID-based records @@ -120,6 +139,25 @@ namespace CSMWorld virtual void appendBlankRecord (const std::string& id); + virtual int searchId (const std::string& id) const; + ////< Search record with \a id. + /// \return index of record (if found) or -1 (not found) + + virtual void replace (int index, const RecordBase& record); + ///< If the record type does not match, an exception is thrown. + /// + /// \attention \a record must not change the ID. + + virtual void appendRecord (const RecordBase& record); + ///< If the record type does not match, an exception is thrown. + + virtual std::string getId (const RecordBase& record) const; + ///< Return ID for \a record. + /// + /// \attention Throw san exception, if the type of \a record does not match. + + virtual const RecordBase& getRecord (const std::string& id) const; + void addColumn (Column *column); }; @@ -174,12 +212,12 @@ namespace CSMWorld template int IdCollection::getIndex (const std::string& id) const { - std::map::const_iterator iter = mIndex.find (id); + int index = searchId (id); - if (iter==mIndex.end()) + if (index==-1) throw std::runtime_error ("invalid ID: " + id); - return iter->second; + return index; } template @@ -268,6 +306,49 @@ namespace CSMWorld record.blank(); add (record); } + + template + int IdCollection::searchId (const std::string& id) const + { + std::string id2; + + std::transform (id.begin(), id.end(), std::back_inserter (id2), + (int(*)(int)) std::tolower); + + std::map::const_iterator iter = mIndex.find (id2); + + if (iter==mIndex.end()) + return -1; + + return iter->second; + } + + template + void IdCollection::replace (int index, const RecordBase& record) + { + mRecords.at (index) = dynamic_cast&> (record); + } + + template + void IdCollection::appendRecord (const RecordBase& record) + { + mRecords.push_back (dynamic_cast&> (record)); + mIndex.insert (std::make_pair (getId (record), mRecords.size()-1)); + } + + template + std::string IdCollection::getId (const RecordBase& record) const + { + const Record& record2 = dynamic_cast&> (record); + return (record2.isModified() ? record2.mModified : record2.mBase).mId; + } + + template + const RecordBase& IdCollection::getRecord (const std::string& id) const + { + int index = getIndex (id); + return mRecords.at (index); + } } #endif diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 50a6953d6..a2b0a3c1f 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -56,7 +56,10 @@ bool CSMWorld::IdTable::setData ( const QModelIndex &index, const QVariant &valu if (mIdCollection->isEditable (index.column()) && role==Qt::EditRole) { mIdCollection->setData (index.row(), index.column(), value); - emit dataChanged (index, index); + + emit dataChanged (CSMWorld::IdTable::index (index.row(), 0), + CSMWorld::IdTable::index (index.row(), mIdCollection->getColumns()-1)); + return true; } @@ -101,4 +104,31 @@ void CSMWorld::IdTable::addRecord (const std::string& id) QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const { return index (mIdCollection->getIndex (id), column); +} + +void CSMWorld::IdTable::setRecord (const RecordBase& record) +{ + int index = mIdCollection->searchId (mIdCollection->getId (record)); + + if (index==-1) + { + int index = mIdCollection->getSize(); + + beginInsertRows (QModelIndex(), index, index); + + mIdCollection->appendRecord (record); + + endInsertRows(); + } + else + { + mIdCollection->replace (index, record); + emit dataChanged (CSMWorld::IdTable::index (index, 0), + CSMWorld::IdTable::index (index, mIdCollection->getColumns()-1)); + } +} + +const CSMWorld::RecordBase& CSMWorld::IdTable::getRecord (const std::string& id) const +{ + return mIdCollection->getRecord (id); } \ No newline at end of file diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index f95873c7a..deaebaa38 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -6,6 +6,7 @@ namespace CSMWorld { class IdCollectionBase; + class RecordBase; class IdTable : public QAbstractTableModel { @@ -41,6 +42,11 @@ namespace CSMWorld void addRecord (const std::string& id); QModelIndex getModelIndex (const std::string& id, int column) const; + + void setRecord (const RecordBase& record); + ///< Add record or overwrite existing recrod. + + const RecordBase& getRecord (const std::string& id) const; }; } diff --git a/apps/opencs/model/world/record.cpp b/apps/opencs/model/world/record.cpp new file mode 100644 index 000000000..229985a8a --- /dev/null +++ b/apps/opencs/model/world/record.cpp @@ -0,0 +1,21 @@ + +#include "record.hpp" + +CSMWorld::RecordBase::~RecordBase() {} + +bool CSMWorld::RecordBase::RecordBase::isDeleted() const +{ + return mState==State_Deleted || mState==State_Erased; +} + + +bool CSMWorld::RecordBase::RecordBase::isErased() const +{ + return mState==State_Erased; +} + + +bool CSMWorld::RecordBase::RecordBase::isModified() const +{ + return mState==State_Modified || mState==State_ModifiedOnly; +} \ No newline at end of file diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index df93501f3..3b83836ab 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -15,20 +15,27 @@ namespace CSMWorld State_Deleted = 3, // exists in base, but has been deleted State_Erased = 4 // does not exist at all (we mostly treat that the same way as deleted) }; - }; - template - struct Record : public RecordBase - { - ESXRecordT mBase; - ESXRecordT mModified; State mState; + virtual ~RecordBase(); + + virtual RecordBase *clone() const = 0; + bool isDeleted() const; bool isErased() const; bool isModified() const; + }; + + template + struct Record : public RecordBase + { + ESXRecordT mBase; + ESXRecordT mModified; + + virtual RecordBase *clone() const; const ESXRecordT& get() const; ///< Throws an exception, if the record is deleted. @@ -44,21 +51,9 @@ namespace CSMWorld }; template - bool Record::isDeleted() const - { - return mState==State_Deleted || mState==State_Erased; - } - - template - bool Record::isErased() const - { - return mState==State_Erased; - } - - template - bool Record::isModified() const + RecordBase *Record::clone() const { - return mState==State_Modified || mState==State_ModifiedOnly; + return new Record (*this); } template diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 4f20d9fd2..dafcb95c0 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -11,6 +11,8 @@ #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtableproxymodel.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/record.hpp" namespace CSVWorld { @@ -108,11 +110,16 @@ void CSVWorld::CommandDelegate::setEditLock (bool locked) void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { - QMenu menu (this); + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + QMenu menu (this); if (mCreateAction) menu.addAction (mCreateAction); + if (selectedRows.size()>0) + menu.addAction (mRevertAction); + menu.exec (event->globalPos()); } @@ -120,9 +127,9 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q bool createAndDelete) : mUndoStack (undoStack), mCreateAction (0) { - QAbstractTableModel *model = data.getTableModel (id); + mModel = &dynamic_cast (*data.getTableModel (id)); - int columns = model->columnCount(); + int columns = mModel->columnCount(); for (int i=0; isetSourceModel (model); + mProxyModel = new CSMWorld::IdTableProxyModel (this); + mProxyModel->setSourceModel (mModel); - setModel (mModel); + setModel (mProxyModel); horizontalHeader()->setResizeMode (QHeaderView::Interactive); verticalHeader()->hide(); setSortingEnabled (true); @@ -149,6 +156,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q connect (mCreateAction, SIGNAL (triggered()), this, SLOT (createRecord())); addAction (mCreateAction); } + + mRevertAction = new QAction (tr ("Revert Record"), this); + connect (mRevertAction, SIGNAL (triggered()), this, SLOT (revertRecord())); + addAction (mRevertAction); } void CSVWorld::Table::setEditLock (bool locked) @@ -166,5 +177,35 @@ void CSVWorld::Table::createRecord() std::ostringstream stream; stream << "id" << index++; - mUndoStack.push (new CSMWorld::CreateCommand (*mModel, stream.str())); + mUndoStack.push (new CSMWorld::CreateCommand (*mProxyModel, stream.str())); +} + +void CSVWorld::Table::revertRecord() +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + std::vector revertableIds; + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); ++iter) + { + std::string id = mProxyModel->data (*iter).toString().toStdString(); + + CSMWorld::RecordBase::State state = + static_cast (mModel->data (mModel->getModelIndex (id, 1)).toInt()); + + if (state!=CSMWorld::RecordBase::State_BaseOnly) + revertableIds.push_back (id); + } + + if (revertableIds.size()>0) + { + if (revertableIds.size()>1) + mUndoStack.beginMacro (tr ("Revert multiple records")); + + for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) + mUndoStack.push (new CSMWorld::RevertCommand (*mModel, *iter)); + + if (revertableIds.size()>1) + mUndoStack.endMacro(); + } } \ No newline at end of file diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index f07f3ff24..022d4f12a 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -13,6 +13,7 @@ namespace CSMWorld class Data; class UniversalId; class IdTableProxyModel; + class IdTable; } namespace CSVWorld @@ -27,7 +28,9 @@ namespace CSVWorld std::vector mDelegates; QUndoStack& mUndoStack; QAction *mCreateAction; - CSMWorld::IdTableProxyModel *mModel; + QAction *mRevertAction; + CSMWorld::IdTableProxyModel *mProxyModel; + CSMWorld::IdTable *mModel; private: @@ -43,6 +46,8 @@ namespace CSVWorld private slots: void createRecord(); + + void revertRecord(); }; }