mirror of https://github.com/OpenMW/openmw.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
313 lines
9.0 KiB
C++
313 lines
9.0 KiB
C++
#include "search.hpp"
|
|
|
|
#include <QModelIndex>
|
|
#include <QRegularExpression>
|
|
#include <QString>
|
|
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <apps/opencs/model/world/columns.hpp>
|
|
|
|
#include "../doc/document.hpp"
|
|
#include "../doc/messages.hpp"
|
|
|
|
#include "../world/columnbase.hpp"
|
|
#include "../world/commands.hpp"
|
|
#include "../world/idtablebase.hpp"
|
|
#include "../world/universalid.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;
|
|
|
|
Qt::CaseSensitivity caseSensitivity = mCase ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
|
while ((pos = text.indexOf(search, pos, caseSensitivity)) != -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
|
|
{
|
|
// TODO: verify regular expression before starting a search
|
|
if (!mRegExp.isValid())
|
|
return;
|
|
|
|
QString text = model->data(index).toString();
|
|
|
|
QRegularExpressionMatchIterator i = mRegExp.globalMatch(text);
|
|
while (i.hasNext())
|
|
{
|
|
QRegularExpressionMatch match = i.next();
|
|
|
|
int pos = match.capturedStart();
|
|
int length = match.capturedLength();
|
|
|
|
std::ostringstream hint;
|
|
hint << (writable ? 'R' : 'r') << ": " << model->getColumnId(index.column()) << " " << pos << " " << length;
|
|
|
|
messages.add(id, formatDescription(text, pos, length).toUtf8().data(), hint.str());
|
|
}
|
|
}
|
|
|
|
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<std::pair<int, std::string>> states
|
|
= CSMWorld::Columns::getEnums(CSMWorld::Columns::ColumnId_Modification);
|
|
|
|
const std::string hint = "r: " + std::to_string(model->getColumnId(index.column()));
|
|
messages.add(id, states.at(data).second, hint);
|
|
}
|
|
}
|
|
|
|
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 + "<b>" + highlight + "</b>" + 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)
|
|
, mValue(0)
|
|
, mCase(false)
|
|
, mIdColumn(0)
|
|
, mTypeColumn(0)
|
|
, mPaddingBefore(10)
|
|
, mPaddingAfter(10)
|
|
{
|
|
}
|
|
|
|
CSMTools::Search::Search(Type type, bool caseSensitive, const std::string& value)
|
|
: mType(type)
|
|
, mText(value)
|
|
, mValue(0)
|
|
, mCase(caseSensitive)
|
|
, mIdColumn(0)
|
|
, mTypeColumn(0)
|
|
, mPaddingBefore(10)
|
|
, mPaddingAfter(10)
|
|
{
|
|
if (type != Type_Text && type != Type_Id)
|
|
throw std::logic_error("Invalid search parameter (string)");
|
|
}
|
|
|
|
CSMTools::Search::Search(Type type, bool caseSensitive, const QRegularExpression& value)
|
|
: mType(type)
|
|
, mRegExp(value)
|
|
, mValue(0)
|
|
, mCase(caseSensitive)
|
|
, mIdColumn(0)
|
|
, mTypeColumn(0)
|
|
, mPaddingBefore(10)
|
|
, mPaddingAfter(10)
|
|
{
|
|
mRegExp.setPatternOptions(mCase ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption);
|
|
if (type != Type_TextRegEx && type != Type_IdRegEx)
|
|
throw std::logic_error("Invalid search parameter (RegExp)");
|
|
}
|
|
|
|
CSMTools::Search::Search(Type type, bool caseSensitive, int value)
|
|
: mType(type)
|
|
, mValue(value)
|
|
, mCase(caseSensitive)
|
|
, mIdColumn(0)
|
|
, mTypeColumn(0)
|
|
, 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 < columns; ++i)
|
|
{
|
|
CSMWorld::ColumnBase::Display display = static_cast<CSMWorld::ColumnBase::Display>(
|
|
model->headerData(i, Qt::Horizontal, static_cast<int>(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<int>::const_iterator iter(mColumns.begin()); iter != mColumns.end(); ++iter)
|
|
{
|
|
QModelIndex index = model->index(row, *iter);
|
|
|
|
CSMWorld::UniversalId::Type type
|
|
= static_cast<CSMWorld::UniversalId::Type>(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<CSMWorld::Columns::ColumnId>(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())));
|
|
}
|
|
}
|
|
|
|
bool CSMTools::Search::verify(CSMDoc::Document& document, CSMWorld::IdTableBase* model, const CSMWorld::UniversalId& id,
|
|
const std::string& messageHint) const
|
|
{
|
|
CSMDoc::Messages messages(CSMDoc::Message::Severity_Info);
|
|
|
|
int row = model->getModelIndex(id.getId(), model->findColumnIndex(CSMWorld::Columns::ColumnId_Id)).row();
|
|
|
|
searchRow(model, row, messages);
|
|
|
|
for (CSMDoc::Messages::Iterator iter(messages.begin()); iter != messages.end(); ++iter)
|
|
if (iter->mHint == messageHint)
|
|
return true;
|
|
|
|
return false;
|
|
}
|