Merge branch 'filter'

actorid
Marc Zinnschlag 12 years ago
commit f605dcdd24

@ -107,10 +107,18 @@ opencs_units_noqt (model/settings
settingsitem
)
opencs_units_noqt (model/filter
node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode
)
opencs_hdrs_noqt (model/filter
filter
)
opencs_units (view/filter
filtercreator filterbox recordfilterbox editwidget
)
set (OPENCS_US
)

@ -0,0 +1,20 @@
#include "andnode.hpp"
#include <sstream>
CSMFilter::AndNode::AndNode (const std::vector<boost::shared_ptr<Node> >& nodes)
: NAryNode (nodes, "and")
{}
bool CSMFilter::AndNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
int size = getSize();
for (int i=0; i<size; ++i)
if (!(*this)[i].test (table, row, columns))
return false;
return true;
}

@ -0,0 +1,23 @@
#ifndef CSM_FILTER_ANDNODE_H
#define CSM_FILTER_ANDNODE_H
#include "narynode.hpp"
namespace CSMFilter
{
class AndNode : public NAryNode
{
bool mAnd;
public:
AndNode (const std::vector<boost::shared_ptr<Node> >& nodes);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
};
}
#endif

@ -0,0 +1,15 @@
#include "booleannode.hpp"
CSMFilter::BooleanNode::BooleanNode (bool true_) : mTrue (true_) {}
bool CSMFilter::BooleanNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
return mTrue;
}
std::string CSMFilter::BooleanNode::toString (bool numericColumns) const
{
return mTrue ? "true" : "false";
}

