diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 627199c22..5648ace54 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -80,7 +80,7 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const char type, ignore; int fieldIndex; - if ((stream >> type >> ignore >> fieldIndex) && type=='r') + if ((stream >> type >> ignore >> fieldIndex) && (type=='r' || type=='R')) { field = CSMWorld::Columns::getName ( static_cast (fieldIndex)); @@ -135,6 +135,21 @@ void CSMTools::ReportModel::add (const CSMWorld::UniversalId& id, const std::str endInsertRows(); } +void CSMTools::ReportModel::flagAsReplaced (int index) +{ + Line& line = mRows.at (index); + std::string hint = line.mHint; + + if (hint.empty() || hint[0]!='R') + throw std::logic_error ("trying to flag message as replaced that is not replaceable"); + + hint[0] = 'r'; + + line.mHint = hint; + + emit dataChanged (this->index (index, 0), this->index (index, columnCount())); +} + const CSMWorld::UniversalId& CSMTools::ReportModel::getUniversalId (int row) const { return mRows.at (row).mId; diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp index 7e733fab6..4d2d0542f 100644 --- a/apps/opencs/model/tools/reportmodel.hpp +++ b/apps/opencs/model/tools/reportmodel.hpp @@ -49,10 +49,12 @@ namespace CSMTools virtual QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()); - + void add (const CSMWorld::UniversalId& id, const std::string& message, const std::string& hint = ""); + void flagAsReplaced (int index); + const CSMWorld::UniversalId& getUniversalId (int row) const; std::string getHint (int row) const; diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp index db495be11..cb8850754 100644 --- a/apps/opencs/model/tools/search.cpp +++ b/apps/opencs/model/tools/search.cpp @@ -4,14 +4,16 @@ #include #include -#include "../../model/doc/messages.hpp" +#include "../doc/messages.hpp" +#include "../doc/document.hpp" -#include "../../model/world/idtablebase.hpp" -#include "../../model/world/columnbase.hpp" -#include "../../model/world/universalid.hpp" +#include "../world/idtablebase.hpp" +#include "../world/columnbase.hpp" +#include "../world/universalid.hpp" +#include "../world/commands.hpp" void CSMTools::Search::searchTextCell (const CSMWorld::IdTableBase *model, - const QModelIndex& index, const CSMWorld::UniversalId& id, + const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const { // using QString here for easier handling of case folding. @@ -25,7 +27,8 @@ void CSMTools::Search::searchTextCell (const CSMWorld::IdTableBase *model, { std::ostringstream hint; hint - << "r: " + << (writable ? 'R' : 'r') + <<": " << model->getColumnId (index.column()) << " " << pos << " " << search.length(); @@ -37,7 +40,7 @@ void CSMTools::Search::searchTextCell (const CSMWorld::IdTableBase *model, } void CSMTools::Search::searchRegExCell (const CSMWorld::IdTableBase *model, - const QModelIndex& index, const CSMWorld::UniversalId& id, + const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const { QString text = model->data (index).toString(); @@ -49,7 +52,12 @@ void CSMTools::Search::searchRegExCell (const CSMWorld::IdTableBase *model, int length = mRegExp.matchedLength(); std::ostringstream hint; - hint << "r: " << model->getColumnId (index.column()) << " " << pos << " " << length; + hint + << (writable ? 'R' : 'r') + <<": " + << model->getColumnId (index.column()) + << " " << pos + << " " << length; messages.add (id, formatDescription (text, pos, length).toUtf8().data(), hint.str()); @@ -58,8 +66,11 @@ void CSMTools::Search::searchRegExCell (const CSMWorld::IdTableBase *model, } void CSMTools::Search::searchRecordStateCell (const CSMWorld::IdTableBase *model, - const QModelIndex& index, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) const + const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const { + if (writable) + throw std::logic_error ("Record state can not be modified by search and replace"); + int data = model->data (index).toInt(); if (data==mValue) @@ -203,24 +214,26 @@ void CSMTools::Search::searchRow (const CSMWorld::IdTableBase *model, int row, CSMWorld::UniversalId id ( type, model->data (model->index (row, mIdColumn)).toString().toUtf8().data()); - + + bool writable = model->flags (index) & Qt::ItemIsEditable; + switch (mType) { case Type_Text: case Type_Id: - searchTextCell (model, index, id, messages); + searchTextCell (model, index, id, writable, messages); break; case Type_TextRegEx: case Type_IdRegEx: - searchRegExCell (model, index, id, messages); + searchRegExCell (model, index, id, writable, messages); break; case Type_RecordState: - searchRecordStateCell (model, index, id, messages); + searchRecordStateCell (model, index, id, writable, messages); break; case Type_None: @@ -235,3 +248,32 @@ void CSMTools::Search::setPadding (int before, int after) mPaddingBefore = before; mPaddingAfter = after; } + +void CSMTools::Search::replace (CSMDoc::Document& document, CSMWorld::IdTableBase *model, + const CSMWorld::UniversalId& id, const std::string& messageHint, + const std::string& replaceText) const +{ + std::istringstream stream (messageHint.c_str()); + + char hint, ignore; + int columnId, pos, length; + + if (stream >> hint >> ignore >> columnId >> pos >> length) + { + int column = + model->findColumnIndex (static_cast (columnId)); + + QModelIndex index = model->getModelIndex (id.getId(), column); + + std::string text = model->data (index).toString().toUtf8().constData(); + + std::string before = text.substr (0, pos); + std::string after = text.substr (pos+length); + + std::string newText = before + replaceText + after; + + document.getUndoStack().push ( + new CSMWorld::ModifyCommand (*model, index, QString::fromUtf8 (newText.c_str()))); + } +} + diff --git a/apps/opencs/model/tools/search.hpp b/apps/opencs/model/tools/search.hpp index 452b3cad2..c9dfc4c44 100644 --- a/apps/opencs/model/tools/search.hpp +++ b/apps/opencs/model/tools/search.hpp @@ -12,6 +12,7 @@ class QModelIndex; namespace CSMDoc { class Messages; + class Document; } namespace CSMWorld @@ -49,13 +50,13 @@ namespace CSMTools int mPaddingAfter; void searchTextCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, - const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) const; + const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; void searchRegExCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, - const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) const; + const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; void searchRecordStateCell (const CSMWorld::IdTableBase *model, - const QModelIndex& index, const CSMWorld::UniversalId& id, + const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; QString formatDescription (const QString& description, int pos, int length) const; @@ -82,6 +83,11 @@ namespace CSMTools CSMDoc::Messages& messages) const; void setPadding (int before, int after); + + // Configuring *this for the model is not necessary when calling this function. + void replace (CSMDoc::Document& document, CSMWorld::IdTableBase *model, + const CSMWorld::UniversalId& id, const std::string& messageHint, + const std::string& replaceText) const; }; } diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index 73fee2362..1b07d3c0f 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -56,8 +56,25 @@ void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) { menu.addAction (mShowAction); menu.addAction (mRemoveAction); - } + bool found = false; + for (QModelIndexList::const_iterator iter (selectedRows.begin()); + iter!=selectedRows.end(); ++iter) + { + QString hint = mModel->data (mModel->index (iter->row(), 2)).toString(); + + if (!hint.isEmpty() && hint[0]=='R') + { + found = true; + break; + } + } + + if (found) + menu.addAction (mReplaceAction); + + } + menu.exec (event->globalPos()); } @@ -129,6 +146,10 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, mRemoveAction = new QAction (tr ("Remove from list"), this); connect (mRemoveAction, SIGNAL (triggered()), this, SLOT (removeSelection())); addAction (mRemoveAction); + + mReplaceAction = new QAction (tr ("Replace"), this); + connect (mReplaceAction, SIGNAL (triggered()), this, SIGNAL (replaceRequest())); + addAction (mReplaceAction); } std::vector CSVTools::ReportTable::getDraggedRecords() const @@ -151,6 +172,42 @@ void CSVTools::ReportTable::updateUserSetting (const QString& name, const QStrin mIdTypeDelegate->updateUserSetting (name, list); } +std::vector CSVTools::ReportTable::getReplaceIndices (bool selection) const +{ + std::vector indices; + + if (selection) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + { + QString hint = mModel->data (mModel->index (iter->row(), 2)).toString(); + + if (!hint.isEmpty() && hint[0]=='R') + indices.push_back (iter->row()); + } + } + else + { + for (int i=0; irowCount(); ++i) + { + QString hint = mModel->data (mModel->index (i, 2)).toString(); + + if (!hint.isEmpty() && hint[0]=='R') + indices.push_back (i); + } + } + + return indices; +} + +void CSVTools::ReportTable::flagAsReplaced (int index) +{ + mModel->flagAsReplaced (index); +} + void CSVTools::ReportTable::showSelection() { QModelIndexList selectedRows = selectionModel()->selectedRows(); diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index dde6cc634..c4d5b414e 100644 --- a/apps/opencs/view/tools/reporttable.hpp +++ b/apps/opencs/view/tools/reporttable.hpp @@ -25,6 +25,7 @@ namespace CSVTools CSVWorld::CommandDelegate *mIdTypeDelegate; QAction *mShowAction; QAction *mRemoveAction; + QAction *mReplaceAction; private: @@ -46,6 +47,13 @@ namespace CSVTools void clear(); + // Return indices of rows that are suitable for replacement. + // + // \param selection Only list selected rows. + std::vector getReplaceIndices (bool selection) const; + + void flagAsReplaced (int index); + private slots: void showSelection(); @@ -55,6 +63,8 @@ namespace CSVTools signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); + + void replaceRequest(); }; } diff --git a/apps/opencs/view/tools/searchbox.cpp b/apps/opencs/view/tools/searchbox.cpp index 178dd1f04..1e92acb75 100644 --- a/apps/opencs/view/tools/searchbox.cpp +++ b/apps/opencs/view/tools/searchbox.cpp @@ -38,6 +38,9 @@ void CSVTools::SearchBox::updateSearchButton() CSVTools::SearchBox::SearchBox (QWidget *parent) : QWidget (parent), mSearch ("Search"), mSearchEnabled (false) { + mLayout = new QGridLayout (this); + + // search panel std::vector states = CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); states.resize (states.size()-1); // ignore erased state @@ -46,8 +49,6 @@ CSVTools::SearchBox::SearchBox (QWidget *parent) ++iter) mRecordState.addItem (QString::fromUtf8 (iter->c_str())); - mLayout = new QGridLayout (this); - mMode.addItem ("Text"); mMode.addItem ("Text (RegEx)"); mMode.addItem ("ID"); @@ -61,13 +62,8 @@ CSVTools::SearchBox::SearchBox (QWidget *parent) mInput.insertWidget (0, &mText); mInput.insertWidget (1, &mRecordState); - mLayout->addWidget (&mInput, 0, 1); - - mLayout->setColumnMinimumWidth (2, 50); - mLayout->setColumnStretch (1, 1); + mLayout->addWidget (&mInput, 0, 1); - mLayout->setContentsMargins (0, 0, 0, 0); - connect (&mMode, SIGNAL (activated (int)), this, SLOT (modeSelected (int))); connect (&mText, SIGNAL (textChanged (const QString&)), @@ -76,7 +72,20 @@ CSVTools::SearchBox::SearchBox (QWidget *parent) connect (&mSearch, SIGNAL (clicked (bool)), this, SLOT (startSearch (bool))); connect (&mText, SIGNAL (returnPressed()), this, SLOT (startSearch())); + + // replace panel + mReplaceInput.insertWidget (0, &mReplaceText); + mReplaceInput.insertWidget (1, &mReplacePlaceholder); + + mLayout->addWidget (&mReplaceInput, 1, 1); + + // layout adjustments + mLayout->setColumnMinimumWidth (2, 50); + mLayout->setColumnStretch (1, 1); + + mLayout->setContentsMargins (0, 0, 0, 0); + // update modeSelected (0); updateSearchButton(); @@ -116,6 +125,25 @@ CSMTools::Search CSVTools::SearchBox::getSearch() const throw std::logic_error ("invalid search mode index"); } +std::string CSVTools::SearchBox::getReplaceText() const +{ + CSMTools::Search::Type type = static_cast (mMode.currentIndex()); + + switch (type) + { + case CSMTools::Search::Type_Text: + case CSMTools::Search::Type_TextRegEx: + case CSMTools::Search::Type_Id: + case CSMTools::Search::Type_IdRegEx: + + return mReplaceText.text().toUtf8().data(); + + default: + + throw std::logic_error ("Invalid search mode for replace"); + } +} + void CSVTools::SearchBox::modeSelected (int index) { switch (index) @@ -126,10 +154,12 @@ void CSVTools::SearchBox::modeSelected (int index) case CSMTools::Search::Type_IdRegEx: mInput.setCurrentIndex (0); + mReplaceInput.setCurrentIndex (0); break; case CSMTools::Search::Type_RecordState: mInput.setCurrentIndex (1); + mReplaceInput.setCurrentIndex (1); break; } diff --git a/apps/opencs/view/tools/searchbox.hpp b/apps/opencs/view/tools/searchbox.hpp index 35c656d16..594788e6c 100644 --- a/apps/opencs/view/tools/searchbox.hpp +++ b/apps/opencs/view/tools/searchbox.hpp @@ -6,6 +6,7 @@ #include #include #include +#include class QGridLayout; @@ -27,6 +28,9 @@ namespace CSVTools QGridLayout *mLayout; QComboBox mMode; bool mSearchEnabled; + QStackedWidget mReplaceInput; + QLineEdit mReplaceText; + QLabel mReplacePlaceholder; private: @@ -40,6 +44,8 @@ namespace CSVTools CSMTools::Search getSearch() const; + std::string getReplaceText() const; + private slots: void modeSelected (int index); diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index 36fd65a2b..64827b737 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -5,6 +5,8 @@ #include "../../model/doc/document.hpp" #include "../../model/tools/search.hpp" +#include "../../model/tools/reportmodel.hpp" +#include "../../model/world/idtablebase.hpp" #include "reporttable.hpp" #include "searchbox.hpp" @@ -31,6 +33,8 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); + connect (mTable, SIGNAL (replaceRequest()), this, SLOT (replaceRequest())); + connect (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), this, SLOT (stateChanged (int, CSMDoc::Document *))); @@ -63,9 +67,36 @@ void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *documen void CSVTools::SearchSubView::startSearch (const CSMTools::Search& search) { - CSMTools::Search search2 (search); - search2.setPadding (mPaddingBefore, mPaddingAfter); + mSearch = search; + mSearch.setPadding (mPaddingBefore, mPaddingAfter); mTable->clear(); - mDocument.runSearch (getUniversalId(), search2); + mDocument.runSearch (getUniversalId(), mSearch); +} + +void CSVTools::SearchSubView::replaceRequest() +{ + std::vector indices = mTable->getReplaceIndices (true); + + std::string replace = mSearchBox.getReplaceText(); + + const CSMTools::ReportModel& model = + dynamic_cast (*mTable->model()); + + // We are running through the indices in reverse order to avoid messing up multiple results + // in a single string. + for (std::vector::const_reverse_iterator iter (indices.rbegin()); iter!=indices.rend(); ++iter) + { + CSMWorld::UniversalId id = model.getUniversalId (*iter); + + CSMWorld::UniversalId::Type type = CSMWorld::UniversalId::getParentType (id.getType()); + + CSMWorld::IdTableBase *table = &dynamic_cast ( + *mDocument.getData().getTableModel (type)); + + std::string hint = model.getHint (*iter); + + mSearch.replace (mDocument, table, id, hint, replace); + mTable->flagAsReplaced (*iter); + } } diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp index b9b24f774..125d0b927 100644 --- a/apps/opencs/view/tools/searchsubview.hpp +++ b/apps/opencs/view/tools/searchsubview.hpp @@ -1,6 +1,8 @@ #ifndef CSV_TOOLS_SEARCHSUBVIEW_H #define CSV_TOOLS_SEARCHSUBVIEW_H +#include "../../model/tools/search.hpp" + #include "../doc/subview.hpp" #include "searchbox.hpp" @@ -26,6 +28,7 @@ namespace CSVTools CSMDoc::Document& mDocument; int mPaddingBefore; int mPaddingAfter; + CSMTools::Search mSearch; public: @@ -40,6 +43,8 @@ namespace CSVTools void stateChanged (int state, CSMDoc::Document *document); void startSearch (const CSMTools::Search& search); + + void replaceRequest(); }; }