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;