@ -0,0 +1,29 @@
#ifndef CSM_FILTER_BOOLEANNODE_H
#define CSM_FILTER_BOOLEANNODE_H
#include "leafnode.hpp"
namespace CSMFilter
{
class BooleanNode : public LeafNode
{
bool mTrue;
public:
BooleanNode (bool true_);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
virtual std::string toString (bool numericColumns) const;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
#endif

@ -11,15 +11,14 @@ namespace CSMFilter
/// \brief Wrapper for Filter record
struct Filter : public ESM::Filter
{
enum scope
enum Scope
{
Global = 0,
Local = 1,
Session = 2,
Content = 3
Scope_Project = 0, // per project
Scope_Session = 1, // exists only for one editing session; not saved
Scope_Content = 2 // embedded in the edited content file
};
scope mScope;
Scope mScope;
};
}

@ -0,0 +1,8 @@
#include "leafnode.hpp"
std::vector<int> CSMFilter::LeafNode::getReferencedColumns() const
{
return std::vector<int>();
}

@ -0,0 +1,20 @@
#ifndef CSM_FILTER_LEAFNODE_H
#define CSM_FILTER_LEAFNODE_H
#include <memory>
#include "node.hpp"
namespace CSMFilter
{
class LeafNode : public Node
{
public:
virtual std::vector<int> getReferencedColumns() const;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
};
}
#endif

@ -0,0 +1,60 @@
#include "narynode.hpp"
#include <sstream>
CSMFilter::NAryNode::NAryNode (const std::vector<boost::shared_ptr<Node> >& nodes,
const std::string& name)
: mNodes (nodes), mName (name)
{}
int CSMFilter::NAryNode::getSize() const
{
return mNodes.size();
}
const CSMFilter::Node& CSMFilter::NAryNode::operator[] (int index) const
{
return *mNodes.at (index);
}
std::vector<int> CSMFilter::NAryNode::getReferencedColumns() const
{
std::vector<int> columns;
for (std::vector<boost::shared_ptr<Node> >::const_iterator iter (mNodes.begin());
iter!=mNodes.end(); ++iter)
{
std::vector<int> columns2 = (*iter)->getReferencedColumns();
columns.insert (columns.end(), columns2.begin(), columns2.end());
}
return columns;
}
std::string CSMFilter::NAryNode::toString (bool numericColumns) const
{
std::ostringstream stream;
stream << mName << " (";
bool first = true;
int size = getSize();
for (int i=0; i<size; ++i)
{
if (first)
first = false;
else
stream << ", ";
stream << (*this)[i].toString (numericColumns);
}
stream << ")";
return stream.str();
}

@ -0,0 +1,37 @@
#ifndef CSM_FILTER_NARYNODE_H
#define CSM_FILTER_NARYNODE_H
#include <vector>
#include <string>
#include <boost/shared_ptr.hpp>
#include "node.hpp"
namespace CSMFilter
{
class NAryNode : public Node
{
std::vector<boost::shared_ptr<Node> > mNodes;
std::string mName;
public:
NAryNode (const std::vector<boost::shared_ptr<Node> >& nodes, const std::string& name);
int getSize() const;
const Node& operator[] (int index) const;
virtual std::vector<int> getReferencedColumns() const;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
virtual std::string toString (bool numericColumns) const;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
#endif

@ -0,0 +1,6 @@
#include "node.hpp"
CSMFilter::Node::Node() {}
CSMFilter::Node::~Node() {}

@ -0,0 +1,53 @@
#ifndef CSM_FILTER_NODE_H
#define CSM_FILTER_NODE_H
#include <string>
#include <map>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <QMetaType>
namespace CSMWorld
{
class IdTable;
}
namespace CSMFilter
{
/// \brief Root class for the filter node hierarchy
///
/// \note When the function documentation for this class mentions "this node", this should be
/// interpreted as "the node and all its children".
class Node
{
// not implemented
Node (const Node&);
Node& operator= (const Node&);
public:
Node();
virtual ~Node();
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const = 0;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
virtual std::vector<int> getReferencedColumns() const = 0;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
virtual std::string toString (bool numericColumns) const = 0;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
Q_DECLARE_METATYPE (boost::shared_ptr<CSMFilter::Node>)
#endif

@ -0,0 +1,10 @@
#include "notnode.hpp"
CSMFilter::NotNode::NotNode (boost::shared_ptr<Node> child) : UnaryNode (child, "not") {}
bool CSMFilter::NotNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
return !getChild().test (table, row, columns);
}

@ -0,0 +1,21 @@
#ifndef CSM_FILTER_NOTNODE_H
#define CSM_FILTER_NOTNODE_H
#include "unarynode.hpp"
namespace CSMFilter
{
class NotNode : public UnaryNode
{
public:
NotNode (boost::shared_ptr<Node> child);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
};
}
#endif

@ -0,0 +1,20 @@
#include "ornode.hpp"
#include <sstream>
CSMFilter::OrNode::OrNode (const std::vector<boost::shared_ptr<Node> >& nodes)
: NAryNode (nodes, "or")
{}
bool CSMFilter::OrNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
int size = getSize();
for (int i=0; i<size; ++i)
if ((*this)[i].test (table, row, columns))
return true;
return false;
}

@ -0,0 +1,23 @@
#ifndef CSM_FILTER_ORNODE_H
#define CSM_FILTER_ORNODE_H
#include "narynode.hpp"
namespace CSMFilter
{
class OrNode : public NAryNode
{
bool mAnd;
public:
OrNode (const std::vector<boost::shared_ptr<Node> >& nodes);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
};
}
#endif

@ -0,0 +1,544 @@
#include "parser.hpp"
#include <cctype>
#include <stdexcept>
#include <sstream>
#include <components/misc/stringops.hpp>
#include "../world/columns.hpp"
#include "booleannode.hpp"
#include "ornode.hpp"
#include "andnode.hpp"
#include "notnode.hpp"
#include "textnode.hpp"
#include "valuenode.hpp"
namespace CSMFilter
{
struct Token
{
enum Type
{
Type_EOS,
Type_None,
Type_String,
Type_Number,
Type_Open,
Type_Close,
Type_OpenSquare,
Type_CloseSquare,
Type_Comma,
Type_Keyword_True, ///< \attention Keyword enums must be arranged continuously.
Type_Keyword_False,
Type_Keyword_And,
Type_Keyword_Or,
Type_Keyword_Not,
Type_Keyword_Text,
Type_Keyword_Value
};
Type mType;
std::string mString;
double mNumber;
Token (Type type);
Token (const std::string& string);
Token (double number);
operator bool() const;
};
Token::Token (Type type) : mType (type) {}
Token::Token (const std::string& string) : mType (Type_String), mString (string) {}
Token::Token (double number) : mType (Type_Number), mNumber (number) {}
Token::operator bool() const
{
return mType!=Type_None;
}
bool operator== (const Token& left, const Token& right)
{
if (left.mType!=right.mType)
return false;
switch (left.mType)
{
case Token::Type_String: return left.mString==right.mString;
case Token::Type_Number: return left.mNumber==right.mNumber;
default: return true;
}
}
}
CSMFilter::Token CSMFilter::Parser::getStringToken()
{
std::string string;
int size = static_cast<int> (mInput.size());
for (; mIndex<size; ++mIndex)
{
char c = mInput[mIndex];
if (std::isalpha (c) || c=='_' || (!string.empty() && std::isdigit (c)) || c=='"' ||
(!string.empty() && string[0]=='"'))
string += c;
else
break;
if (c=='"' && string.size()>1)
{
++mIndex;
break;
}
};
if (!string.empty())
{
if (string[0]=='"' && (string[string.size()-1]!='"' || string.size()<2) )
{
error();
return Token (Token::Type_None);
}
if (string[0]!='"' && string[string.size()-1]=='"')
{
error();
return Token (Token::Type_None);
}
if (string[0]=='"')
string = string.substr (1, string.size()-2);
}
return checkKeywords (string);
}
CSMFilter::Token CSMFilter::Parser::getNumberToken()
{
std::string string;
int size = static_cast<int> (mInput.size());
bool hasDecimalPoint = false;
bool hasDigit = false;
for (; mIndex<size; ++mIndex)
{
char c = mInput[mIndex];
if (std::isdigit (c))
{
string += c;
hasDigit = true;
}
else if (c=='.' && !hasDecimalPoint)
{
string += c;
hasDecimalPoint = true;
}
else if (string.empty() && c=='-')
string += c;
else
break;
}
if (!hasDigit)
{
error();
return Token (Token::Type_None);
}
float value;
std::istringstream stream (string.c_str());
stream >> value;
return value;
}
CSMFilter::Token CSMFilter::Parser::checkKeywords (const Token& token)
{
static const char *sKeywords[] =
{
"true", "false",
"and", "or", "not",
"text", "value",
0
};
std::string string = Misc::StringUtils::lowerCase (token.mString);
for (int i=0; sKeywords[i]; ++i)
if (sKeywords[i]==string)
return Token (static_cast<Token::Type> (i+Token::Type_Keyword_True));
return token;
}
CSMFilter::Token CSMFilter::Parser::getNextToken()
{
int size = static_cast<int> (mInput.size());
char c = 0;
for (; mIndex<size; ++mIndex)
{
c = mInput[mIndex];
if (c!=' ')
break;
}
if (mIndex>=size)
return Token (Token::Type_EOS);
switch (c)
{
case '(': ++mIndex; return Token (Token::Type_Open);
case ')': ++mIndex; return Token (Token::Type_Close);
case '[': ++mIndex; return Token (Token::Type_OpenSquare);
case ']': ++mIndex; return Token (Token::Type_CloseSquare);
case ',': ++mIndex; return Token (Token::Type_Comma);
}
if (c=='"' || c=='_' || std::isalpha (c))
return getStringToken();
if (c=='-' || c=='.' || std::isdigit (c))
return getNumberToken();
error();
return Token (Token::Type_None);
}
boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseImp (bool allowEmpty)
{
if (Token token = getNextToken())
{
switch (token.mType)
{
case Token::Type_Keyword_True:
return boost::shared_ptr<CSMFilter::Node> (new BooleanNode (true));
case Token::Type_Keyword_False:
return boost::shared_ptr<CSMFilter::Node> (new BooleanNode (false));
case Token::Type_Keyword_And:
case Token::Type_Keyword_Or:
return parseNAry (token);
case Token::Type_Keyword_Not:
{
boost::shared_ptr<CSMFilter::Node> node = parseImp();
if (mError)
return boost::shared_ptr<Node>();
return boost::shared_ptr<CSMFilter::Node> (new NotNode (node));
}
case Token::Type_Keyword_Text:
return parseText();
case Token::Type_Keyword_Value:
return parseValue();
case Token::Type_EOS:
if (!allowEmpty)
error();
return boost::shared_ptr<Node>();
default:
error();
}
}
return boost::shared_ptr<Node>();
}
boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseNAry (const Token& keyword)
{
std::vector<boost::shared_ptr<Node> > nodes;
Token token = getNextToken();
if (token.mType!=Token::Type_Open)
{
error();
return boost::shared_ptr<Node>();
}
for (;;)
{
boost::shared_ptr<Node> node = parseImp();
if (mError)
return boost::shared_ptr<Node>();
nodes.push_back (node);
Token token = getNextToken();
if (!token || (token.mType!=Token::Type_Close && token.mType!=Token::Type_Comma))
{
error();
return boost::shared_ptr<Node>();
}
if (token.mType==Token::Type_Close)
break;
}
if (nodes.empty())
{
error();
return boost::shared_ptr<Node>();
}
switch (keyword.mType)
{
case Token::Type_Keyword_And: return boost::shared_ptr<CSMFilter::Node> (new AndNode (nodes));
case Token::Type_Keyword_Or: return boost::shared_ptr<CSMFilter::Node> (new OrNode (nodes));
default: error(); return boost::shared_ptr<Node>();
}
}
boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseText()
{
Token token = getNextToken();
if (token.mType!=Token::Type_Open)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (!token)
return boost::shared_ptr<Node>();
// parse column ID
int columnId = -1;
if (token.mType==Token::Type_Number)
{
if (static_cast<int> (token.mNumber)==token.mNumber)
columnId = static_cast<int> (token.mNumber);
}
else if (token.mType==Token::Type_String)
{
columnId = CSMWorld::Columns::getId (token.mString);
}
if (columnId<0)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (token.mType!=Token::Type_Comma)
{
error();
return boost::shared_ptr<Node>();
}
// parse text pattern
token = getNextToken();
if (token.mType!=Token::Type_String)
{
error();
return boost::shared_ptr<Node>();
}
std::string text = token.mString;
token = getNextToken();
if (token.mType!=Token::Type_Close)
{
error();
return boost::shared_ptr<Node>();
}
return boost::shared_ptr<Node> (new TextNode (columnId, text));
}
boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::parseValue()
{
Token token = getNextToken();
if (token.mType!=Token::Type_Open)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (!token)
return boost::shared_ptr<Node>();
// parse column ID
int columnId = -1;
if (token.mType==Token::Type_Number)
{
if (static_cast<int> (token.mNumber)==token.mNumber)
columnId = static_cast<int> (token.mNumber);
}
else if (token.mType==Token::Type_String)
{
columnId = CSMWorld::Columns::getId (token.mString);
}
if (columnId<0)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (token.mType!=Token::Type_Comma)
{
error();
return boost::shared_ptr<Node>();
}
// parse value
double lower = 0;
double upper = 0;
bool min = false;
bool max = false;
token = getNextToken();
if (token.mType==Token::Type_Number)
{
// single value
min = max = true;
lower = upper = token.mNumber;
}
else
{
// interval
if (token.mType==Token::Type_OpenSquare)
min = true;
else if (token.mType!=Token::Type_CloseSquare && token.mType!=Token::Type_Open)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (token.mType!=Token::Type_Number)
{
error();
return boost::shared_ptr<Node>();
}
lower = token.mNumber;
token = getNextToken();
if (token.mType!=Token::Type_Comma)
{
error();
return boost::shared_ptr<Node>();
}
token = getNextToken();
if (token.mType!=Token::Type_Number)
{
error();
return boost::shared_ptr<Node>();
}
upper = token.mNumber;
token = getNextToken();
if (token.mType==Token::Type_CloseSquare)
max = true;
else if (token.mType!=Token::Type_OpenSquare && token.mType!=Token::Type_Close)
{
error();
return boost::shared_ptr<Node>();
}
}
token = getNextToken();
if (token.mType!=Token::Type_Close)
{
error();
return boost::shared_ptr<Node>();
}
return boost::shared_ptr<Node> (new ValueNode (columnId, lower, upper, min, max));
}
void CSMFilter::Parser::error()
{
mError = true;
}
CSMFilter::Parser::Parser() : mIndex (0), mError (false) {}
bool CSMFilter::Parser::parse (const std::string& filter)
{
// reset
mFilter.reset();
mError = false;
mInput = filter;
mIndex = 0;
boost::shared_ptr<Node> node = parseImp (true);
if (mError)
return false;
if (getNextToken()!=Token (Token::Type_EOS))
return false;
if (node)
mFilter = node;
else
{
// Empty filter string equals to filter "true".
mFilter.reset (new BooleanNode (true));
}
return true;
}
boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::getFilter() const
{
if (mError)
throw std::logic_error ("No filter available");
return mFilter;
}

@ -0,0 +1,53 @@
#ifndef CSM_FILTER_PARSER_H
#define CSM_FILTER_PARSER_H
#include <boost/shared_ptr.hpp>
#include "node.hpp"
namespace CSMFilter
{
struct Token;
class Parser
{
boost::shared_ptr<Node> mFilter;
std::string mInput;
int mIndex;
bool mError;
Token getStringToken();
Token getNumberToken();
Token getNextToken();
Token checkKeywords (const Token& token);
///< Turn string token into keyword token, if possible.
boost::shared_ptr<Node> parseImp (bool allowEmpty = false);
///< Will return a null-pointer, if there is nothing more to parse.
boost::shared_ptr<Node> parseNAry (const Token& keyword);
boost::shared_ptr<Node> parseText();
boost::shared_ptr<Node> parseValue();
void error();
public:
Parser();
bool parse (const std::string& filter);
///< Discards any previous calls to parse
///
/// \return Success?
boost::shared_ptr<Node> getFilter() const;
///< Throws an exception if the last call to parse did not return true.
};
}
#endif

@ -0,0 +1,62 @@
#include "textnode.hpp"
#include <sstream>
#include <stdexcept>
#include <QRegExp>
#include "../world/columns.hpp"
#include "../world/idtable.hpp"
CSMFilter::TextNode::TextNode (int columnId, const std::string& text)
: mColumnId (columnId), mText (text)
{}
bool CSMFilter::TextNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
const std::map<int, int>::const_iterator iter = columns.find (mColumnId);
if (iter==columns.end())
throw std::logic_error ("invalid column in text node test");
if (iter->second==-1)
return true;
QModelIndex index = table.index (row, iter->second);
QVariant data = table.data (index);
if (data.type()!=QVariant::String)
return false;
/// \todo make pattern syntax configurable
QRegExp regExp (QString::fromUtf8 (mText.c_str()), Qt::CaseInsensitive);
return regExp.exactMatch (data.toString());
}
std::vector<int> CSMFilter::TextNode::getReferencedColumns() const
{
return std::vector<int> (1, mColumnId);
}
std::string CSMFilter::TextNode::toString (bool numericColumns) const
{
std::ostringstream stream;
stream << "text (";
if (numericColumns)
stream << mColumnId;
else
stream
<< "\""
<< CSMWorld::Columns::getName (static_cast<CSMWorld::Columns::ColumnId> (mColumnId))
<< "\"";
stream << ", \"" << mText << "\")";
return stream.str();
}

@ -0,0 +1,33 @@
#ifndef CSM_FILTER_TEXTNODE_H
#define CSM_FILTER_TEXTNODE_H
#include "leafnode.hpp"
namespace CSMFilter
{
class TextNode : public LeafNode
{
int mColumnId;
std::string mText;
public:
TextNode (int columnId, const std::string& text);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
virtual std::vector<int> getReferencedColumns() const;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
virtual std::string toString (bool numericColumns) const;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
#endif

@ -0,0 +1,26 @@
#include "unarynode.hpp"
CSMFilter::UnaryNode::UnaryNode (boost::shared_ptr<Node> child, const std::string& name)
: mChild (child), mName (name)
{}
const CSMFilter::Node& CSMFilter::UnaryNode::getChild() const
{
return *mChild;
}
CSMFilter::Node& CSMFilter::UnaryNode::getChild()
{
return *mChild;
}
std::vector<int> CSMFilter::UnaryNode::getReferencedColumns() const
{
return mChild->getReferencedColumns();
}
std::string CSMFilter::UnaryNode::toString (bool numericColumns) const
{
return mName + " " + mChild->toString (numericColumns);
}

@ -0,0 +1,34 @@
#ifndef CSM_FILTER_UNARYNODE_H
#define CSM_FILTER_UNARYNODE_H
#include <boost/shared_ptr.hpp>
#include "node.hpp"
namespace CSMFilter
{
class UnaryNode : public Node
{
boost::shared_ptr<Node> mChild;
std::string mName;
public:
UnaryNode (boost::shared_ptr<Node> child, const std::string& name);
const Node& getChild() const;
Node& getChild();
virtual std::vector<int> getReferencedColumns() const;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
virtual std::string toString (bool numericColumns) const;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
#endif

@ -0,0 +1,71 @@
#include "valuenode.hpp"
#include <sstream>
#include <stdexcept>
#include "../world/columns.hpp"
#include "../world/idtable.hpp"
CSMFilter::ValueNode::ValueNode (int columnId,
double lower, double upper, bool min, bool max)
: mColumnId (columnId), mLower (lower), mUpper (upper), mMin (min), mMax (max)
{}
bool CSMFilter::ValueNode::test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const
{
const std::map<int, int>::const_iterator iter = columns.find (mColumnId);
if (iter==columns.end())
throw std::logic_error ("invalid column in test value test");
if (iter->second==-1)
return true;
QModelIndex index = table.index (row, iter->second);
QVariant data = table.data (index);
if (data.type()!=QVariant::Double && data.type()!=QVariant::Bool && data.type()!=QVariant::Int &&
data.type()!=QVariant::UInt)
return false;
double value = data.toDouble();
if (mLower==mUpper && mMin && mMax)
return value==mLower;
return (mMin ? value>=mLower : value>mLower) && (mMax ? value<=mUpper : value<mUpper);
}
std::vector<int> CSMFilter::ValueNode::getReferencedColumns() const
{
return std::vector<int> (1, mColumnId);
}
std::string CSMFilter::ValueNode::toString (bool numericColumns) const
{
std::ostringstream stream;
stream << "value (";
if (numericColumns)
stream << mColumnId;
else
stream
<< "\""
<< CSMWorld::Columns::getName (static_cast<CSMWorld::Columns::ColumnId> (mColumnId))
<< "\"";
stream << ", \"";
if (mLower==mUpper && mMin && mMax)
stream << mLower;
else
stream << (mMin ? "[" : "(") << mLower << ", " << mUpper << (mMax ? "]" : ")");
stream << ")";
return stream.str();
}

@ -0,0 +1,37 @@
#ifndef CSM_FILTER_VALUENODE_H
#define CSM_FILTER_VALUENODE_H
#include "leafnode.hpp"
namespace CSMFilter
{
class ValueNode : public LeafNode
{
int mColumnId;
std::string mText;
double mLower;
double mUpper;
bool mMin;
bool mMax;
public:
ValueNode (int columnId, double lower, double upper, bool min, bool max);
virtual bool test (const CSMWorld::IdTable& table, int row,
const std::map<int, int>& columns) const;
///< \return Can the specified table row pass through to filter?
/// \param columns column ID to column index mapping
virtual std::vector<int> getReferencedColumns() const;
///< Return a list of the IDs of the columns referenced by this node. The column mapping
/// passed into test as columns must contain all columns listed here.
virtual std::string toString (bool numericColumns) const;
///< Return a string that represents this node.
///
/// \param numericColumns Use numeric IDs instead of string to represent columns.
};
}
#endif

@ -150,6 +150,7 @@ CSMWorld::Data::Data() : mRefs (mCells)
mFilters.addColumn (new StringIdColumn<CSMFilter::Filter>);
mFilters.addColumn (new RecordStateColumn<CSMFilter::Filter>);
mFilters.addColumn (new DescriptionColumn<CSMFilter::Filter>);
addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global);
addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst);

@ -1,8 +1,36 @@
#include "idtableproxymodel.hpp"
#include <vector>
#include "idtable.hpp"
void CSMWorld::IdTableProxyModel::updateColumnMap()
{
mColumnMap.clear();
if (mFilter)
{
std::vector<int> columns = mFilter->getReferencedColumns();
const IdTable& table = dynamic_cast<const IdTable&> (*sourceModel());
for (std::vector<int>::const_iterator iter (columns.begin()); iter!=columns.end(); ++iter)
mColumnMap.insert (std::make_pair (*iter,
table.searchColumnIndex (static_cast<CSMWorld::Columns::ColumnId> (*iter))));
}
}
bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent)
const
{
if (!mFilter)
return true;
return mFilter->test (
dynamic_cast<IdTable&> (*sourceModel()), sourceRow, mColumnMap);
}
CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent)
: QSortFilterProxyModel (parent)
{}
@ -10,4 +38,11 @@ CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent)
QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const
{
return mapFromSource (dynamic_cast<IdTable&> (*sourceModel()).getModelIndex (id, column));
}
void CSMWorld::IdTableProxyModel::setFilter (const boost::shared_ptr<CSMFilter::Node>& filter)
{
mFilter = filter;
updateColumnMap();
invalidateFilter();
}

@ -1,9 +1,15 @@
#ifndef CSM_WOLRD_IDTABLEPROXYMODEL_H
#define CSM_WOLRD_IDTABLEPROXYMODEL_H
#include <string>
#include <boost/shared_ptr.hpp>
#include <map>
#include <QSortFilterProxyModel>
#include <string>
#include "../filter/node.hpp"
namespace CSMWorld
{
@ -11,11 +17,22 @@ namespace CSMWorld
{
Q_OBJECT
boost::shared_ptr<CSMFilter::Node> mFilter;
std::map<int, int> mColumnMap; // column ID, column index in this model (or -1)
private:
void updateColumnMap();
bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const;
public:
IdTableProxyModel (QObject *parent = 0);
virtual QModelIndex getModelIndex (const std::string& id, int column) const;
void setFilter (const boost::shared_ptr<CSMFilter::Node>& filter);
};
}

@ -0,0 +1,26 @@
#include "editwidget.hpp"
CSVFilter::EditWidget::EditWidget (QWidget *parent)
: QLineEdit (parent)
{
mPalette = palette();
connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&)));
}
void CSVFilter::EditWidget::textChanged (const QString& text)
{
if (mParser.parse (text.toUtf8().constData()))
{
setPalette (mPalette);
emit filterChanged (mParser.getFilter());
}
else
{
QPalette palette (mPalette);
palette.setColor (QPalette::Text, Qt::red);
setPalette (palette);
/// \todo improve error reporting; mark only the faulty part
}
}

