From 78c6268891020b0da1b30245a05c787e458f983e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 25 Mar 2015 11:56:14 +0100 Subject: [PATCH] added search class and search box widget --- apps/opencs/CMakeLists.txt | 4 +- apps/opencs/model/tools/search.cpp | 213 +++++++++++++++++++++++ apps/opencs/model/tools/search.hpp | 82 +++++++++ apps/opencs/model/world/columnbase.cpp | 72 +++++++- apps/opencs/model/world/columnbase.hpp | 6 + apps/opencs/view/tools/searchbox.cpp | 145 +++++++++++++++ apps/opencs/view/tools/searchbox.hpp | 57 ++++++ apps/opencs/view/tools/searchsubview.cpp | 29 ++- apps/opencs/view/tools/searchsubview.hpp | 7 + 9 files changed, 611 insertions(+), 4 deletions(-) create mode 100644 apps/opencs/model/tools/search.cpp create mode 100644 apps/opencs/model/tools/search.hpp create mode 100644 apps/opencs/view/tools/searchbox.cpp create mode 100644 apps/opencs/view/tools/searchbox.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index e3b44ac91..eb83b14d5 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 ) @@ -91,7 +91,7 @@ opencs_hdrs_noqt (view/render opencs_units (view/tools - reportsubview reporttable searchsubview + reportsubview reporttable searchsubview searchbox ) opencs_units_noqt (view/tools diff --git a/apps/opencs/model/tools/search.cpp b/apps/opencs/model/tools/search.cpp new file mode 100644 index 000000000..1b7be6fbf --- /dev/null +++ b/apps/opencs/model/tools/search.cpp @@ -0,0 +1,213 @@ + +#include "search.hpp" + +#include +#include + +#include "../../model/doc/messages.hpp" + +#include "../../model/world/idtablebase.hpp" +#include "../../model/world/columnbase.hpp" +#include "../../model/world/universalid.hpp" + +void CSMTools::Search::searchTextCell (const CSMWorld::IdTableBase *model, + const QModelIndex& index, const CSMWorld::UniversalId& id, bool multiple, + 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 message; + message << text.mid (pos).toUtf8().data(); + + std::ostringstream hint; + message << "r: " << index.column() << " " << pos << " " << search.length(); + + messages.add (id, message.str(), hint.str()); + + if (!multiple) + break; + + pos += search.length(); + } +} + +void CSMTools::Search::searchRegExCell (const CSMWorld::IdTableBase *model, + const QModelIndex& index, const CSMWorld::UniversalId& id, bool multiple, + CSMDoc::Messages& messages) const +{ + QString text = model->data (index).toString(); + + int pos = 0; + + while ((pos = mRegExp.indexIn (text, pos))!=-1) + { + std::ostringstream message; + message << text.mid (pos).toUtf8().data(); + + int length = mRegExp.matchedLength(); + + std::ostringstream hint; + message << "r: " << index.column() << " " << pos << " " << length; + + messages.add (id, message.str(), hint.str()); + + if (!multiple) + break; + + pos += length; + } +} + +void CSMTools::Search::searchRecordStateCell (const CSMWorld::IdTableBase *model, + const QModelIndex& index, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) const +{ + int data = model->data (index).toInt(); + + if (data==mValue) + { + std::vector states = + CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_Modification); + + std::ostringstream message; + message << id.getId() << " " << states.at (data); + + std::ostringstream hint; + message << "r: " << index.column(); + + messages.add (id, message.str(), hint.str()); + } +} + +CSMTools::Search::Search() : mType (Type_None) {} + +CSMTools::Search::Search (Type type, const std::string& value) +: mType (type), mText (value) +{ + if (type!=Type_Text && type!=Type_Reference) + throw std::logic_error ("Invalid search parameter (string)"); +} + +CSMTools::Search::Search (Type type, const QRegExp& value) +: mType (type), mRegExp (value) +{ + if (type!=Type_TextRegEx && type!=Type_ReferenceRegEx) + throw std::logic_error ("Invalid search parameter (RegExp)"); +} + +CSMTools::Search::Search (Type type, int value) +: mType (type), mValue (value) +{ + 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; + bool multiple = false; + + switch (mType) + { + case Type_Text: + case Type_TextRegEx: + + if (CSMWorld::ColumnBase::isText (display) || + CSMWorld::ColumnBase::isScript (display)) + { + consider = true; + multiple = true; + } + + break; + + case Type_Reference: + case Type_ReferenceRegEx: + + if (CSMWorld::ColumnBase::isId (display)) + { + consider = true; + } + else if (CSMWorld::ColumnBase::isScript (display)) + { + consider = true; + multiple = true; + } + + break; + + case Type_RecordState: + + if (display==CSMWorld::ColumnBase::Display_RecordState) + consider = true; + + break; + + case Type_None: + + break; + } + + if (consider) + mColumns.insert (std::make_pair (i, multiple)); + } + + 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::map::const_iterator iter (mColumns.begin()); iter!=mColumns.end(); + ++iter) + { + QModelIndex index = model->index (row, iter->first); + + 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()); + + switch (mType) + { + case Type_Text: + case Type_Reference: + + searchTextCell (model, index, id, iter->second, messages); + break; + + case Type_TextRegEx: + case Type_ReferenceRegEx: + + searchRegExCell (model, index, id, iter->second, messages); + break; + + case Type_RecordState: + + searchRecordStateCell (model, index, id, messages); + break; + + case Type_None: + + break; + } + } +} diff --git a/apps/opencs/model/tools/search.hpp b/apps/opencs/model/tools/search.hpp new file mode 100644 index 000000000..3683f9c9b --- /dev/null +++ b/apps/opencs/model/tools/search.hpp @@ -0,0 +1,82 @@ +#ifndef CSM_TOOLS_SEARCH_H +#define CSM_TOOLS_SEARCH_H + +#include +#include + +#include +#include + +class QModelIndex; + +namespace CSMDoc +{ + class Messages; +} + +namespace CSMWorld +{ + class IdTableBase; + class UniversalId; +} + +namespace CSMTools +{ + class Search + { + public: + + enum Type + { + Type_Text = 0, + Type_TextRegEx = 1, + Type_Reference = 2, + Type_ReferenceRegEx = 3, + Type_RecordState = 4, + Type_None + }; + + private: + + Type mType; + std::string mText; + QRegExp mRegExp; + int mValue; + std::map mColumns; // column, multiple finds per cell + int mIdColumn; + int mTypeColumn; + + void searchTextCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, + const CSMWorld::UniversalId& id, bool multiple, CSMDoc::Messages& messages) const; + + void searchRegExCell (const CSMWorld::IdTableBase *model, const QModelIndex& index, + const CSMWorld::UniversalId& id, bool multiple, CSMDoc::Messages& messages) const; + + void searchRecordStateCell (const CSMWorld::IdTableBase *model, + const QModelIndex& index, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) 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; + }; +} + +Q_DECLARE_METATYPE (CSMTools::Search) + +#endif diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 665ab9354..f4f2310b6 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -19,7 +19,77 @@ 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_Mesh, + Display_Icon, + Display_Music, + Display_SoundRes, + Display_Texture, + Display_Video, + + 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_Script || display==Display_ScriptLines; +} diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index db9b8b3c6..c49785154 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -122,6 +122,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/view/tools/searchbox.cpp b/apps/opencs/view/tools/searchbox.cpp new file mode 100644 index 000000000..1b11d8a4d --- /dev/null +++ b/apps/opencs/view/tools/searchbox.cpp @@ -0,0 +1,145 @@ + +#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) +{ + 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())); + + mLayout = new QGridLayout (this); + + mMode.addItem ("Text"); + mMode.addItem ("Text (RegEx)"); + mMode.addItem ("Reference"); + mMode.addItem ("Reference (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); + + mLayout->setColumnMinimumWidth (2, 50); + mLayout->setColumnStretch (1, 1); + + mLayout->setContentsMargins (0, 0, 0, 0); + + 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))); + + 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_Reference: + + return CSMTools::Search (type, std::string (mText.text().toUtf8().data())); + + case CSMTools::Search::Type_TextRegEx: + case CSMTools::Search::Type_ReferenceRegEx: + + 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"); +} + +void CSVTools::SearchBox::modeSelected (int index) +{ + switch (index) + { + case CSMTools::Search::Type_Text: + case CSMTools::Search::Type_TextRegEx: + case CSMTools::Search::Type_Reference: + case CSMTools::Search::Type_ReferenceRegEx: + + mInput.setCurrentIndex (0); + break; + + case CSMTools::Search::Type_RecordState: + mInput.setCurrentIndex (1); + break; + } + + updateSearchButton(); +} + +void CSVTools::SearchBox::textChanged (const QString& text) +{ + updateSearchButton(); +} + +void CSVTools::SearchBox::startSearch (bool checked) +{ + emit startSearch (getSearch()); +} diff --git a/apps/opencs/view/tools/searchbox.hpp b/apps/opencs/view/tools/searchbox.hpp new file mode 100644 index 000000000..dc975cac9 --- /dev/null +++ b/apps/opencs/view/tools/searchbox.hpp @@ -0,0 +1,57 @@ +#ifndef CSV_TOOLS_SEARCHBOX_H +#define CSV_TOOLS_SEARCHBOX_H + +#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; + + private: + + void updateSearchButton(); + + public: + + SearchBox (QWidget *parent = 0); + + void setSearchMode (bool enabled); + + CSMTools::Search getSearch() const; + + private slots: + + void modeSelected (int index); + + void textChanged (const QString& text); + + void startSearch (bool checked); + + signals: + + void startSearch (const CSMTools::Search& search); + }; +} + +#endif diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index 7173a6634..bc818df07 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -1,15 +1,37 @@ #include "searchsubview.hpp" +#include + +#include "../../model/doc/document.hpp" + #include "reporttable.hpp" +#include "searchbox.hpp" CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : CSVDoc::SubView (id) { - setWidget (mTable = new ReportTable (document, id, this)); + QVBoxLayout *layout = new QVBoxLayout; + layout->setContentsMargins (QMargins (0, 0, 0, 0)); + + layout->addWidget (&mSearchBox); + + layout->addWidget (mTable = new ReportTable (document, id), 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 (&document, SIGNAL (stateChanged (int, CSMDoc::Document *)), + this, SLOT (stateChanged (int, CSMDoc::Document *))); } void CSVTools::SearchSubView::setEditLock (bool locked) @@ -21,3 +43,8 @@ void CSVTools::SearchSubView::updateUserSetting (const QString &name, const QStr { mTable->updateUserSetting (name, list); } + +void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document) +{ + mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching)); +} diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp index abedd5e6d..b389805bb 100644 --- a/apps/opencs/view/tools/searchsubview.hpp +++ b/apps/opencs/view/tools/searchsubview.hpp @@ -3,6 +3,8 @@ #include "../doc/subview.hpp" +#include "searchbox.hpp" + class QTableView; class QModelIndex; @@ -20,6 +22,7 @@ namespace CSVTools Q_OBJECT ReportTable *mTable; + SearchBox mSearchBox; public: @@ -28,6 +31,10 @@ namespace CSVTools virtual void setEditLock (bool locked); virtual void updateUserSetting (const QString &, const QStringList &); + + private slots: + + void stateChanged (int state, CSMDoc::Document *document); }; }