diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index e618d2961..f27231c5a 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -40,7 +40,7 @@ opencs_units (model/tools opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck - startscriptcheck + startscriptcheck search searchoperation searchstage ) @@ -91,7 +91,7 @@ opencs_hdrs_noqt (view/render opencs_units (view/tools - reportsubview reporttable + reportsubview reporttable searchsubview searchbox ) opencs_units_noqt (view/tools diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 7d27143b4..cb3b4ba18 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2374,6 +2374,17 @@ CSMWorld::UniversalId CSMDoc::Document::verify() return id; } + +CSMWorld::UniversalId CSMDoc::Document::newSearch() +{ + return mTools.newSearch(); +} + +void CSMDoc::Document::runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search) +{ + return mTools.runSearch (searchId, search); +} + void CSMDoc::Document::abortOperation (int type) { if (type==State_Saving) diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 1f96b44a1..6b1a1fc1e 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -120,6 +120,10 @@ namespace CSMDoc CSMWorld::UniversalId verify(); + CSMWorld::UniversalId newSearch(); + + void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search); + void abortOperation (int type); const CSMWorld::Data& getData() const; diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp index 287439a8b..78f468101 100644 --- a/apps/opencs/model/doc/state.hpp +++ b/apps/opencs/model/doc/state.hpp @@ -13,7 +13,7 @@ namespace CSMDoc State_Saving = 16, State_Verifying = 32, State_Compiling = 64, // not implemented yet - State_Searching = 128, // not implemented yet + State_Searching = 128, State_Loading = 256 // pseudo-state; can not be encountered in a loaded document }; } diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 7dac660c3..7a975c99c 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -208,6 +208,21 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() shiftCtrlDoubleClick->setToolTip ("Action on shift control double click in table:

" + toolTip); } + declareSection ("search", "Search & Replace"); + { + Setting *before = createSetting (Type_SpinBox, "char-before", + "Characters before search string"); + before->setDefaultValue (10); + before->setRange (0, 1000); + before->setToolTip ("Maximum number of character to display in search result before the searched text"); + + Setting *after = createSetting (Type_SpinBox, "char-after", + "Characters after search string"); + after->setDefaultValue (10); + after->setRange (0, 1000); + after->setToolTip ("Maximum number of character to display in search result after the searched text"); + } + { /****************************************************************** * There are three types of values: diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index ac9dabb25..5648ace54 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -2,6 +2,29 @@ #include "reportmodel.hpp" #include +#include + +#include "../world/columns.hpp" + +CSMTools::ReportModel::Line::Line (const CSMWorld::UniversalId& id, const std::string& message, + const std::string& hint) +: mId (id), mMessage (message), mHint (hint) +{} + +CSMTools::ReportModel::ReportModel (bool fieldColumn) +{ + if (fieldColumn) + { + mColumnField = 3; + mColumnDescription = 4; + } + else + { + mColumnDescription = 3; + + mColumnField = -1; + } +} int CSMTools::ReportModel::rowCount (const QModelIndex & parent) const { @@ -16,7 +39,7 @@ int CSMTools::ReportModel::columnCount (const QModelIndex & parent) const if (parent.isValid()) return 0; - return 3; + return mColumnDescription+1; } QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const @@ -24,13 +47,49 @@ QVariant CSMTools::ReportModel::data (const QModelIndex & index, int role) const if (role!=Qt::DisplayRole) return QVariant(); - if (index.column()==0) - return static_cast (mRows.at (index.row()).first.getType()); + switch (index.column()) + { + case Column_Type: + + return static_cast (mRows.at (index.row()).mId.getType()); + + case Column_Id: + { + CSMWorld::UniversalId id = mRows.at (index.row()).mId; + + if (id.getArgumentType()==CSMWorld::UniversalId::ArgumentType_Id) + return QString::fromUtf8 (id.getId().c_str()); + + return QString ("-"); + } + + case Column_Hint: + + return QString::fromUtf8 (mRows.at (index.row()).mHint.c_str()); + } + + if (index.column()==mColumnDescription) + return QString::fromUtf8 (mRows.at (index.row()).mMessage.c_str()); + + if (index.column()==mColumnField) + { + std::string field; + + std::istringstream stream (mRows.at (index.row()).mHint); - if (index.column()==1) - return QString::fromUtf8 (mRows.at (index.row()).second.first.c_str()); + char type, ignore; + int fieldIndex; - return QString::fromUtf8 (mRows.at (index.row()).second.second.c_str()); + if ((stream >> type >> ignore >> fieldIndex) && (type=='r' || type=='R')) + { + field = CSMWorld::Columns::getName ( + static_cast (fieldIndex)); + } + + return QString::fromUtf8 (field.c_str()); + } + + return QVariant(); } QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orientation, int role) const @@ -41,13 +100,19 @@ QVariant CSMTools::ReportModel::headerData (int section, Qt::Orientation orienta if (orientation==Qt::Vertical) return QVariant(); - if (section==0) - return "Type"; + switch (section) + { + case Column_Type: return "Type"; + case Column_Id: return "ID"; + } - if (section==1) + if (section==mColumnDescription) return "Description"; - return "Hint"; + if (section==mColumnField) + return "Field"; + + return "-"; } bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& parent) @@ -64,18 +129,43 @@ void CSMTools::ReportModel::add (const CSMWorld::UniversalId& id, const std::str const std::string& hint) { beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); - - mRows.push_back (std::make_pair (id, std::make_pair (message, hint))); + + mRows.push_back (Line (id, message, hint)); 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).first; + return mRows.at (row).mId; } std::string CSMTools::ReportModel::getHint (int row) const { - return mRows.at (row).second.second; + return mRows.at (row).mHint; +} + +void CSMTools::ReportModel::clear() +{ + if (!mRows.empty()) + { + beginRemoveRows (QModelIndex(), 0, mRows.size()-1); + mRows.clear(); + endRemoveRows(); + } } diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp index 709e024a7..4d2d0542f 100644 --- a/apps/opencs/model/tools/reportmodel.hpp +++ b/apps/opencs/model/tools/reportmodel.hpp @@ -14,10 +14,32 @@ namespace CSMTools { Q_OBJECT - std::vector > > mRows; + struct Line + { + Line (const CSMWorld::UniversalId& id, const std::string& message, + const std::string& hint); + + CSMWorld::UniversalId mId; + std::string mMessage; + std::string mHint; + }; + + std::vector mRows; + + // Fixed columns + enum Columns + { + Column_Type = 0, Column_Id = 1, Column_Hint = 2 + }; + + // Configurable columns + int mColumnDescription; + int mColumnField; public: + ReportModel (bool fieldColumn = false); + virtual int rowCount (const QModelIndex & parent = QModelIndex()) const; virtual int columnCount (const QModelIndex & parent = QModelIndex()) const; @@ -27,13 +49,17 @@ 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; + + void clear(); }; } diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp new file mode 100644 index 000000000..cb8850754 --- /dev/null +++ b/apps/opencs/model/tools/search.cpp @@ -0,0 +1,279 @@ + +#include "search.hpp" + +#include +#include + +#include "../doc/messages.hpp" +#include "../doc/document.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, bool writable, + CSMDoc::Messages& messages) const +{ + // using QString here for easier handling of case folding. + + QString search = QString::fromUtf8 (mText.c_str()); + QString text = model->data (index).toString(); + + int pos = 0; + + while ((pos = text.indexOf (search, pos, Qt::CaseInsensitive))!=-1) + { + std::ostringstream hint; + hint + << (writable ? 'R' : 'r') + <<": " + << model->getColumnId (index.column()) + << " " << pos + << " " << search.length(); + + messages.add (id, formatDescription (text, pos, search.length()).toUtf8().data(), hint.str()); + + pos += search.length(); + } +} + +void CSMTools::Search::searchRegExCell (const CSMWorld::IdTableBase *model, + const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, + CSMDoc::Messages& messages) const +{ + QString text = model->data (index).toString(); + + int pos = 0; + + while ((pos = mRegExp.indexIn (text, pos))!=-1) + { + int length = mRegExp.matchedLength(); + + std::ostringstream hint; + hint + << (writable ? 'R' : 'r') + <<": " + << model->getColumnId (index.column()) + << " " << pos + << " " << length; + + messages.add (id, formatDescription (text, pos, length).toUtf8().data(), hint.str()); + + pos += length; + } +} + +void CSMTools::Search::searchRecordStateCell (const CSMWorld::IdTableBase *model, + 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) + { + std::vector states = + CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); + + std::ostringstream message; + message << states.at (data); + + std::ostringstream hint; + hint << "r: " << model->getColumnId (index.column()); + + messages.add (id, message.str(), hint.str()); + } +} + +QString CSMTools::Search::formatDescription (const QString& description, int pos, int length) const +{ + QString text (description); + + // split + QString highlight = flatten (text.mid (pos, length)); + QString before = flatten (mPaddingBefore>=pos ? + text.mid (0, pos) : text.mid (pos-mPaddingBefore, mPaddingBefore)); + QString after = flatten (text.mid (pos+length, mPaddingAfter)); + + // compensate for Windows nonsense + text.remove ('\r'); + + // join + text = before + "" + highlight + "" + after; + + // improve layout for single line display + text.replace ("\n", "<CR>"); + text.replace ('\t', ' '); + + return text; +} + +QString CSMTools::Search::flatten (const QString& text) const +{ + QString flat (text); + + flat.replace ("&", "&"); + flat.replace ("<", "<"); + + return flat; +} + +CSMTools::Search::Search() : mType (Type_None), mPaddingBefore (10), mPaddingAfter (10) {} + +CSMTools::Search::Search (Type type, const std::string& value) +: mType (type), mText (value), mPaddingBefore (10), mPaddingAfter (10) +{ + if (type!=Type_Text && type!=Type_Id) + throw std::logic_error ("Invalid search parameter (string)"); +} + +CSMTools::Search::Search (Type type, const QRegExp& value) +: mType (type), mRegExp (value), mPaddingBefore (10), mPaddingAfter (10) +{ + if (type!=Type_TextRegEx && type!=Type_IdRegEx) + throw std::logic_error ("Invalid search parameter (RegExp)"); +} + +CSMTools::Search::Search (Type type, int value) +: mType (type), mValue (value), mPaddingBefore (10), mPaddingAfter (10) +{ + if (type!=Type_RecordState) + throw std::logic_error ("invalid search parameter (int)"); +} + +void CSMTools::Search::configure (const CSMWorld::IdTableBase *model) +{ + mColumns.clear(); + + int columns = model->columnCount(); + + for (int i=0; i ( + model->headerData ( + i, Qt::Horizontal, static_cast (CSMWorld::ColumnBase::Role_Display)).toInt()); + + bool consider = false; + + switch (mType) + { + case Type_Text: + case Type_TextRegEx: + + if (CSMWorld::ColumnBase::isText (display) || + CSMWorld::ColumnBase::isScript (display)) + { + consider = true; + } + + break; + + case Type_Id: + case Type_IdRegEx: + + if (CSMWorld::ColumnBase::isId (display) || + CSMWorld::ColumnBase::isScript (display)) + { + consider = true; + } + + break; + + case Type_RecordState: + + if (display==CSMWorld::ColumnBase::Display_RecordState) + consider = true; + + break; + + case Type_None: + + break; + } + + if (consider) + mColumns.insert (i); + } + + mIdColumn = model->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + mTypeColumn = model->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); +} + +void CSMTools::Search::searchRow (const CSMWorld::IdTableBase *model, int row, + CSMDoc::Messages& messages) const +{ + for (std::set::const_iterator iter (mColumns.begin()); iter!=mColumns.end(); ++iter) + { + QModelIndex index = model->index (row, *iter); + + CSMWorld::UniversalId::Type type = static_cast ( + model->data (model->index (row, mTypeColumn)).toInt()); + + 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, writable, messages); + break; + + case Type_TextRegEx: + case Type_IdRegEx: + + searchRegExCell (model, index, id, writable, messages); + break; + + case Type_RecordState: + + searchRecordStateCell (model, index, id, writable, messages); + break; + + case Type_None: + + break; + } + } +} + +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 new file mode 100644 index 000000000..c9dfc4c44 --- /dev/null +++ b/apps/opencs/model/tools/search.hpp @@ -0,0 +1,96 @@ +#ifndef CSM_TOOLS_SEARCH_H +#define CSM_TOOLS_SEARCH_H + +#include +#include + +#include +#include + +class QModelIndex; + +namespace CSMDoc +{ + class Messages; + class Document; +} + +namespace CSMWorld +{ + class IdTableBase; + class UniversalId; +} + +namespace CSMTools +{ + class Search + { + public: + + enum Type + { + Type_Text = 0, + Type_TextRegEx = 1, + Type_Id = 2, + Type_IdRegEx = 3, + Type_RecordState = 4, + Type_None + }; + + private: + + Type mType; + std::string mText; + QRegExp mRegExp; + int mValue; + std::set mColumns; + int mIdColumn; + int mTypeColumn; + int mPaddingBefore; + int mPaddingAfter; + + void searchTextCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, + const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; + + void searchRegExCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, + const CSMWorld::UniversalId& id, bool writable, CSMDoc::Messages& messages) const; + + void searchRecordStateCell (const CSMWorld::IdTableBase *model, + const QModelIndex& index, const CSMWorld::UniversalId& id, bool writable, + CSMDoc::Messages& messages) const; + + QString formatDescription (const QString& description, int pos, int length) const; + + QString flatten (const QString& text) const; + + public: + + Search(); + + Search (Type type, const std::string& value); + + Search (Type type, const QRegExp& value); + + Search (Type type, int value); + + // Configure search for the specified model. + void configure (const CSMWorld::IdTableBase *model); + + // Search row in \a model and store results in \a messages. + // + // \attention *this needs to be configured for \a model. + void searchRow (const CSMWorld::IdTableBase *model, int row, + 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; + }; +} + +Q_DECLARE_METATYPE (CSMTools::Search) + +#endif diff --git a/apps/opencs/model/tools/searchoperation.cpp b/apps/opencs/model/tools/searchoperation.cpp new file mode 100644 index 000000000..4512de582 --- /dev/null +++ b/apps/opencs/model/tools/searchoperation.cpp @@ -0,0 +1,40 @@ + +#include "searchoperation.hpp" + +#include "../doc/state.hpp" +#include "../doc/document.hpp" + +#include "../world/data.hpp" +#include "../world/idtablebase.hpp" + +#include "searchstage.hpp" + +CSMTools::SearchOperation::SearchOperation (CSMDoc::Document& document) +: CSMDoc::Operation (CSMDoc::State_Searching, false) +{ + std::vector types = CSMWorld::UniversalId::listTypes ( + CSMWorld::UniversalId::Class_RecordList | + CSMWorld::UniversalId::Class_ResourceList + ); + + for (std::vector::const_iterator iter (types.begin()); + iter!=types.end(); ++iter) + appendStage (new SearchStage (&dynamic_cast ( + *document.getData().getTableModel (*iter)))); +} + +void CSMTools::SearchOperation::configure (const Search& search) +{ + mSearch = search; +} + +void CSMTools::SearchOperation::appendStage (SearchStage *stage) +{ + CSMDoc::Operation::appendStage (stage); + stage->setOperation (this); +} + +const CSMTools::Search& CSMTools::SearchOperation::getSearch() const +{ + return mSearch; +} diff --git a/apps/opencs/model/tools/searchoperation.hpp b/apps/opencs/model/tools/searchoperation.hpp new file mode 100644 index 000000000..fbbb38898 --- /dev/null +++ b/apps/opencs/model/tools/searchoperation.hpp @@ -0,0 +1,38 @@ +#ifndef CSM_TOOLS_SEARCHOPERATION_H +#define CSM_TOOLS_SEARCHOPERATION_H + +#include "../doc/operation.hpp" + +#include "search.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMTools +{ + class SearchStage; + + class SearchOperation : public CSMDoc::Operation + { + Search mSearch; + + public: + + SearchOperation (CSMDoc::Document& document); + + /// \attention Do not call this function while a search is running. + void configure (const Search& search); + + void appendStage (SearchStage *stage); + ///< The ownership of \a stage is transferred to *this. + /// + /// \attention Do no call this function while this Operation is running. + + const Search& getSearch() const; + + }; +} + +#endif diff --git a/apps/opencs/model/tools/searchstage.cpp b/apps/opencs/model/tools/searchstage.cpp new file mode 100644 index 000000000..17859d930 --- /dev/null +++ b/apps/opencs/model/tools/searchstage.cpp @@ -0,0 +1,30 @@ + +#include "searchstage.hpp" + +#include "../world/idtablebase.hpp" + +#include "searchoperation.hpp" + +CSMTools::SearchStage::SearchStage (const CSMWorld::IdTableBase *model) +: mModel (model), mOperation (0) +{} + +int CSMTools::SearchStage::setup() +{ + if (mOperation) + mSearch = mOperation->getSearch(); + + mSearch.configure (mModel); + + return mModel->rowCount(); +} + +void CSMTools::SearchStage::perform (int stage, CSMDoc::Messages& messages) +{ + mSearch.searchRow (mModel, stage, messages); +} + +void CSMTools::SearchStage::setOperation (const SearchOperation *operation) +{ + mOperation = operation; +} diff --git a/apps/opencs/model/tools/searchstage.hpp b/apps/opencs/model/tools/searchstage.hpp new file mode 100644 index 000000000..073487c0d --- /dev/null +++ b/apps/opencs/model/tools/searchstage.hpp @@ -0,0 +1,37 @@ +#ifndef CSM_TOOLS_SEARCHSTAGE_H +#define CSM_TOOLS_SEARCHSTAGE_H + +#include "../doc/stage.hpp" + +#include "search.hpp" + +namespace CSMWorld +{ + class IdTableBase; +} + +namespace CSMTools +{ + class SearchOperation; + + class SearchStage : public CSMDoc::Stage + { + const CSMWorld::IdTableBase *mModel; + Search mSearch; + const SearchOperation *mOperation; + + public: + + SearchStage (const CSMWorld::IdTableBase *model); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + + void setOperation (const SearchOperation *operation); + }; +} + +#endif diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 170ea8ccd..970a8ac4f 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -25,12 +25,14 @@ #include "bodypartcheck.hpp" #include "referencecheck.hpp" #include "startscriptcheck.hpp" +#include "searchoperation.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { switch (type) { case CSMDoc::State_Verifying: return &mVerifier; + case CSMDoc::State_Searching: return &mSearch; } return 0; @@ -101,11 +103,18 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() } CSMTools::Tools::Tools (CSMDoc::Document& document) -: mDocument (document), mData (document.getData()), mVerifierOperation (0), mNextReportNumber (0) +: mDocument (document), mData (document.getData()), mVerifierOperation (0), mNextReportNumber (0), + mSearchOperation (0) { // index 0: load error log mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); mActiveReports.insert (std::make_pair (CSMDoc::State_Loading, 0)); + + connect (&mSearch, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); + connect (&mSearch, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool))); + connect (&mSearch, + SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int)), + this, SLOT (verifierMessage (const CSMWorld::UniversalId&, const std::string&, const std::string&, int))); } CSMTools::Tools::~Tools() @@ -116,6 +125,12 @@ CSMTools::Tools::~Tools() delete mVerifierOperation; } + if (mSearchOperation) + { + mSearch.abortAndWait(); + delete mSearchOperation; + } + for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) delete iter->second; } @@ -130,6 +145,28 @@ CSMWorld::UniversalId CSMTools::Tools::runVerifier() return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_VerificationResults, mNextReportNumber-1); } +CSMWorld::UniversalId CSMTools::Tools::newSearch() +{ + mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel (true))); + + return CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Search, mNextReportNumber-1); +} + +void CSMTools::Tools::runSearch (const CSMWorld::UniversalId& searchId, const Search& search) +{ + mActiveReports[CSMDoc::State_Searching] = searchId.getIndex(); + + if (!mSearchOperation) + { + mSearchOperation = new SearchOperation (mDocument); + mSearch.setOperation (mSearchOperation); + } + + mSearchOperation->configure (search); + + mSearch.start(); +} + void CSMTools::Tools::abortOperation (int type) { if (CSMDoc::OperationHolder *operation = get (type)) @@ -141,6 +178,7 @@ int CSMTools::Tools::getRunningOperations() const static const int sOperations[] = { CSMDoc::State_Verifying, + CSMDoc::State_Searching, -1 }; @@ -157,9 +195,10 @@ int CSMTools::Tools::getRunningOperations() const CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& id) { if (id.getType()!=CSMWorld::UniversalId::Type_VerificationResults && - id.getType()!=CSMWorld::UniversalId::Type_LoadErrorLog) + id.getType()!=CSMWorld::UniversalId::Type_LoadErrorLog && + id.getType()!=CSMWorld::UniversalId::Type_Search) throw std::logic_error ("invalid request for report model: " + id.toString()); - + return mReports.at (id.getIndex()); } diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index b8ded8a83..0f9e57044 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -22,6 +22,8 @@ namespace CSMDoc namespace CSMTools { class ReportModel; + class Search; + class SearchOperation; class Tools : public QObject { @@ -31,6 +33,8 @@ namespace CSMTools CSMWorld::Data& mData; CSMDoc::Operation *mVerifierOperation; CSMDoc::OperationHolder mVerifier; + SearchOperation *mSearchOperation; + CSMDoc::OperationHolder mSearch; std::map mReports; int mNextReportNumber; std::map mActiveReports; // type, report number @@ -56,6 +60,11 @@ namespace CSMTools CSMWorld::UniversalId runVerifier(); ///< \return ID of the report for this verification run + /// Return ID of the report for this search. + CSMWorld::UniversalId newSearch(); + + void runSearch (const CSMWorld::UniversalId& searchId, const Search& search); + void abortOperation (int type); ///< \attention The operation is not aborted immediately. diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 665ab9354..17e4def2f 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -19,7 +19,80 @@ std::string CSMWorld::ColumnBase::getTitle() const return Columns::getName (static_cast (mColumnId)); } -int CSMWorld::ColumnBase::getId() const +int CSMWorld::ColumnBase::getId() const { return mColumnId; } + +bool CSMWorld::ColumnBase::isId (Display display) +{ + static const Display ids[] = + { + Display_Skill, + Display_Class, + Display_Faction, + Display_Race, + Display_Sound, + Display_Region, + Display_Birthsign, + Display_Spell, + Display_Cell, + Display_Referenceable, + Display_Activator, + Display_Potion, + Display_Apparatus, + Display_Armor, + Display_Book, + Display_Clothing, + Display_Container, + Display_Creature, + Display_Door, + Display_Ingredient, + Display_CreatureLevelledList, + Display_ItemLevelledList, + Display_Light, + Display_Lockpick, + Display_Miscellaneous, + Display_Npc, + Display_Probe, + Display_Repair, + Display_Static, + Display_Weapon, + Display_Reference, + Display_Filter, + Display_Topic, + Display_Journal, + Display_TopicInfo, + Display_JournalInfo, + Display_Scene, + Display_GlobalVariable, + Display_Script, + + Display_Mesh, + Display_Icon, + Display_Music, + Display_SoundRes, + Display_Texture, + Display_Video, + + Display_Id, + + Display_None + }; + + for (int i=0; ids[i]!=Display_None; ++i) + if (ids[i]==display) + return true; + + return false; +} + +bool CSMWorld::ColumnBase::isText (Display display) +{ + return display==Display_String || display==Display_LongString; +} + +bool CSMWorld::ColumnBase::isScript (Display display) +{ + return display==Display_ScriptFile || display==Display_ScriptLines; +} diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index db9b8b3c6..6f67898f6 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -101,9 +101,11 @@ namespace CSMWorld Display_Texture, Display_Video, Display_Colour, + Display_ScriptFile, Display_ScriptLines, // console context Display_SoundGeneratorType, - Display_School + Display_School, + Display_Id }; int mColumnId; @@ -122,6 +124,12 @@ namespace CSMWorld virtual std::string getTitle() const; virtual int getId() const; + + static bool isId (Display display); + + static bool isText (Display display); + + static bool isScript (Display display); }; template diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index da14bb495..e67fdd59c 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -43,7 +43,7 @@ namespace CSMWorld struct StringIdColumn : public Column { StringIdColumn (bool hidden = false) - : Column (Columns::ColumnId_Id, ColumnBase::Display_String, + : Column (Columns::ColumnId_Id, ColumnBase::Display_Id, hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) {} @@ -818,7 +818,7 @@ namespace CSMWorld ScriptColumn (Type type) : Column (Columns::ColumnId_ScriptText, - type==Type_File ? ColumnBase::Display_Script : ColumnBase::Display_ScriptLines, + type==Type_File ? ColumnBase::Display_ScriptFile : ColumnBase::Display_ScriptLines, type==Type_File ? 0 : ColumnBase::Flag_Dialogue) {} diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index ea8ab80f9..6f4be7178 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -82,7 +82,7 @@ namespace CSMWorld /// Is \a id flagged as deleted? virtual bool isDeleted (const std::string& id) const; - int getColumnId(int column) const; + virtual int getColumnId(int column) const; }; } diff --git a/apps/opencs/model/world/idtablebase.hpp b/apps/opencs/model/world/idtablebase.hpp index ef5a9c42e..0d77d48ef 100644 --- a/apps/opencs/model/world/idtablebase.hpp +++ b/apps/opencs/model/world/idtablebase.hpp @@ -60,6 +60,8 @@ namespace CSMWorld /// Is \a id flagged as deleted? virtual bool isDeleted (const std::string& id) const = 0; + virtual int getColumnId (int column) const = 0; + unsigned int getFeatures() const; }; } diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 14a8890ad..6963c1331 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -40,7 +40,7 @@ CSMWorld::RefIdCollection::RefIdCollection() { BaseColumns baseColumns; - mColumns.push_back (RefIdColumn (Columns::ColumnId_Id, ColumnBase::Display_String, + mColumns.push_back (RefIdColumn (Columns::ColumnId_Id, ColumnBase::Display_Id, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false)); baseColumns.mId = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_Modification, ColumnBase::Display_RecordState, diff --git a/apps/opencs/model/world/resourcetable.cpp b/apps/opencs/model/world/resourcetable.cpp index 9257b9d2a..2cd44781a 100644 --- a/apps/opencs/model/world/resourcetable.cpp +++ b/apps/opencs/model/world/resourcetable.cpp @@ -60,7 +60,7 @@ QVariant CSMWorld::ResourceTable::headerData (int section, Qt::Orientation orien return Columns::getName (Columns::ColumnId_Id).c_str(); if (role==ColumnBase::Role_Display) - return ColumnBase::Display_String; + return ColumnBase::Display_Id; break; @@ -144,3 +144,14 @@ bool CSMWorld::ResourceTable::isDeleted (const std::string& id) const { return false; } + +int CSMWorld::ResourceTable::getColumnId (int column) const +{ + switch (column) + { + case 0: return Columns::ColumnId_Id; + case 1: return Columns::ColumnId_RecordType; + } + + return -1; +} diff --git a/apps/opencs/model/world/resourcetable.hpp b/apps/opencs/model/world/resourcetable.hpp index f5011ab2b..88dcc24b0 100644 --- a/apps/opencs/model/world/resourcetable.hpp +++ b/apps/opencs/model/world/resourcetable.hpp @@ -51,6 +51,8 @@ namespace CSMWorld /// Is \a id flagged as deleted? virtual bool isDeleted (const std::string& id) const; + + virtual int getColumnId (int column) const; }; } diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 50ac846db..fbc942f8e 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -128,6 +128,7 @@ namespace { { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results", 0 }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_LoadErrorLog, "Load Error Log", 0 }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; } @@ -347,6 +348,25 @@ std::vector CSMWorld::UniversalId::listReferenceabl return list; } +std::vector CSMWorld::UniversalId::listTypes (int classes) +{ + std::vector list; + + for (int i=0; sNoArg[i].mName; ++i) + if (sNoArg[i].mClass & classes) + list.push_back (sNoArg[i].mType); + + for (int i=0; sIdArg[i].mName; ++i) + if (sIdArg[i].mClass & classes) + list.push_back (sIdArg[i].mType); + + for (int i=0; sIndexArg[i].mName; ++i) + if (sIndexArg[i].mClass & classes) + list.push_back (sIndexArg[i].mType); + + return list; +} + CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType (Type type) { for (int i=0; sIdArg[i].mType; ++i) diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index a716aec03..0a9fa3847 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -16,16 +16,16 @@ namespace CSMWorld enum Class { Class_None = 0, - Class_Record, - Class_RefRecord, // referenceable record - Class_SubRecord, - Class_RecordList, - Class_Collection, // multiple types of records combined - Class_Transient, // not part of the world data or the project data - Class_NonRecord, // record like data that is not part of the world - Class_Resource, ///< \attention Resource IDs are unique only within the + Class_Record = 1, + Class_RefRecord = 2, // referenceable record + Class_SubRecord = 4, + Class_RecordList = 8, + Class_Collection = 16, // multiple types of records combined + Class_Transient = 32, // not part of the world data or the project data + Class_NonRecord = 64, // record like data that is not part of the world + Class_Resource = 128, ///< \attention Resource IDs are unique only within the /// respective collection - Class_ResourceList + Class_ResourceList = 256 }; enum ArgumentType @@ -130,6 +130,7 @@ namespace CSMWorld Type_Pathgrid, Type_StartScripts, Type_StartScript, + Type_Search, Type_RunLog }; @@ -180,6 +181,8 @@ namespace CSMWorld static std::vector listReferenceableTypes(); + static std::vector listTypes (int classes); + /// 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. diff --git a/apps/opencs/view/doc/operation.cpp b/apps/opencs/view/doc/operation.cpp index 6977d7953..95cbf012d 100644 --- a/apps/opencs/view/doc/operation.cpp +++ b/apps/opencs/view/doc/operation.cpp @@ -18,6 +18,7 @@ void CSVDoc::Operation::updateLabel (int threads) { case CSMDoc::State_Saving: name = "saving"; break; case CSMDoc::State_Verifying: name = "verifying"; break; + case CSMDoc::State_Searching: name = "searching"; break; } std::ostringstream stream; diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index cf2940b99..9b601243c 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -91,6 +91,10 @@ void CSVDoc::View::setupEditMenu() QAction *userSettings = new QAction (tr ("&Preferences"), this); connect (userSettings, SIGNAL (triggered()), this, SIGNAL (editSettingsRequest())); edit->addAction (userSettings); + + QAction *search = new QAction (tr ("Search"), this); + connect (search, SIGNAL (triggered()), this, SLOT (addSearchSubView())); + edit->addAction (search); } void CSVDoc::View::setupViewMenu() @@ -443,7 +447,7 @@ void CSVDoc::View::updateDocumentState() static const int operations[] = { - CSMDoc::State_Saving, CSMDoc::State_Verifying, + CSMDoc::State_Saving, CSMDoc::State_Verifying, CSMDoc::State_Searching, -1 // end marker }; @@ -728,6 +732,11 @@ void CSVDoc::View::addStartScriptsSubView() addSubView (CSMWorld::UniversalId::Type_StartScripts); } +void CSVDoc::View::addSearchSubView() +{ + addSubView (mDocument->newSearch()); +} + void CSVDoc::View::abortOperation (int type) { mDocument->abortOperation (type); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index baadca85c..32d7159c2 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -217,6 +217,8 @@ namespace CSVDoc void addStartScriptsSubView(); + void addSearchSubView(); + void toggleShowStatusBar (bool show); void loadErrorLog(); diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index df1a5298c..492874c01 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -6,7 +6,7 @@ CSVTools::ReportSubView::ReportSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : CSVDoc::SubView (id) { - setWidget (mTable = new ReportTable (document, id, this)); + setWidget (mTable = new ReportTable (document, id, false, this)); connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&))); diff --git a/apps/opencs/view/tools/reporttable.cpp b/apps/opencs/view/tools/reporttable.cpp index 809a39fa4..1b07d3c0f 100644 --- a/apps/opencs/view/tools/reporttable.cpp +++ b/apps/opencs/view/tools/reporttable.cpp @@ -6,11 +6,45 @@ #include #include #include +#include +#include +#include #include "../../model/tools/reportmodel.hpp" #include "../../view/world/idtypedelegate.hpp" +namespace CSVTools +{ + class RichTextDelegate : public QStyledItemDelegate + { + public: + + RichTextDelegate (QObject *parent = 0); + + virtual void paint(QPainter *painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const; + }; +} + +CSVTools::RichTextDelegate::RichTextDelegate (QObject *parent) : QStyledItemDelegate (parent) +{} + +void CSVTools::RichTextDelegate::paint(QPainter *painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const +{ + QTextDocument document; + QVariant value = index.data (Qt::DisplayRole); + if (value.isValid() && !value.isNull()) + { + document.setHtml (value.toString()); + painter->translate (option.rect.topLeft()); + document.drawContents (painter); + painter->translate (-option.rect.topLeft()); + } +} + + void CSVTools::ReportTable::contextMenuEvent (QContextMenuEvent *event) { QModelIndexList selectedRows = selectionModel()->selectedRows(); @@ -22,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()); } @@ -67,10 +118,11 @@ void CSVTools::ReportTable::mouseDoubleClickEvent (QMouseEvent *event) } CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, - const CSMWorld::UniversalId& id, QWidget *parent) + const CSMWorld::UniversalId& id, bool richTextDescription, QWidget *parent) : CSVWorld::DragRecordTable (document, parent), mModel (document.getReport (id)) { horizontalHeader()->setResizeMode (QHeaderView::Interactive); + horizontalHeader()->setStretchLastSection (true); verticalHeader()->hide(); setSortingEnabled (true); setSelectionBehavior (QAbstractItemView::SelectRows); @@ -84,6 +136,9 @@ CSVTools::ReportTable::ReportTable (CSMDoc::Document& document, setItemDelegateForColumn (0, mIdTypeDelegate); + if (richTextDescription) + setItemDelegateForColumn (mModel->columnCount()-1, new RichTextDelegate (this)); + mShowAction = new QAction (tr ("Show"), this); connect (mShowAction, SIGNAL (triggered()), this, SLOT (showSelection())); addAction (mShowAction); @@ -91,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 @@ -113,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(); @@ -134,3 +229,8 @@ void CSVTools::ReportTable::removeSelection() selectionModel()->clear(); } + +void CSVTools::ReportTable::clear() +{ + mModel->clear(); +} diff --git a/apps/opencs/view/tools/reporttable.hpp b/apps/opencs/view/tools/reporttable.hpp index 7a5b232f9..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: @@ -36,13 +37,23 @@ namespace CSVTools public: + /// \param richTextDescription Use rich text in the description column. ReportTable (CSMDoc::Document& document, const CSMWorld::UniversalId& id, - QWidget *parent = 0); + bool richTextDescription, QWidget *parent = 0); virtual std::vector getDraggedRecords() const; void updateUserSetting (const QString& name, const QStringList& list); + 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(); @@ -52,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 new file mode 100644 index 000000000..ca5520787 --- /dev/null +++ b/apps/opencs/view/tools/searchbox.cpp @@ -0,0 +1,192 @@ + +#include "searchbox.hpp" + +#include + +#include +#include +#include + +#include "../../model/world/columns.hpp" + +#include "../../model/tools/search.hpp" + +void CSVTools::SearchBox::updateSearchButton() +{ + if (!mSearchEnabled) + mSearch.setEnabled (false); + else + { + switch (mMode.currentIndex()) + { + case 0: + case 1: + case 2: + case 3: + + mSearch.setEnabled (!mText.text().isEmpty()); + break; + + case 4: + + mSearch.setEnabled (true); + break; + } + } +} + +CSVTools::SearchBox::SearchBox (QWidget *parent) +: QWidget (parent), mSearch ("Search"), mSearchEnabled (false), mReplace ("Replace All") +{ + mLayout = new QGridLayout (this); + + // search panel + std::vector states = + CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); + states.resize (states.size()-1); // ignore erased state + + for (std::vector::const_iterator iter (states.begin()); iter!=states.end(); + ++iter) + mRecordState.addItem (QString::fromUtf8 (iter->c_str())); + + mMode.addItem ("Text"); + mMode.addItem ("Text (RegEx)"); + mMode.addItem ("ID"); + mMode.addItem ("ID (RegEx)"); + mMode.addItem ("Record State"); + + mLayout->addWidget (&mMode, 0, 0); + + mLayout->addWidget (&mSearch, 0, 3); + + mInput.insertWidget (0, &mText); + mInput.insertWidget (1, &mRecordState); + + mLayout->addWidget (&mInput, 0, 1); + + connect (&mMode, SIGNAL (activated (int)), this, SLOT (modeSelected (int))); + + connect (&mText, SIGNAL (textChanged (const QString&)), + this, SLOT (textChanged (const QString&))); + + 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); + + mLayout->addWidget (&mReplace, 1, 3); + + // layout adjustments + mLayout->setColumnMinimumWidth (2, 50); + mLayout->setColumnStretch (1, 1); + + mLayout->setContentsMargins (0, 0, 0, 0); + + connect (&mReplace, (SIGNAL (clicked (bool))), this, SLOT (replaceAll (bool))); + + // update + modeSelected (0); + + updateSearchButton(); +} + +void CSVTools::SearchBox::setSearchMode (bool enabled) +{ + mSearchEnabled = enabled; + updateSearchButton(); +} + +CSMTools::Search CSVTools::SearchBox::getSearch() const +{ + CSMTools::Search::Type type = static_cast (mMode.currentIndex()); + + switch (type) + { + case CSMTools::Search::Type_Text: + case CSMTools::Search::Type_Id: + + return CSMTools::Search (type, std::string (mText.text().toUtf8().data())); + + case CSMTools::Search::Type_TextRegEx: + case CSMTools::Search::Type_IdRegEx: + + return CSMTools::Search (type, QRegExp (mText.text().toUtf8().data(), Qt::CaseInsensitive)); + + case CSMTools::Search::Type_RecordState: + + return CSMTools::Search (type, mRecordState.currentIndex()); + + case CSMTools::Search::Type_None: + + break; + } + + 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::setEditLock (bool locked) +{ + mReplace.setEnabled (!locked); +} + +void CSVTools::SearchBox::modeSelected (int index) +{ + switch (index) + { + case CSMTools::Search::Type_Text: + case CSMTools::Search::Type_TextRegEx: + case CSMTools::Search::Type_Id: + case CSMTools::Search::Type_IdRegEx: + + mInput.setCurrentIndex (0); + mReplaceInput.setCurrentIndex (0); + break; + + case CSMTools::Search::Type_RecordState: + mInput.setCurrentIndex (1); + mReplaceInput.setCurrentIndex (1); + break; + } + + updateSearchButton(); +} + +void CSVTools::SearchBox::textChanged (const QString& text) +{ + updateSearchButton(); +} + +void CSVTools::SearchBox::startSearch (bool checked) +{ + if (mSearch.isEnabled()) + emit startSearch (getSearch()); +} + +void CSVTools::SearchBox::replaceAll (bool checked) +{ + emit replaceAll(); +} diff --git a/apps/opencs/view/tools/searchbox.hpp b/apps/opencs/view/tools/searchbox.hpp new file mode 100644 index 000000000..433c09693 --- /dev/null +++ b/apps/opencs/view/tools/searchbox.hpp @@ -0,0 +1,70 @@ +#ifndef CSV_TOOLS_SEARCHBOX_H +#define CSV_TOOLS_SEARCHBOX_H + +#include +#include +#include +#include +#include +#include + +class QGridLayout; + +namespace CSMTools +{ + class Search; +} + +namespace CSVTools +{ + class SearchBox : public QWidget + { + Q_OBJECT + + QStackedWidget mInput; + QLineEdit mText; + QComboBox mRecordState; + QPushButton mSearch; + QGridLayout *mLayout; + QComboBox mMode; + bool mSearchEnabled; + QStackedWidget mReplaceInput; + QLineEdit mReplaceText; + QLabel mReplacePlaceholder; + QPushButton mReplace; + + private: + + void updateSearchButton(); + + public: + + SearchBox (QWidget *parent = 0); + + void setSearchMode (bool enabled); + + CSMTools::Search getSearch() const; + + std::string getReplaceText() const; + + void setEditLock (bool locked); + + private slots: + + void modeSelected (int index); + + void textChanged (const QString& text); + + void startSearch (bool checked = true); + + void replaceAll (bool checked); + + signals: + + void startSearch (const CSMTools::Search& search); + + void replaceAll(); + }; +} + +#endif diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp new file mode 100644 index 000000000..5743ad761 --- /dev/null +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -0,0 +1,119 @@ + +#include "searchsubview.hpp" + +#include + +#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" + +void CSVTools::SearchSubView::replace (bool selection) +{ + if (mLocked) + return; + + std::vector indices = mTable->getReplaceIndices (selection); + + 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); + } +} + +CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) +: CSVDoc::SubView (id), mDocument (document), mPaddingBefore (10), mPaddingAfter (10), + mLocked (false) +{ + QVBoxLayout *layout = new QVBoxLayout; + + layout->setContentsMargins (QMargins (0, 0, 0, 0)); + + layout->addWidget (&mSearchBox); + + layout->addWidget (mTable = new ReportTable (document, id, true), 2); + + QWidget *widget = new QWidget; + + widget->setLayout (layout); + + setWidget (widget); + + stateChanged (document.getState(), &document); + + 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 *))); + + connect (&mSearchBox, SIGNAL (startSearch (const CSMTools::Search&)), + this, SLOT (startSearch (const CSMTools::Search&))); + + connect (&mSearchBox, SIGNAL (replaceAll()), this, SLOT (replaceAllRequest())); +} + +void CSVTools::SearchSubView::setEditLock (bool locked) +{ + mLocked = locked; + mSearchBox.setEditLock (locked); +} + +void CSVTools::SearchSubView::updateUserSetting (const QString &name, const QStringList &list) +{ + mTable->updateUserSetting (name, list); + + if (!list.empty()) + { + if (name=="search/char-before") + mPaddingBefore = list.at (0).toInt(); + else if (name=="search/char-after") + mPaddingAfter = list.at (0).toInt(); + } +} + +void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document) +{ + mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching)); +} + +void CSVTools::SearchSubView::startSearch (const CSMTools::Search& search) +{ + mSearch = search; + mSearch.setPadding (mPaddingBefore, mPaddingAfter); + + mTable->clear(); + mDocument.runSearch (getUniversalId(), mSearch); +} + +void CSVTools::SearchSubView::replaceRequest() +{ + replace (true); +} + +void CSVTools::SearchSubView::replaceAllRequest() +{ + replace (false); +} diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp new file mode 100644 index 000000000..eeefa9afb --- /dev/null +++ b/apps/opencs/view/tools/searchsubview.hpp @@ -0,0 +1,58 @@ +#ifndef CSV_TOOLS_SEARCHSUBVIEW_H +#define CSV_TOOLS_SEARCHSUBVIEW_H + +#include "../../model/tools/search.hpp" + +#include "../doc/subview.hpp" + +#include "searchbox.hpp" + +class QTableView; +class QModelIndex; + +namespace CSMDoc +{ + class Document; +} + +namespace CSVTools +{ + class ReportTable; + + class SearchSubView : public CSVDoc::SubView + { + Q_OBJECT + + ReportTable *mTable; + SearchBox mSearchBox; + CSMDoc::Document& mDocument; + int mPaddingBefore; + int mPaddingAfter; + CSMTools::Search mSearch; + bool mLocked; + + private: + + void replace (bool selection); + + public: + + SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); + + virtual void setEditLock (bool locked); + + virtual void updateUserSetting (const QString &, const QStringList &); + + private slots: + + void stateChanged (int state, CSMDoc::Document *document); + + void startSearch (const CSMTools::Search& search); + + void replaceRequest(); + + void replaceAllRequest(); + }; +} + +#endif diff --git a/apps/opencs/view/tools/subviews.cpp b/apps/opencs/view/tools/subviews.cpp index a50b5724a..8a343ebe8 100644 --- a/apps/opencs/view/tools/subviews.cpp +++ b/apps/opencs/view/tools/subviews.cpp @@ -4,6 +4,7 @@ #include "../doc/subviewfactoryimp.hpp" #include "reportsubview.hpp" +#include "searchsubview.hpp" void CSVTools::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) { @@ -11,4 +12,6 @@ void CSVTools::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactory); manager.add (CSMWorld::UniversalId::Type_LoadErrorLog, new CSVDoc::SubViewFactory); + manager.add (CSMWorld::UniversalId::Type_Search, + new CSVDoc::SubViewFactory); } diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 211462a26..ea9dcee8c 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -22,7 +22,7 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: for (int i=0; icolumnCount(); ++i) if (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display)== - CSMWorld::ColumnBase::Display_Script) + CSMWorld::ColumnBase::Display_ScriptFile) { mColumn = i; break;