@ -0,0 +1,35 @@
#ifndef CSV_FILTER_EDITWIDGET_H
#define CSV_FILTER_EDITWIDGET_H
#include <boost/shared_ptr.hpp>
#include <QLineEdit>
#include <QPalette>
#include "../../model/filter/parser.hpp"
#include "../../model/filter/node.hpp"
namespace CSVFilter
{
class EditWidget : public QLineEdit
{
Q_OBJECT
CSMFilter::Parser mParser;
QPalette mPalette;
public:
EditWidget (QWidget *parent = 0);
signals:
void filterChanged (boost::shared_ptr<CSMFilter::Node> filter);
private slots:
void textChanged (const QString& text);
};
}
#endif

@ -0,0 +1,24 @@
#include "filterbox.hpp"
#include <QHBoxLayout>
#include "recordfilterbox.hpp"
CSVFilter::FilterBox::FilterBox (QWidget *parent)
: QWidget (parent)
{
QHBoxLayout *layout = new QHBoxLayout (this);
layout->setContentsMargins (0, 0, 0, 0);
RecordFilterBox *recordFilterBox = new RecordFilterBox (this);
layout->addWidget (recordFilterBox);
setLayout (layout);
connect (recordFilterBox,
SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)),
this, SIGNAL (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)));
}

