/ filters / expressionfilter.cpp
expressionfilter.cpp
  1  #include "expressionfilter.h"
  2  #include "qqmlsortfilterproxymodel.h"
  3  #include <QtQml>
  4  
  5  namespace qqsfpm {
  6  
  7  /*!
  8      \qmltype ExpressionFilter
  9      \inherits Filter
 10      \inqmlmodule SortFilterProxyModel
 11      \ingroup Filters
 12      \brief Filters row with a custom filtering.
 13  
 14      An ExpressionFilter is a \l Filter allowing to implement custom filtering based on a javascript expression.
 15  */
 16  
 17  /*!
 18      \qmlproperty expression ExpressionFilter::expression
 19  
 20      An expression to implement custom filtering, it must evaluate to a boolean.
 21      It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding} except it will be evaluated for each of the source model's rows.
 22      Rows that have their expression evaluating to \c true will be accepted by the model.
 23      Data for each row is exposed like for a delegate of a QML View.
 24  
 25      This expression is reevaluated for a row every time its model data changes.
 26      When an external property (not \c index or in \c model) the expression depends on changes, the expression is reevaluated for every row of the source model.
 27      To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine.
 28      This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes.
 29  
 30      A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression.
 31  */
 32  const QQmlScriptString& ExpressionFilter::expression() const
 33  {
 34      return m_scriptString;
 35  }
 36  
 37  void ExpressionFilter::setExpression(const QQmlScriptString& scriptString)
 38  {
 39      if (m_scriptString == scriptString)
 40          return;
 41  
 42      m_scriptString = scriptString;
 43      updateExpression();
 44  
 45      Q_EMIT expressionChanged();
 46      invalidate();
 47  }
 48  
 49  void ExpressionFilter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
 50  {
 51      updateContext(proxyModel);
 52  }
 53  
 54  bool ExpressionFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
 55  {
 56      if (!m_scriptString.isEmpty()) {
 57          QVariantMap modelMap;
 58          QHash<int, QByteArray> roles = proxyModel.roleNames();
 59  
 60          QQmlContext context(qmlContext(this));
 61          auto addToContext = [&] (const QString &name, const QVariant& value) {
 62              context.setContextProperty(name, value);
 63              modelMap.insert(name, value);
 64          };
 65  
 66          for (auto it = roles.cbegin(); it != roles.cend(); ++it)
 67              addToContext(it.value(), proxyModel.sourceData(sourceIndex, it.key()));
 68          addToContext(QStringLiteral("index"), sourceIndex.row());
 69  
 70          context.setContextProperty(QStringLiteral("model"), modelMap);
 71  
 72          QQmlExpression expression(m_scriptString, &context);
 73          QVariant variantResult = expression.evaluate();
 74  
 75          if (expression.hasError()) {
 76              qWarning() << expression.error();
 77              return true;
 78          }
 79          if (variantResult.canConvert<bool>()) {
 80              return variantResult.toBool();
 81          } else {
 82              qWarning("%s:%i:%i : Can't convert result to bool",
 83                       expression.sourceFile().toUtf8().data(),
 84                       expression.lineNumber(),
 85                       expression.columnNumber());
 86              return true;
 87          }
 88      }
 89      return true;
 90  }
 91  
 92  void ExpressionFilter::updateContext(const QQmlSortFilterProxyModel& proxyModel)
 93  {
 94      delete m_context;
 95      m_context = new QQmlContext(qmlContext(this), this);
 96      // what about roles changes ?
 97      QVariantMap modelMap;
 98  
 99      auto addToContext = [&] (const QString &name, const QVariant& value) {
100          m_context->setContextProperty(name, value);
101          modelMap.insert(name, value);
102      };
103  
104      const auto roleNames = proxyModel.roleNames();
105      for (const QByteArray& roleName : roleNames)
106          addToContext(roleName, QVariant());
107  
108      addToContext(QStringLiteral("index"), -1);
109  
110      m_context->setContextProperty(QStringLiteral("model"), modelMap);
111      updateExpression();
112  }
113  
114  void ExpressionFilter::updateExpression()
115  {
116      if (!m_context)
117          return;
118  
119      delete m_expression;
120      m_expression = new QQmlExpression(m_scriptString, m_context, nullptr, this);
121      connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionFilter::invalidate);
122      m_expression->setNotifyOnValueChanged(true);
123      m_expression->evaluate();
124  }
125  
126  }