parent
26508f5c91
commit
44808424a4
|
@ -0,0 +1,123 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ 0.1
|
||||
|
||||
Pane {
|
||||
id: root
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
||||
RowLayout {
|
||||
Label {
|
||||
text: "Capitalized words:"
|
||||
}
|
||||
|
||||
TextField {
|
||||
validator: GenericValidator {
|
||||
validate: {
|
||||
const split = input.split(' ')
|
||||
|
||||
const upperCase = split.map(w =>
|
||||
w.charAt(0).toUpperCase()
|
||||
+ w.slice(1).toLowerCase())
|
||||
|
||||
return {
|
||||
state: GenericValidator.Acceptable,
|
||||
output: upperCase.join(' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label {
|
||||
text: "Decimal numbers, replacing ',' with '.':"
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: decimalsTextField
|
||||
|
||||
validator: GenericValidator {
|
||||
validate: {
|
||||
if (input.length === 0)
|
||||
return GenericValidator.Intermediate
|
||||
|
||||
const validCharSet = /^[0-9\.\,]*$/.test(input)
|
||||
|
||||
if (!validCharSet)
|
||||
return GenericValidator.Invalid
|
||||
|
||||
const pointFixed = input.replace(",", ".")
|
||||
const pointsCount = (pointFixed.match(/\./g) || []).length
|
||||
|
||||
const wellFormed = pointFixed.charAt(0) !== '.'
|
||||
&& pointFixed.charAt(
|
||||
pointFixed.length - 1) !== '.'
|
||||
|
||||
if (pointsCount > 1)
|
||||
return GenericValidator.Invalid
|
||||
|
||||
return {
|
||||
state: wellFormed ? GenericValidator.Acceptable
|
||||
: GenericValidator.Intermediate,
|
||||
output: pointFixed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: `acceptable: ${decimalsTextField.acceptableInput}`
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label {
|
||||
text: "Position always at 0:"
|
||||
}
|
||||
|
||||
TextField {
|
||||
validator: GenericValidator {
|
||||
validate: ({
|
||||
state: GenericValidator.Acceptable,
|
||||
pos: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label {
|
||||
text: "Maximum number of characters:"
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: limitedTextField
|
||||
|
||||
validator: GenericValidator {
|
||||
validate: input.length <= slider.value
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: `acceptable: ${limitedTextField.acceptableInput}`
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: slider
|
||||
|
||||
from: 3
|
||||
to: 10
|
||||
stepSize: 1
|
||||
}
|
||||
|
||||
Label {
|
||||
text: `max: ${slider.value}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -100,6 +100,7 @@ add_library(StatusQ SHARED
|
|||
include/StatusQ/fastexpressionsorter.h
|
||||
include/StatusQ/formatteddoubleproperty.h
|
||||
include/StatusQ/functionaggregator.h
|
||||
include/StatusQ/genericvalidator.h
|
||||
include/StatusQ/groupingmodel.h
|
||||
include/StatusQ/leftjoinmodel.h
|
||||
include/StatusQ/modelcount.h
|
||||
|
@ -129,6 +130,7 @@ add_library(StatusQ SHARED
|
|||
src/fastexpressionsorter.cpp
|
||||
src/formatteddoubleproperty.cpp
|
||||
src/functionaggregator.cpp
|
||||
src/genericvalidator.cpp
|
||||
src/groupingmodel.cpp
|
||||
src/leftjoinmodel.cpp
|
||||
src/modelcount.cpp
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include <QValidator>
|
||||
#include <QQmlScriptString>
|
||||
#include <QQmlPropertyMap>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QQmlExpression;
|
||||
|
||||
class GenericValidator : public QValidator
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QQmlScriptString fixup
|
||||
READ fixupScriptString WRITE setFixupScriptString
|
||||
NOTIFY fixupScriptStringChanged)
|
||||
|
||||
Q_PROPERTY(QQmlScriptString validate
|
||||
READ validateScriptString WRITE setValidateScriptString
|
||||
NOTIFY validateScriptStringChanged)
|
||||
|
||||
public:
|
||||
enum State {
|
||||
Invalid = QValidator::Invalid,
|
||||
Intermediate = QValidator::Intermediate,
|
||||
Acceptable = QValidator::Acceptable
|
||||
};
|
||||
Q_ENUM(State)
|
||||
|
||||
explicit GenericValidator(QObject* parent = nullptr);
|
||||
|
||||
const QQmlScriptString& fixupScriptString() const;
|
||||
void setFixupScriptString(const QQmlScriptString& scriptString);
|
||||
|
||||
const QQmlScriptString& validateScriptString() const;
|
||||
void setValidateScriptString(const QQmlScriptString& scriptString);
|
||||
|
||||
void fixup(QString& input) const override;
|
||||
QValidator::State validate(QString& input, int& pos) const override;
|
||||
|
||||
signals:
|
||||
void fixupScriptStringChanged();
|
||||
void validateScriptStringChanged();
|
||||
|
||||
private:
|
||||
bool isValidState(int state) const;
|
||||
|
||||
QQmlScriptString m_fixupScriptString;
|
||||
QQmlScriptString m_validateScriptString;
|
||||
|
||||
mutable QQmlPropertyMap m_fixupScope;
|
||||
mutable QQmlPropertyMap m_validateScope;
|
||||
|
||||
std::unique_ptr<QQmlExpression> m_fixupExpression;
|
||||
std::unique_ptr<QQmlExpression> m_validateExpression;
|
||||
};
|
|
@ -0,0 +1,189 @@
|
|||
#include "StatusQ/genericvalidator.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJSValue>
|
||||
#include <QQmlEngine>
|
||||
#include <QQmlExpression>
|
||||
#include <QScopeGuard>
|
||||
|
||||
/*!
|
||||
\qmltype GenericValidator
|
||||
\instantiates GenericValidator
|
||||
\inqmlmodule StatusQ
|
||||
\inherits QValidator
|
||||
\brief Exposes \l {QValidator} interface to QML.
|
||||
|
||||
It allows defining fully featured validators directly from QML. Validate
|
||||
expression can return bool (Invalid/Acceptable), `State`
|
||||
(Invalid/Intermediate/Acceptable) or object with following properties:
|
||||
|
||||
- state - result of validation of type GenericValidator.State
|
||||
- output - optional - value overriding input
|
||||
- pos - optional - new position of the cursor
|
||||
|
||||
Within the validate expression there are two parameters available:
|
||||
- input (string intended to be validated)
|
||||
- pos (current position of the cursor)
|
||||
*/
|
||||
GenericValidator::GenericValidator(QObject* parent)
|
||||
: QValidator(parent)
|
||||
{
|
||||
}
|
||||
|
||||
const QQmlScriptString& GenericValidator::fixupScriptString() const
|
||||
{
|
||||
return m_fixupScriptString;
|
||||
}
|
||||
|
||||
void GenericValidator::setFixupScriptString(
|
||||
const QQmlScriptString& scriptString)
|
||||
{
|
||||
if (m_fixupScriptString == scriptString)
|
||||
return;
|
||||
|
||||
m_fixupScriptString = scriptString;
|
||||
m_fixupExpression = std::make_unique<QQmlExpression>(
|
||||
m_fixupScriptString, qmlContext(this), &m_fixupScope);
|
||||
|
||||
emit fixupScriptStringChanged();
|
||||
}
|
||||
|
||||
const QQmlScriptString& GenericValidator::validateScriptString() const
|
||||
{
|
||||
return m_validateScriptString;
|
||||
}
|
||||
|
||||
void GenericValidator::setValidateScriptString(
|
||||
const QQmlScriptString& scriptString)
|
||||
{
|
||||
if (m_validateScriptString == scriptString)
|
||||
return;
|
||||
|
||||
m_validateScriptString = scriptString;
|
||||
|
||||
m_validateExpression = std::make_unique<QQmlExpression>(
|
||||
m_validateScriptString, qmlContext(this), &m_validateScope);
|
||||
m_validateExpression->setNotifyOnValueChanged(true);
|
||||
|
||||
connect(m_validateExpression.get(), &QQmlExpression::valueChanged, this,
|
||||
&QValidator::changed);
|
||||
|
||||
emit validateScriptStringChanged();
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void GenericValidator::fixup(QString& input) const
|
||||
{
|
||||
if (!m_fixupExpression)
|
||||
return;
|
||||
|
||||
m_fixupScope.insert("input", input);
|
||||
|
||||
m_fixupExpression->clearError();
|
||||
QVariant value = m_fixupExpression->evaluate();
|
||||
|
||||
if (m_fixupExpression->hasError()) {
|
||||
qWarning() << m_fixupExpression->error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.type() == QVariant::String)
|
||||
input = m_fixupExpression->evaluate().toString();
|
||||
else
|
||||
qWarning() << "Validator: fixup expression must return string.";
|
||||
}
|
||||
|
||||
QValidator::State GenericValidator::validate(QString& input, int& pos) const
|
||||
{
|
||||
// To avoid unnecessary `QValidator::changed` calls the signal is temporarily
|
||||
// disconnected. For some reason QQmlExpression::setNotifyOnValueChanged(true)
|
||||
// doesn't work as expected when used for that purpose. Once set to false,
|
||||
// changes are no longer notified even after switching back to true.
|
||||
m_validateExpression->disconnect(this);
|
||||
|
||||
QScopeGuard guard([this]() {
|
||||
connect(m_validateExpression.get(), &QQmlExpression::valueChanged, this,
|
||||
&QValidator::changed);
|
||||
});
|
||||
|
||||
m_validateScope.insert("input", input);
|
||||
m_validateScope.insert("pos", pos);
|
||||
|
||||
m_validateExpression->clearError();
|
||||
QVariant value = m_validateExpression->evaluate();
|
||||
|
||||
if (m_validateExpression->hasError()) {
|
||||
qWarning() << m_validateExpression->error();
|
||||
return QValidator::Invalid;
|
||||
}
|
||||
|
||||
if (value.type() == QVariant::Bool)
|
||||
return value.toBool() ? QValidator::Acceptable : QValidator::Invalid;
|
||||
|
||||
if (value.type() == QVariant::Int) {
|
||||
auto stateInt = value.toInt();
|
||||
|
||||
if (isValidState(stateInt)) {
|
||||
return static_cast<QValidator::State>(stateInt);
|
||||
} else {
|
||||
qWarning() << "Validator: numeric value returned from validate "
|
||||
"expression must be one of GenericValidator.Invalid, "
|
||||
"GenericValidator.Intermediate or "
|
||||
"GenericValidator.Acceptable";
|
||||
return QValidator::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
QJSValue jsValue = value.value<QJSValue>();
|
||||
|
||||
if (!jsValue.isObject()) {
|
||||
qWarning() << "Validator: validate expression must return bool, "
|
||||
"Validator.State or object.";
|
||||
return QValidator::Invalid;
|
||||
}
|
||||
|
||||
if (!jsValue.hasProperty("state")) {
|
||||
qWarning() << "Validator: object returned from validate expression "
|
||||
"must contain state property of type Validator.State.";
|
||||
return QValidator::Invalid;
|
||||
}
|
||||
|
||||
QJSValue stateValue = value.value<QJSValue>().property("state");
|
||||
|
||||
if (!stateValue.isNumber() || !isValidState(stateValue.toInt())) {
|
||||
qWarning() << "Validator: state property of object returned from "
|
||||
"validate expression must be of type Validator.State.";
|
||||
return QValidator::Invalid;
|
||||
}
|
||||
|
||||
int state = stateValue.toInt();
|
||||
|
||||
if (jsValue.hasProperty("output")) {
|
||||
QJSValue outputProperty = jsValue.property("output");
|
||||
|
||||
if (outputProperty.isString())
|
||||
input = outputProperty.toString();
|
||||
else
|
||||
qWarning() << "Validator: 'output' property must be a string.";
|
||||
}
|
||||
|
||||
if (jsValue.hasProperty("pos")) {
|
||||
QJSValue posProperty = jsValue.property("pos");
|
||||
|
||||
if (posProperty.isNumber())
|
||||
pos = posProperty.toInt();
|
||||
else
|
||||
qWarning() << "Validator: 'pos' property must be an integer.";
|
||||
}
|
||||
|
||||
return static_cast<QValidator::State>(state);
|
||||
}
|
||||
|
||||
bool GenericValidator::isValidState(int state) const
|
||||
{
|
||||
auto stateCasted = static_cast<QValidator::State>(state);
|
||||
|
||||
return stateCasted == QValidator::Invalid
|
||||
|| stateCasted == QValidator::Intermediate
|
||||
|| stateCasted == QValidator::Acceptable;
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
#include "StatusQ/fastexpressionsorter.h"
|
||||
#include "StatusQ/formatteddoubleproperty.h"
|
||||
#include "StatusQ/functionaggregator.h"
|
||||
#include "StatusQ/genericvalidator.h"
|
||||
#include "StatusQ/groupingmodel.h"
|
||||
#include "StatusQ/leftjoinmodel.h"
|
||||
#include "StatusQ/modelcount.h"
|
||||
|
@ -45,6 +46,11 @@ public:
|
|||
qmlRegisterType<StatusSyntaxHighlighter>("StatusQ", 0, 1, "StatusSyntaxHighlighter");
|
||||
qmlRegisterType<RXValidator>("StatusQ", 0, 1, "RXValidator");
|
||||
|
||||
qmlRegisterUncreatableType<QValidator>(
|
||||
"StatusQ", 0, 1,
|
||||
"Validator", "This is abstract type, cannot be created directly.");
|
||||
qmlRegisterType<GenericValidator>("StatusQ", 0, 1, "GenericValidator");
|
||||
|
||||
qmlRegisterType<ManageTokensController>("StatusQ.Models", 0, 1, "ManageTokensController");
|
||||
qmlRegisterType<ManageTokensModel>("StatusQ.Models", 0, 1, "ManageTokensModel");
|
||||
|
||||
|
|
Loading…
Reference in New Issue