@ -0,0 +1,25 @@
#ifndef CSV_FILTER_FILTERBOX_H
#define CSV_FILTER_FILTERBOX_H
#include <QWidget>
#include "../../model/filter/node.hpp"
namespace CSVFilter
{
class FilterBox : public QWidget
{
Q_OBJECT
public:
FilterBox (QWidget *parent = 0);
signals:
void recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter);
};
}
#endif

@ -0,0 +1,63 @@
#include "filtercreator.hpp"
#include <QComboBox>
#include <QLabel>
#include "../../model/filter/filter.hpp"
std::string CSVFilter::FilterCreator::getNamespace() const
{
switch (mScope->currentIndex())
{
case CSMFilter::Filter::Scope_Project: return "project::";
case CSMFilter::Filter::Scope_Session: return "session::";
}
return "";
}
void CSVFilter::FilterCreator::update()
{
mNamespace->setText (QString::fromUtf8 (getNamespace().c_str()));
GenericCreator::update();
}
std::string CSVFilter::FilterCreator::getId() const
{
return getNamespace() + GenericCreator::getId();
}
CSVFilter::FilterCreator::FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack,
const CSMWorld::UniversalId& id)
: GenericCreator (data, undoStack, id)
{
mNamespace = new QLabel ("::", this);
insertAtBeginning (mNamespace, false);
mScope = new QComboBox (this);
mScope->addItem ("Project");
mScope->addItem ("Session");
/// \ŧodo re-enable for OpenMW 1.1
// mScope->addItem ("Content");
connect (mScope, SIGNAL (currentIndexChanged (int)), this, SLOT (setScope (int)));
insertAtBeginning (mScope, false);
QLabel *label = new QLabel ("Scope", this);
insertAtBeginning (label, false);
mScope->setCurrentIndex (1);
}
void CSVFilter::FilterCreator::reset()
{
GenericCreator::reset();
}
void CSVFilter::FilterCreator::setScope (int index)
{
update();
}

