diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index dd31aa81b..ddaca7e71 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -18,7 +18,7 @@ opencs_hdrs_noqt (model/doc opencs_units (model/world - idtable idtableproxymodel regionmap data + idtable idtableproxymodel regionmap data commanddispatcher ) diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp new file mode 100644 index 000000000..4e146d87c --- /dev/null +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -0,0 +1,267 @@ + +#include "commanddispatcher.hpp" + +#include + +#include + +#include "../doc/document.hpp" + +#include "idtable.hpp" +#include "record.hpp" +#include "commands.hpp" + +std::vector CSMWorld::CommandDispatcher::getDeletableRecords() const +{ + std::vector result; + + IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + + int stateColumnIndex = model.findColumnIndex (Columns::ColumnId_Modification); + + for (std::vector::const_iterator iter (mSelection.begin()); + iter!=mSelection.end(); ++iter) + { + int row = model.getModelIndex (*iter, 0).row(); + + // check record state + RecordBase::State state = static_cast ( + model.data (model.index (row, stateColumnIndex)).toInt()); + + if (state==RecordBase::State_Deleted) + continue; + + // check other columns (only relevant for a subset of the tables) + int dialogueTypeIndex = model.searchColumnIndex (Columns::ColumnId_DialogueType); + + if (dialogueTypeIndex!=-1) + { + int type = model.data (model.index (row, dialogueTypeIndex)).toInt(); + + if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) + continue; + } + + result.push_back (*iter); + } + + return result; +} + +std::vector CSMWorld::CommandDispatcher::getRevertableRecords() const +{ + std::vector result; + + IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + + /// \todo Reverting temporarily disabled on tables that support reordering, because + /// revert logic currently can not handle reordering. + if (model.getFeatures() & IdTable::Feature_ReorderWithinTopic) + return result; + + int stateColumnIndex = model.findColumnIndex (Columns::ColumnId_Modification); + + for (std::vector::const_iterator iter (mSelection.begin()); + iter!=mSelection.end(); ++iter) + { + int row = model.getModelIndex (*iter, 0).row(); + + // check record state + RecordBase::State state = static_cast ( + model.data (model.index (row, stateColumnIndex)).toInt()); + + if (state==RecordBase::State_BaseOnly) + continue; + + result.push_back (*iter); + } + + return result; +} + +CSMWorld::CommandDispatcher::CommandDispatcher (CSMDoc::Document& document, + const CSMWorld::UniversalId& id, QObject *parent) +: QObject (parent), mDocument (document), mId (id), mLocked (false) +{} + +void CSMWorld::CommandDispatcher::setEditLock (bool locked) +{ + mLocked = locked; +} + +void CSMWorld::CommandDispatcher::setSelection (const std::vector& selection) +{ + mSelection = selection; + std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::toLower); + std::sort (mSelection.begin(), mSelection.end()); +} + +void CSMWorld::CommandDispatcher::setExtendedTypes (const std::vector& types) +{ + mExtendedTypes = types; +} + +bool CSMWorld::CommandDispatcher::canDelete() const +{ + if (mLocked) + return false; + + return getDeletableRecords().size()!=0; +} + +bool CSMWorld::CommandDispatcher::canRevert() const +{ + if (mLocked) + return false; + + return getRevertableRecords().size()!=0; +} + +std::vector CSMWorld::CommandDispatcher::getExtendedTypes() const +{ + std::vector tables; + + if (mId==UniversalId::Type_Cells) + { + tables.push_back (mId); + tables.push_back (UniversalId::Type_References); + /// \todo add other cell-specific types + } + + return tables; +} + +void CSMWorld::CommandDispatcher::executeDelete() +{ + if (mLocked) + return; + + std::vector rows = getDeletableRecords(); + + if (rows.empty()) + return; + + IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + + int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); + + if (rows.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Delete multiple records")); + + for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + { + std::string id = model.data (model.getModelIndex (*iter, columnIndex)). + toString().toUtf8().constData(); + + mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (model, id)); + } + + if (rows.size()>1) + mDocument.getUndoStack().endMacro(); +} + +void CSMWorld::CommandDispatcher::executeRevert() +{ + if (mLocked) + return; + + std::vector rows = getRevertableRecords(); + + if (rows.empty()) + return; + + IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + + int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); + + if (rows.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Revert multiple records")); + + for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + { + std::string id = model.data (model.getModelIndex (*iter, columnIndex)). + toString().toUtf8().constData(); + + mDocument.getUndoStack().push (new CSMWorld::RevertCommand (model, id)); + } + + if (rows.size()>1) + mDocument.getUndoStack().endMacro(); +} + +void CSMWorld::CommandDispatcher::executeExtendedDelete() +{ + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Extended delete of multiple records")); + + for (std::vector::const_iterator iter (mExtendedTypes.begin()); + iter!=mExtendedTypes.end(); ++iter) + { + if (*iter==mId) + executeDelete(); + else if (*iter==UniversalId::Type_References) + { + IdTable& model = dynamic_cast ( + *mDocument.getData().getTableModel (*iter)); + + const RefCollection& collection = mDocument.getData().getReferences(); + + int size = collection.getSize(); + + for (int i=size-1; i>=0; --i) + { + const Record& record = collection.getRecord (i); + + if (record.mState==RecordBase::State_Deleted) + continue; + + if (!std::binary_search (mSelection.begin(), mSelection.end(), + Misc::StringUtils::lowerCase (record.get().mCell))) + continue; + + mDocument.getUndoStack().push ( + new CSMWorld::DeleteCommand (model, record.get().mId)); + } + } + } + + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().endMacro(); +} + +void CSMWorld::CommandDispatcher::executeExtendedRevert() +{ + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Extended revert of multiple records")); + + for (std::vector::const_iterator iter (mExtendedTypes.begin()); + iter!=mExtendedTypes.end(); ++iter) + { + if (*iter==mId) + executeRevert(); + else if (*iter==UniversalId::Type_References) + { + IdTable& model = dynamic_cast ( + *mDocument.getData().getTableModel (*iter)); + + const RefCollection& collection = mDocument.getData().getReferences(); + + int size = collection.getSize(); + + for (int i=size-1; i>=0; --i) + { + const Record& record = collection.getRecord (i); + + if (!std::binary_search (mSelection.begin(), mSelection.end(), + Misc::StringUtils::lowerCase (record.get().mCell))) + continue; + + mDocument.getUndoStack().push ( + new CSMWorld::RevertCommand (model, record.get().mId)); + } + } + } + + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().endMacro(); +} \ No newline at end of file diff --git a/apps/opencs/model/world/commanddispatcher.hpp b/apps/opencs/model/world/commanddispatcher.hpp new file mode 100644 index 000000000..50085b1a1 --- /dev/null +++ b/apps/opencs/model/world/commanddispatcher.hpp @@ -0,0 +1,69 @@ +#ifndef CSM_WOLRD_COMMANDDISPATCHER_H +#define CSM_WOLRD_COMMANDDISPATCHER_H + +#include + +#include + +#include "universalid.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher : public QObject + { + Q_OBJECT + + bool mLocked; + CSMDoc::Document& mDocument; + UniversalId mId; + std::vector mSelection; + std::vector mExtendedTypes; + + std::vector getDeletableRecords() const; + + std::vector getRevertableRecords() const; + + public: + + CommandDispatcher (CSMDoc::Document& document, const CSMWorld::UniversalId& id, + QObject *parent = 0); + ///< \param id ID of the table the commands should operate on primarily. + + void setEditLock (bool locked); + + void setSelection (const std::vector& selection); + + void setExtendedTypes (const std::vector& types); + ///< Set record lists selected by the user for extended operations. + + bool canDelete() const; + + bool canRevert() const; + + /// Return IDs of the record collection that can also be affected when + /// operating on the record collection this dispatcher is used for. + /// + /// \note The returned collection contains the ID of the record collection this + /// dispatcher is used for. However if that record collection does not support + /// the extended mode, the returned vector will be empty instead. + std::vector getExtendedTypes() const; + + public slots: + + void executeDelete(); + + void executeRevert(); + + void executeExtendedDelete(); + + void executeExtendedRevert(); + + }; +} + +#endif diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 442e73e51..7f2eac20d 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -16,11 +16,12 @@ #include "regionmap.hpp" #include "columns.hpp" -void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type1, - UniversalId::Type type2, bool update) +void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update) { mModels.push_back (model); - mModelIndex.insert (std::make_pair (type1, model)); + mModelIndex.insert (std::make_pair (type, model)); + + UniversalId::Type type2 = UniversalId::getParentType (type); if (type2!=UniversalId::Type_None) mModelIndex.insert (std::make_pair (type2, model)); @@ -235,26 +236,26 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding) mFilters.addColumn (new DescriptionColumn); mFilters.addColumn (new ScopeColumn); - addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); - addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst); - addModel (new IdTable (&mSkills), UniversalId::Type_Skills, UniversalId::Type_Skill, false); - addModel (new IdTable (&mClasses), UniversalId::Type_Classes, UniversalId::Type_Class); - addModel (new IdTable (&mFactions), UniversalId::Type_Factions, UniversalId::Type_Faction); - addModel (new IdTable (&mRaces), UniversalId::Type_Races, UniversalId::Type_Race); - addModel (new IdTable (&mSounds), UniversalId::Type_Sounds, UniversalId::Type_Sound); - addModel (new IdTable (&mScripts), UniversalId::Type_Scripts, UniversalId::Type_Script); - addModel (new IdTable (&mRegions), UniversalId::Type_Regions, UniversalId::Type_Region); - addModel (new IdTable (&mBirthsigns), UniversalId::Type_Birthsigns, UniversalId::Type_Birthsign); - addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell); - addModel (new IdTable (&mTopics), UniversalId::Type_Topics, UniversalId::Type_Topic); - addModel (new IdTable (&mJournals), UniversalId::Type_Journals, UniversalId::Type_Journal); - addModel (new IdTable (&mTopicInfos, IdTable::Reordering_WithinTopic), UniversalId::Type_TopicInfos, UniversalId::Type_TopicInfo); - addModel (new IdTable (&mJournalInfos, IdTable::Reordering_WithinTopic), UniversalId::Type_JournalInfos, UniversalId::Type_JournalInfo); - addModel (new IdTable (&mCells, IdTable::Reordering_None, IdTable::Viewing_Id), UniversalId::Type_Cells, UniversalId::Type_Cell); - addModel (new IdTable (&mReferenceables, IdTable::Reordering_None, IdTable::Viewing_None, true), - UniversalId::Type_Referenceables, UniversalId::Type_Referenceable); - addModel (new IdTable (&mRefs, IdTable::Reordering_None, IdTable::Viewing_Cell, true), UniversalId::Type_References, UniversalId::Type_Reference, false); - addModel (new IdTable (&mFilters), UniversalId::Type_Filters, UniversalId::Type_Filter, false); + addModel (new IdTable (&mGlobals), UniversalId::Type_Global); + addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); + addModel (new IdTable (&mSkills), UniversalId::Type_Skill); + addModel (new IdTable (&mClasses), UniversalId::Type_Class); + addModel (new IdTable (&mFactions), UniversalId::Type_Faction); + addModel (new IdTable (&mRaces), UniversalId::Type_Race); + addModel (new IdTable (&mSounds), UniversalId::Type_Sound); + addModel (new IdTable (&mScripts), UniversalId::Type_Script); + addModel (new IdTable (&mRegions), UniversalId::Type_Region); + addModel (new IdTable (&mBirthsigns), UniversalId::Type_Birthsign); + addModel (new IdTable (&mSpells), UniversalId::Type_Spell); + addModel (new IdTable (&mTopics), UniversalId::Type_Topic); + addModel (new IdTable (&mJournals), UniversalId::Type_Journal); + addModel (new IdTable (&mTopicInfos, IdTable::Feature_ReorderWithinTopic), UniversalId::Type_TopicInfo); + addModel (new IdTable (&mJournalInfos, IdTable::Feature_ReorderWithinTopic), UniversalId::Type_JournalInfo); + addModel (new IdTable (&mCells, IdTable::Feature_ViewId), UniversalId::Type_Cell); + addModel (new IdTable (&mReferenceables, IdTable::Feature_Preview), + UniversalId::Type_Referenceable); + addModel (new IdTable (&mRefs, IdTable::Feature_ViewCell | IdTable::Feature_Preview), UniversalId::Type_Reference); + addModel (new IdTable (&mFilters), UniversalId::Type_Filter); } CSMWorld::Data::~Data() @@ -469,8 +470,7 @@ QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& if (id.getType()==UniversalId::Type_RegionMap) { RegionMap *table = 0; - addModel (table = new RegionMap (*this), UniversalId::Type_RegionMap, - UniversalId::Type_None, false); + addModel (table = new RegionMap (*this), UniversalId::Type_RegionMap, false); return table; } throw std::logic_error ("No table model available for " + id.toString()); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 9a842878a..edca9fdbc 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -83,8 +83,8 @@ namespace CSMWorld Data (const Data&); Data& operator= (const Data&); - void addModel (QAbstractItemModel *model, UniversalId::Type type1, - UniversalId::Type type2 = UniversalId::Type_None, bool update = true); + void addModel (QAbstractItemModel *model, UniversalId::Type type, + bool update = true); static void appendIds (std::vector& ids, const CollectionBase& collection, bool listDeleted); diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 50998c36f..a481ce143 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -4,9 +4,8 @@ #include "collectionbase.hpp" #include "columnbase.hpp" -CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering, - Viewing viewing, bool preview) -: mIdCollection (idCollection), mReordering (reordering), mViewing (viewing), mPreview (preview) +CSMWorld::IdTable::IdTable (CollectionBase *idCollection, unsigned int features) +: mIdCollection (idCollection), mFeatures (features) {} CSMWorld::IdTable::~IdTable() @@ -186,19 +185,9 @@ void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector& newO index (baseIndex+newOrder.size()-1, mIdCollection->getColumns()-1)); } -CSMWorld::IdTable::Reordering CSMWorld::IdTable::getReordering() const +unsigned int CSMWorld::IdTable::getFeatures() const { - return mReordering; -} - -CSMWorld::IdTable::Viewing CSMWorld::IdTable::getViewing() const -{ - return mViewing; -} - -bool CSMWorld::IdTable::hasPreview() const -{ - return mPreview; + return mFeatures; } std::pair CSMWorld::IdTable::view (int row) const @@ -206,7 +195,7 @@ std::pair CSMWorld::IdTable::view (int row) std::string id; std::string hint; - if (mViewing==Viewing_Cell) + if (mFeatures & Feature_ViewCell) { int cellColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Cell); int idColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); @@ -217,7 +206,7 @@ std::pair CSMWorld::IdTable::view (int row) hint = "r:" + std::string (mIdCollection->getData (row, idColumn).toString().toUtf8().constData()); } } - else if (mViewing==Viewing_Id) + else if (mFeatures & Feature_ViewId) { int column = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 8b5462825..5500d40b8 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -19,26 +19,27 @@ namespace CSMWorld public: - enum Reordering + enum Features { - Reordering_None, - Reordering_WithinTopic - }; + Feature_ReorderWithinTopic = 1, - enum Viewing - { - Viewing_None, - Viewing_Id, // use ID column to generate view request (ID is transformed into - // worldspace and original ID is passed as hint with c: prefix) - Viewing_Cell // use cell column to generate view request (cell ID is transformed - // into worldspace and record ID is passed as hint with r: prefix) + /// Use ID column to generate view request (ID is transformed into + /// worldspace and original ID is passed as hint with c: prefix). + Feature_ViewId = 2, + + /// Use cell column to generate view request (cell ID is transformed + /// into worldspace and record ID is passed as hint with r: prefix). + Feature_ViewCell = 4, + + Feature_View = Feature_ViewId | Feature_ViewCell, + + Feature_Preview = 8 }; private: CollectionBase *mIdCollection; - Reordering mReordering; - Viewing mViewing; + unsigned int mFeatures; bool mPreview; // not implemented @@ -47,8 +48,7 @@ namespace CSMWorld public: - IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_None, - Viewing viewing = Viewing_None, bool preview = false); + IdTable (CollectionBase *idCollection, unsigned int features = 0); ///< The ownership of \a idCollection is not transferred. virtual ~IdTable(); @@ -97,11 +97,7 @@ namespace CSMWorld ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). - Reordering getReordering() const; - - Viewing getViewing() const; - - bool hasPreview() const; + unsigned int getFeatures() const; std::pair view (int row) const; ///< Return the UniversalId and the hint for viewing \a row. If viewing is not diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 140a410c0..88e649ace 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -61,8 +61,8 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Referenceables", 0 }, @@ -90,7 +90,7 @@ namespace { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Reference", 0 }, - { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", 0 }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", 0 }, @@ -320,6 +320,28 @@ std::vector CSMWorld::UniversalId::listReferenceabl return list; } +CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType (Type type) +{ + for (int i=0; sIdArg[i].mType; ++i) + if (type==sIdArg[i].mType) + { + if (sIdArg[i].mClass==Class_RefRecord) + return Type_Referenceables; + + if (sIdArg[i].mClass==Class_SubRecord || sIdArg[i].mClass==Class_Record) + { + if (type==Type_Cell_Missing) + return Type_Cells; + + return static_cast (type-1); + } + + break; + } + + return Type_None; +} + bool CSMWorld::operator== (const CSMWorld::UniversalId& left, const CSMWorld::UniversalId& right) { return left.isEqual (right); diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 22779b263..3bef71c75 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -32,6 +32,8 @@ namespace CSMWorld ArgumentType_Index }; + /// \note A record list type must always be immediately followed by the matching + /// record type, if this type is of class SubRecord or Record. enum Type { Type_None = 0, @@ -86,8 +88,8 @@ namespace CSMWorld Type_References, Type_Reference, Type_RegionMap, - Type_Filter, Type_Filters, + Type_Filter, Type_Topics, Type_Topic, Type_Journals, @@ -147,6 +149,11 @@ namespace CSMWorld ///< Will return an empty string, if no icon is available. static std::vector listReferenceableTypes(); + + /// If \a type is a SubRecord, RefRecord or Record type return the type of the table + /// that contains records of type \a type. + /// Otherwise return Type_None. + static Type getParentType (Type type); }; bool operator== (const UniversalId& left, const UniversalId& right); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index d03bf3f80..ad2db8723 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -332,6 +332,7 @@ void CSVWorld::EditWidget::remake(int row) if (mMainWidget) { delete mMainWidget; + mMainWidget = 0; } mMainWidget = new QWidget (this); @@ -339,6 +340,7 @@ void CSVWorld::EditWidget::remake(int row) if (mWidgetMapper) { delete mWidgetMapper; + mWidgetMapper = 0; } mWidgetMapper = new QDataWidgetMapper (this); mWidgetMapper->setModel(mTable); @@ -396,7 +398,7 @@ void CSVWorld::EditWidget::remake(int row) mWidgetMapper->setCurrentModelIndex(mTable->index(row, 0)); - this->setMinimumWidth(325); //TODO find better way to set the width or make it customizable + this->setMinimumWidth(325); /// \todo replace hardcoded value with a user setting this->setWidget(mMainWidget); this->setWidgetResizable(true); } @@ -407,7 +409,6 @@ void CSVWorld::EditWidget::remake(int row) CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) : - SubView (id), mEditWidget(0), mMainLayout(NULL), @@ -415,8 +416,8 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM mTable(dynamic_cast(document.getData().getTableModel(id))), mRow (-1), mLocked(false), - mDocument(document) - + mDocument(document), + mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) { connect(mTable, SIGNAL(dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT(dataChanged(const QModelIndex&))); mRow = mTable->getModelIndex (id.getId(), 0).row(); @@ -440,7 +441,7 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM QToolButton* revertButton = new QToolButton(mainWidget); revertButton->setIcon(QIcon(":/edit-undo.png")); - if (mTable->hasPreview()) + if (mTable->getFeatures() & CSMWorld::IdTable::Feature_Preview) { QToolButton* previewButton = new QToolButton(mainWidget); previewButton->setIcon(QIcon(":/edit-preview.png")); @@ -448,7 +449,7 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM connect(previewButton, SIGNAL(clicked()), this, SLOT(showPreview())); } - if (mTable->getViewing()!=CSMWorld::IdTable::Viewing_None) + if (mTable->getFeatures() & CSMWorld::IdTable::Feature_View) { QToolButton* viewButton = new QToolButton(mainWidget); viewButton->setIcon(QIcon(":/cell.png")); @@ -560,14 +561,12 @@ void CSVWorld::DialogueSubView::nextId() void CSVWorld::DialogueSubView::setEditLock (bool locked) { mLocked = locked; + CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (mRow, 1)).toInt()); - if (state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased) - { - mEditWidget->setDisabled(true); - } else - { - mEditWidget->setDisabled(mLocked); - } + + mEditWidget->setDisabled (state==CSMWorld::RecordBase::State_Deleted || locked); + + mCommandDispatcher.setEditLock (locked); } void CSVWorld::DialogueSubView::dataChanged(const QModelIndex & index) @@ -575,13 +574,8 @@ void CSVWorld::DialogueSubView::dataChanged(const QModelIndex & index) if (index.row() == mRow) { CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (mRow, 1)).toInt()); - if (state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased) - { - mEditWidget->setDisabled(true); - } else - { - mEditWidget->setDisabled(mLocked); - } + + mEditWidget->setDisabled (state==CSMWorld::RecordBase::State_Deleted || mLocked); } } @@ -671,7 +665,8 @@ void CSVWorld::DialogueSubView::cloneRequest () void CSVWorld::DialogueSubView::showPreview () { - if (mTable->hasPreview() && mRow < mTable->rowCount()) + if ((mTable->getFeatures() & CSMWorld::IdTable::Feature_Preview) && + mRow < mTable->rowCount()) { emit focusId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, mTable->data(mTable->index (mRow, 0)).toString().toUtf8().constData()), ""); } diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 5642f46a0..cbca0159c 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -8,7 +8,9 @@ #include #include "../doc/subview.hpp" + #include "../../model/world/columnbase.hpp" +#include "../../model/world/commanddispatcher.hpp" class QDataWidgetMapper; class QSize; @@ -169,6 +171,7 @@ namespace CSVWorld bool mLocked; const CSMDoc::Document& mDocument; TableBottomBox* mBottom; + CSMWorld::CommandDispatcher mCommandDispatcher; public: diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 75f391699..022d5d02e 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -80,30 +80,35 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Scene, new CSVDoc::SubViewFactory); - //edit subviews - manager.add (CSMWorld::UniversalId::Type_Region, - new CSVDoc::SubViewFactoryWithCreator > (false)); - - manager.add (CSMWorld::UniversalId::Type_Spell, - new CSVDoc::SubViewFactoryWithCreator > (false)); + // Dialogue subviews + static const CSMWorld::UniversalId::Type sTableTypes2[] = + { + CSMWorld::UniversalId::Type_Region, + CSMWorld::UniversalId::Type_Spell, + CSMWorld::UniversalId::Type_Birthsign, + CSMWorld::UniversalId::Type_Global, + CSMWorld::UniversalId::Type_Race, + CSMWorld::UniversalId::Type_Class, + CSMWorld::UniversalId::Type_Filter, + CSMWorld::UniversalId::Type_Sound, + CSMWorld::UniversalId::Type_Faction, - manager.add (CSMWorld::UniversalId::Type_Referenceable, - new CSVDoc::SubViewFactoryWithCreator > (false)); + CSMWorld::UniversalId::Type_None // end marker + }; - manager.add (CSMWorld::UniversalId::Type_Birthsign, - new CSVDoc::SubViewFactoryWithCreator > (false)); + for (int i=0; sTableTypes2[i]!=CSMWorld::UniversalId::Type_None; ++i) + manager.add (sTableTypes2[i], + new CSVDoc::SubViewFactoryWithCreator > (false)); - manager.add (CSMWorld::UniversalId::Type_Global, - new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_Skill, + new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_Gmst, - new CSVDoc::SubViewFactoryWithCreator > (false)); - - manager.add (CSMWorld::UniversalId::Type_Race, - new CSVDoc::SubViewFactoryWithCreator > (false)); + new CSVDoc::SubViewFactoryWithCreator (false)); - manager.add (CSMWorld::UniversalId::Type_Class, - new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_Referenceable, + new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_Reference, new CSVDoc::SubViewFactoryWithCreator > (false)); @@ -111,18 +116,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Cell, new CSVDoc::SubViewFactoryWithCreator > (false)); - manager.add (CSMWorld::UniversalId::Type_Filter, - new CSVDoc::SubViewFactoryWithCreator > (false)); - - manager.add (CSMWorld::UniversalId::Type_Sound, - new CSVDoc::SubViewFactoryWithCreator > (false)); - - manager.add (CSMWorld::UniversalId::Type_Faction, - new CSVDoc::SubViewFactoryWithCreator > (false)); - - manager.add (CSMWorld::UniversalId::Type_Skill, - new CSVDoc::SubViewFactoryWithCreator > (false)); - manager.add (CSMWorld::UniversalId::Type_JournalInfo, new CSVDoc::SubViewFactoryWithCreator > (false)); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 3d4b02c9c..877fd51c0 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -19,14 +19,36 @@ #include "../../model/world/columns.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/tablemimedata.hpp" +#include "../../model/world/commanddispatcher.hpp" #include "recordstatusdelegate.hpp" #include "util.hpp" void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { + // configure dispatcher QModelIndexList selectedRows = selectionModel()->selectedRows(); + std::vector records; + + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + { + int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); + + records.push_back (mModel->data ( + mModel->index (row, columnIndex)).toString().toUtf8().constData()); + } + + mDispatcher->setSelection (records); + + std::vector extendedTypes = mDispatcher->getExtendedTypes(); + + mDispatcher->setExtendedTypes (extendedTypes); + + // create context menu QMenu menu (this); /// \todo add menu items for select all and clear selection @@ -44,22 +66,27 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (mCreateAction) menu.addAction (mCreateAction); - /// \todo Reverting temporarily disabled on tables that support reordering, because - /// revert logic currently can not handle reordering. - if (mModel->getReordering()==CSMWorld::IdTable::Reordering_None) - if (listRevertableSelectedIds().size()>0) - menu.addAction (mRevertAction); + if (mDispatcher->canRevert()) + { + menu.addAction (mRevertAction); - if (listDeletableSelectedIds().size()>0) + if (!extendedTypes.empty()) + menu.addAction (mExtendedRevertAction); + } + + if (mDispatcher->canDelete()) + { menu.addAction (mDeleteAction); - if (mModel->getReordering()==CSMWorld::IdTable::Reordering_WithinTopic) + if (!extendedTypes.empty()) + menu.addAction (mExtendedDeleteAction); + } + + if (mModel->getFeatures() & CSMWorld::IdTable::Feature_ReorderWithinTopic) { /// \todo allow reordering of multiple rows if (selectedRows.size()==1) { - int row =selectedRows.begin()->row(); - int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); if (column==-1) @@ -67,14 +94,17 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (column!=-1) { - if (row>0 && mProxyModel->data (mProxyModel->index (row, column))== - mProxyModel->data (mProxyModel->index (row-1, column))) + int row = mProxyModel->mapToSource ( + mProxyModel->index (selectedRows.begin()->row(), 0)).row(); + + if (row>0 && mModel->data (mModel->index (row, column))== + mModel->data (mModel->index (row-1, column))) { menu.addAction (mMoveUpAction); } - if (rowrowCount()-1 && mProxyModel->data (mProxyModel->index (row, column))== - mProxyModel->data (mProxyModel->index (row+1, column))) + if (rowrowCount()-1 && mModel->data (mModel->index (row, column))== + mModel->data (mModel->index (row+1, column))) { menu.addAction (mMoveDownAction); } @@ -85,7 +115,7 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (selectedRows.size()==1) { - if (mModel->getViewing()!=CSMWorld::IdTable::Viewing_None) + if (mModel->getFeatures() & CSMWorld::IdTable::Feature_View) { int row = selectedRows.begin()->row(); @@ -101,91 +131,13 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) menu.addAction (mViewAction); } - if (mModel->hasPreview()) + if (mModel->getFeatures() & CSMWorld::IdTable::Feature_Preview) menu.addAction (mPreviewAction); } menu.exec (event->globalPos()); } -std::vector CSVWorld::Table::listRevertableSelectedIds() const -{ - std::vector revertableIds; - - if (mProxyModel->columnCount()>0) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); - - CSMWorld::RecordBase::State state = - static_cast ( - mModel->data (mModel->index (index.row(), 1)).toInt()); - - if (state!=CSMWorld::RecordBase::State_BaseOnly) - { - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - std::string id = mModel->data (mModel->index (index.row(), columnIndex)). - toString().toUtf8().constData(); - - revertableIds.push_back (id); - } - } - } - - return revertableIds; -} - -std::vector CSVWorld::Table::listDeletableSelectedIds() const -{ - std::vector deletableIds; - - if (mProxyModel->columnCount()>0) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); - - // check record state - CSMWorld::RecordBase::State state = - static_cast ( - mModel->data (mModel->index (index.row(), 1)).toInt()); - - if (state==CSMWorld::RecordBase::State_Deleted) - continue; - - // check other columns (only relevant for a subset of the tables) - int dialogueTypeIndex = - mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); - - if (dialogueTypeIndex!=-1) - { - int type = mModel->data (mModel->index (index.row(), dialogueTypeIndex)).toInt(); - - if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) - continue; - } - - // add the id to the collection - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - std::string id = mModel->data (mModel->index (index.row(), columnIndex)). - toString().toUtf8().constData(); - - deletableIds.push_back (id); - } - } - - return deletableIds; -} - CSVWorld::Table::Table (const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document) : mCreateAction (0), mCloneAction(0), mRecordStatusDisplay (0), @@ -196,6 +148,8 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mProxyModel = new CSMWorld::IdTableProxyModel (this); mProxyModel->setSourceModel (mModel); + mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); + setModel (mProxyModel); horizontalHeader()->setResizeMode (QHeaderView::Interactive); verticalHeader()->hide(); @@ -240,11 +194,11 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, } mRevertAction = new QAction (tr ("Revert Record"), this); - connect (mRevertAction, SIGNAL (triggered()), this, SLOT (revertRecord())); + connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert())); addAction (mRevertAction); mDeleteAction = new QAction (tr ("Delete Record"), this); - connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); + connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete())); addAction (mDeleteAction); mMoveUpAction = new QAction (tr ("Move Up"), this); @@ -263,6 +217,18 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); addAction (mPreviewAction); + /// \todo add a user option, that redirects the extended action to an input panel (in + /// the bottom bar) that lets the user select which record collections should be + /// modified. + + mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); + connect (mExtendedDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedDelete())); + addAction (mExtendedDeleteAction); + + mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); + connect (mExtendedRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedRevert())); + addAction (mExtendedRevertAction); + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); @@ -283,53 +249,19 @@ void CSVWorld::Table::setEditLock (bool locked) (*iter)->setEditLock (locked); DragRecordTable::setEditLock(locked); + mDispatcher->setEditLock (locked); } CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const { - return CSMWorld::UniversalId ( - static_cast (mProxyModel->data (mProxyModel->index (row, 2)).toInt()), - mProxyModel->data (mProxyModel->index (row, 0)).toString().toUtf8().constData()); -} + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); -void CSVWorld::Table::revertRecord() -{ - if (!mEditLock) - { - std::vector revertableIds = listRevertableSelectedIds(); - - if (!revertableIds.empty()) - { - if (revertableIds.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Revert multiple records")); - - for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) - mDocument.getUndoStack().push (new CSMWorld::RevertCommand (*mModel, *iter)); - - if (revertableIds.size()>1) - mDocument.getUndoStack().endMacro(); - } - } -} + int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int typeColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); -void CSVWorld::Table::deleteRecord() -{ - if (!mEditLock) - { - std::vector deletableIds = listDeletableSelectedIds(); - - if (!deletableIds.empty()) - { - if (deletableIds.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Delete multiple records")); - - for (std::vector::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter) - mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (*mModel, *iter)); - - if (deletableIds.size()>1) - mDocument.getUndoStack().endMacro(); - } - } + return CSMWorld::UniversalId ( + static_cast (mModel->data (mModel->index (row, typeColumn)).toInt()), + mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData()); } void CSVWorld::Table::editRecord() diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 3b1d40e78..255c430ea 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -24,6 +24,7 @@ namespace CSMWorld class UniversalId; class IdTableProxyModel; class IdTable; + class CommandDispatcher; } namespace CSVWorld @@ -45,18 +46,17 @@ namespace CSVWorld QAction *mMoveDownAction; QAction *mViewAction; QAction *mPreviewAction; + QAction *mExtendedDeleteAction; + QAction *mExtendedRevertAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTable *mModel; int mRecordStatusDisplay; + CSMWorld::CommandDispatcher *mDispatcher; private: void contextMenuEvent (QContextMenuEvent *event); - std::vector listRevertableSelectedIds() const; - - std::vector listDeletableSelectedIds() const; - void mouseMoveEvent(QMouseEvent *event); void dropEvent(QDropEvent *event); @@ -93,10 +93,6 @@ namespace CSVWorld private slots: - void revertRecord(); - - void deleteRecord(); - void editRecord(); void cloneRecord();