@ -0,0 +1,41 @@
#ifndef CSV_FILTER_FILTERCREATOR_H
#define CSV_FILTER_FILTERCREATOR_H
class QComboBox;
class QLabel;
#include "../world/genericcreator.hpp"
namespace CSVFilter
{
class FilterCreator : public CSVWorld::GenericCreator
{
Q_OBJECT
QComboBox *mScope;
QLabel *mNamespace;
private:
std::string getNamespace() const;
protected:
void update();
virtual std::string getId() const;
public:
FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack,
const CSMWorld::UniversalId& id);
virtual void reset();
private slots:
void setScope (int index);
};
}
#endif

@ -0,0 +1,27 @@
#include "recordfilterbox.hpp"
#include <QHBoxLayout>
#include <QLabel>
#include "editwidget.hpp"
CSVFilter::RecordFilterBox::RecordFilterBox (QWidget *parent)
: QWidget (parent)
{
QHBoxLayout *layout = new QHBoxLayout (this);
layout->setContentsMargins (0, 0, 0, 0);
layout->addWidget (new QLabel ("Record Filter", this));
EditWidget *editWidget = new EditWidget (this);
layout->addWidget (editWidget);
setLayout (layout);
connect (
editWidget, SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)),
this, SIGNAL (filterChanged (boost::shared_ptr<CSMFilter::Node>)));
}

@ -0,0 +1,29 @@
#ifndef CSV_FILTER_RECORDFILTERBOX_H
#define CSV_FILTER_RECORDFILTERBOX_H
#include <boost/shared_ptr.hpp>
#include <QWidget>
#include <QHBoxLayout>
#include "../../model/filter/node.hpp"
namespace CSVFilter
{
class RecordFilterBox : public QWidget
{
Q_OBJECT
public:
RecordFilterBox (QWidget *parent = 0);
signals:
void filterChanged (boost::shared_ptr<CSMFilter::Node> filter);
};
}
#endif

@ -3,6 +3,8 @@
#include "../doc/subviewfactoryimp.hpp"
#include "../filter/filtercreator.hpp"
#include "tablesubview.hpp"
#include "dialoguesubview.hpp"
#include "scriptsubview.hpp"
@ -33,7 +35,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
CSMWorld::UniversalId::Type_Regions,
CSMWorld::UniversalId::Type_Birthsigns,
CSMWorld::UniversalId::Type_Spells,
CSMWorld::UniversalId::Type_Filters,
CSMWorld::UniversalId::Type_None // end marker
};
@ -56,4 +57,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
// Other stuff (combined record tables)
manager.add (CSMWorld::UniversalId::Type_RegionMap, new CSVDoc::SubViewFactory<RegionMapSubView>);
manager.add (CSMWorld::UniversalId::Type_Filters,
new CSVDoc::SubViewFactoryWithCreator<TableSubView,
CreatorFactory<CSVFilter::FilterCreator> >);
}

@ -257,9 +257,9 @@ void CSVWorld::Table::tableSizeUpdate()
int deleted = 0;
int modified = 0;
if (mModel->columnCount()>0)
if (mProxyModel->columnCount()>0)
{
int rows = mModel->rowCount();
int rows = mProxyModel->rowCount();
for (int i=0; i<rows; ++i)
{
@ -292,4 +292,9 @@ void CSVWorld::Table::requestFocus (const std::string& id)
if (index.isValid())
scrollTo (index, QAbstractItemView::PositionAtTop);
}
void CSVWorld::Table::recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter)
{
mProxyModel->setFilter (filter);
}

@ -6,6 +6,8 @@
#include <QTableView>
#include "../../model/filter/node.hpp"
class QUndoStack;
class QAction;
@ -85,6 +87,7 @@ namespace CSVWorld
void requestFocus (const std::string& id);
void recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter);
};
}

@ -5,6 +5,8 @@
#include "../../model/doc/document.hpp"
#include "../filter/filterbox.hpp"
#include "table.hpp"
#include "tablebottombox.hpp"
#include "creator.hpp"
@ -23,6 +25,10 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
layout->insertWidget (0, mTable =
new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete()), 2);
CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (this);
layout->insertWidget (0, filterBox);
QWidget *widget = new QWidget;
widget->setLayout (layout);
@ -44,6 +50,10 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
connect (mBottom, SIGNAL (requestFocus (const std::string&)),
mTable, SLOT (requestFocus (const std::string&)));
connect (filterBox,
SIGNAL (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)),
mTable, SLOT (recordFilterChanged (boost::shared_ptr<CSMFilter::Node>)));
}
void CSVWorld::TableSubView::setEditLock (bool locked)

@ -7,14 +7,17 @@
void ESM::Filter::load (ESMReader& esm)
{
mFilter = esm.getHNString ("FILT");
mDescription = esm.getHNString ("DESC");
}
void ESM::Filter::save (ESMWriter& esm)
{
esm.writeHNCString ("FILT", mFilter);
esm.writeHNCString ("DESC", mDescription);
}
void ESM::Filter::blank()
{
mFilter.clear();
mDescription.clear();
}

@ -12,6 +12,8 @@ namespace ESM
{
std::string mId;
std::string mDescription;
std::string mFilter;
void load (ESMReader& esm);

Loading…
Cancel
Save