Commit 7ccb80aa authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Custom wizard: Add validation rules.



Add XML-elements allowing for Javascript-based
validation rules along with error messages.
Rubber-stamped-by: default avatarErik Verbruggen <erik.verbruggen@nokia.com>
parent 272d22f1
......@@ -67,4 +67,11 @@ Custom class wizard example configuration file. -->
<fielddescription xml:lang="de">Datentyp:</fielddescription>
</field>
</fields>
<!-- Example of a validation rule -->
<validationrules>
<validationrule condition='"%ClassName%" != "QAbstractListModel"'>
<message>%ClassName% cannot be used as class name.</message>
<message xml:lang="de">%ClassName% kann nicht als Klassenname verwendet werden.</message>
</validationrule>
</validationrules>
</wizard>
......@@ -2,5 +2,5 @@ let $prefix := string("QT_TRANSLATE_NOOP(&quot;ProjectExplorer::CustomWizard&quo
let $suffix := concat("&quot;)", codepoints-to-string(10))
for $file in tokenize($files, string("\|"))
let $doc := doc($file)
for $text in ($doc/*:wizard/*:description, $doc/*:wizard/*:displayname, $doc/*:wizard/*:displaycategory, $doc/*:wizard/*:fieldpagetitle, $doc/*:wizard/*:fields/*:field/*:fielddescription, $doc/*:wizard/*:fields/*:field/*:fieldcontrol/*:comboentries/*:comboentry/*:comboentrytext)
for $text in ($doc/*:wizard/*:description, $doc/*:wizard/*:displayname, $doc/*:wizard/*:displaycategory, $doc/*:wizard/*:fieldpagetitle, $doc/*:wizard/*:fields/*:field/*:fielddescription, $doc/*:wizard/*:fields/*:field/*:fieldcontrol/*:comboentries/*:comboentry/*:comboentrytext, $doc/*:wizard/*:validationrules/*:validationrule/*:message)
return fn:concat($prefix, data($text), $suffix)
......@@ -114,7 +114,7 @@ void CustomWizard::initWizardDialog(Utils::Wizard *wizard, const QString &defaul
QTC_ASSERT(!parameters().isNull(), return);
d->m_context->reset();
Internal::CustomWizardPage *customPage = new Internal::CustomWizardPage(d->m_context, parameters()->fields);
Internal::CustomWizardPage *customPage = new Internal::CustomWizardPage(d->m_context, parameters());
customPage->setPath(defaultPath);
addWizardPage(wizard, customPage, parameters()->firstPageId);
if (!parameters()->fieldPageTitle.isEmpty())
......@@ -298,15 +298,7 @@ Core::GeneratedFiles CustomWizard::generateWizardFiles(QString *errorMessage) co
// Create a replacement map of static base fields + wizard dialog fields
CustomWizard::FieldReplacementMap CustomWizard::replacementMap(const QWizard *w) const
{
FieldReplacementMap fieldReplacementMap = d->m_context->baseReplacements;
foreach(const Internal::CustomWizardField &field, d->m_parameters->fields) {
const QString value = w->field(field.name).toString();
fieldReplacementMap.insert(field.name, value);
}
// Insert paths for generator scripts.
fieldReplacementMap.insert(QLatin1String("Path"), QDir::toNativeSeparators(context()->path));
fieldReplacementMap.insert(QLatin1String("TargetPath"), QDir::toNativeSeparators(context()->targetPath));
return fieldReplacementMap;
return Internal::CustomWizardFieldPage::replacementMap(w, context(), d->m_parameters->fields);
}
CustomWizard::CustomWizardParametersPtr CustomWizard::parameters() const
......@@ -501,7 +493,7 @@ void CustomProjectWizard::initProjectWizardDialog(BaseProjectWizardDialog *w,
w->setWindowTitle(displayName());
if (!pa->fields.isEmpty()) {
Internal::CustomWizardFieldPage *cp = new Internal::CustomWizardFieldPage(ctx, pa->fields);
Internal::CustomWizardFieldPage *cp = new Internal::CustomWizardFieldPage(ctx, pa);
addWizardPage(w, cp, parameters()->firstPageId);
if (!pa->fieldPageTitle.isEmpty())
cp->setTitle(pa->fieldPageTitle);
......
......@@ -35,14 +35,17 @@
#include <QtCore/QRegExp>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtGui/QWizardPage>
#include <QtGui/QFormLayout>
#include <QtGui/QVBoxLayout>
#include <QtGui/QLineEdit>
#include <QtGui/QLabel>
#include <QtGui/QRegExpValidator>
#include <QtGui/QComboBox>
#include <QtGui/QTextEdit>
#include <QtGui/QSpacerItem>
enum { debug = 0 };
......@@ -127,18 +130,26 @@ CustomWizardFieldPage::TextEditData::TextEditData(QTextEdit* le, const QString &
}
CustomWizardFieldPage::CustomWizardFieldPage(const QSharedPointer<CustomWizardContext> &ctx,
const FieldList &fields,
const QSharedPointer<CustomWizardParameters> &parameters,
QWidget *parent) :
QWizardPage(parent),
m_parameters(parameters),
m_context(ctx),
m_formLayout(new QFormLayout)
m_formLayout(new QFormLayout),
m_errorLabel(new QLabel)
{
QVBoxLayout *vLayout = new QVBoxLayout;
m_formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
if (debug)
qDebug() << Q_FUNC_INFO << fields.size();
foreach(const CustomWizardField &f, fields)
qDebug() << Q_FUNC_INFO << parameters->fields.size();
foreach(const CustomWizardField &f, parameters->fields)
addField(f);
setLayout(m_formLayout);
vLayout->addLayout(m_formLayout);
m_errorLabel->setVisible(false);
m_errorLabel->setStyleSheet(QLatin1String("background: red"));
vLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
vLayout->addWidget(m_errorLabel);
setLayout(vLayout);
}
CustomWizardFieldPage::~CustomWizardFieldPage()
......@@ -150,6 +161,18 @@ void CustomWizardFieldPage::addRow(const QString &name, QWidget *w)
m_formLayout->addRow(name, w);
}
void CustomWizardFieldPage::showError(const QString &m)
{
m_errorLabel->setText(m);
m_errorLabel->setVisible(true);
}
void CustomWizardFieldPage::clearError()
{
m_errorLabel->setText(QString());
m_errorLabel->setVisible(false);
}
// Create widget a control based on the control attributes map
// and register it with the QWizard.
void CustomWizardFieldPage::addField(const CustomWizardField &field)\
......@@ -294,6 +317,7 @@ QWidget *CustomWizardFieldPage::registerLineEdit(const QString &fieldName,
void CustomWizardFieldPage::initializePage()
{
QWizardPage::initializePage();
clearError();
// Note that the field mechanism will always restore the value
// set on it when entering the page, so, there is no point in
// trying to preserve user modifications of the text.
......@@ -315,6 +339,7 @@ void CustomWizardFieldPage::initializePage()
bool CustomWizardFieldPage::validatePage()
{
clearError();
// Check line edits with validators
foreach(const LineEditData &led, m_lineEdits) {
if (const QValidator *val = led.lineEdit->validator()) {
......@@ -326,15 +351,40 @@ bool CustomWizardFieldPage::validatePage()
}
}
}
// Any user validation rules -> Check all and display messages with
// place holders applied.
if (!m_parameters->rules.isEmpty()) {
const QMap<QString, QString> values = replacementMap(wizard(), m_context, m_parameters->fields);
QString message;
if (!CustomWizardValidationRule::validateRules(m_parameters->rules, values, &message)) {
showError(message);
return false;
}
}
return QWizardPage::validatePage();
}
QMap<QString, QString> CustomWizardFieldPage::replacementMap(const QWizard *w,
const QSharedPointer<CustomWizardContext> &ctx,
const FieldList &f)
{
QMap<QString, QString> fieldReplacementMap = ctx->baseReplacements;
foreach(const Internal::CustomWizardField &field, f) {
const QString value = w->field(field.name).toString();
fieldReplacementMap.insert(field.name, value);
}
// Insert paths for generator scripts.
fieldReplacementMap.insert(QLatin1String("Path"), QDir::toNativeSeparators(ctx->path));
fieldReplacementMap.insert(QLatin1String("TargetPath"), QDir::toNativeSeparators(ctx->targetPath));
return fieldReplacementMap;
}
// --------------- CustomWizardPage
CustomWizardPage::CustomWizardPage(const QSharedPointer<CustomWizardContext> &ctx,
const FieldList &f,
const QSharedPointer<CustomWizardParameters> &parameters,
QWidget *parent) :
CustomWizardFieldPage(ctx, f, parent),
CustomWizardFieldPage(ctx, parameters, parent),
m_pathChooser(new Utils::PathChooser)
{
addRow(tr("Path:"), m_pathChooser);
......
......@@ -39,6 +39,7 @@ QT_BEGIN_NAMESPACE
class QFormLayout;
class QLineEdit;
class QTextEdit;
class QLabel;
QT_END_NAMESPACE
namespace Utils {
......@@ -110,23 +111,30 @@ private:
// as page 2 of a BaseProjectWizardDialog if there are any fields.
// Uses the 'field' functionality of QWizard.
// Implements validatePage() as the field logic cannot be tied up
// with additional validation.
// with additional validation. Performs checking of the Javascript-based
// validation rules of the parameters and displays error messages in a red
// warning label.
class CustomWizardFieldPage : public QWizardPage {
Q_OBJECT
public:
typedef QList<CustomWizardField> FieldList;
explicit CustomWizardFieldPage(const QSharedPointer<CustomWizardContext> &ctx,
const FieldList &f,
const QSharedPointer<CustomWizardParameters> &parameters,
QWidget *parent = 0);
virtual ~CustomWizardFieldPage();
virtual bool validatePage();
virtual void initializePage();
static QMap<QString, QString> replacementMap(const QWizard *w,
const QSharedPointer<CustomWizardContext> &ctx,
const FieldList &f);
protected:
inline void addRow(const QString &name, QWidget *w);
void showError(const QString &);
void clearError();
private:
struct LineEditData {
explicit LineEditData(QLineEdit* le = 0, const QString &defText = QString());
......@@ -150,10 +158,12 @@ private:
const CustomWizardField &field);
void addField(const CustomWizardField &f);
const QSharedPointer<CustomWizardParameters> m_parameters;
const QSharedPointer<CustomWizardContext> m_context;
QFormLayout *m_formLayout;
LineEditDataList m_lineEdits;
TextEditDataList m_textEdits;
QLabel *m_errorLabel;
};
// A custom wizard page presenting the fields to be used and a path chooser
......@@ -164,7 +174,7 @@ class CustomWizardPage : public CustomWizardFieldPage {
Q_OBJECT
public:
explicit CustomWizardPage(const QSharedPointer<CustomWizardContext> &ctx,
const FieldList &f,
const QSharedPointer<CustomWizardParameters> &parameters,
QWidget *parent = 0);
QString path() const;
......
......@@ -46,6 +46,7 @@
#include <QtCore/QXmlStreamReader>
#include <QtCore/QXmlStreamAttribute>
#include <QtCore/QTemporaryFile>
#include <QtScript/QScriptEngine>
#include <QtGui/QIcon>
......@@ -90,6 +91,11 @@ static const char fileSourceAttributeC[] = "source";
static const char fileTargetAttributeC[] = "target";
static const char fileBinaryAttributeC[] = "binary";
static const char rulesElementC[] = "validationrules";
static const char ruleElementC[] = "validationrule";
static const char ruleConditionAttributeC[] = "condition";
static const char ruleMessageElementC[] = "message";
enum ParseState {
ParseBeginning,
ParseWithinWizard,
......@@ -104,6 +110,9 @@ enum ParseState {
ParseWithinFile,
ParseWithinScript,
ParseWithinScriptArguments,
ParseWithinValidationRules,
ParseWithinValidationRule,
ParseWithinValidationRuleMessage,
ParseError
};
......@@ -142,6 +151,38 @@ CustomWizardFile::CustomWizardFile() :
{
}
bool CustomWizardValidationRule::validateRules(const QList<CustomWizardValidationRule> &rules,
const QMap<QString, QString> &replacementMap,
QString *errorMessage)
{
errorMessage->clear();
if (rules.isEmpty())
return true;
QScriptEngine engine;
foreach(const CustomWizardValidationRule &rule, rules)
if (!rule.validate(engine, replacementMap)) {
*errorMessage = rule.message;
CustomWizardContext::replaceFields(replacementMap, errorMessage);
return false;
}
return true;
}
bool CustomWizardValidationRule::validate(QScriptEngine &engine, const QMap<QString, QString> &replacementMap) const
{
// Apply parameters and evaluate using JavaScript
QString cond = condition;
CustomWizardContext::replaceFields(replacementMap, &cond);
bool valid = false;
QString errorMessage;
if (!evaluateBooleanJavaScriptExpression(engine, cond, &valid, &errorMessage)) {
qWarning("Error in custom wizard validation expression '%s': %s",
qPrintable(cond), qPrintable(errorMessage));
return false;
}
return valid;
}
CustomWizardParameters::CustomWizardParameters() :
firstPageId(-1)
{
......@@ -155,6 +196,7 @@ void CustomWizardParameters::clear()
filesGeneratorScript.clear();
filesGeneratorScriptArguments.clear();
firstPageId = -1;
rules.clear();
}
// Resolve icon file path relative to config file directory.
......@@ -296,6 +338,8 @@ static ParseState nextOpeningState(ParseState in, const QStringRef &name)
return ParseWithinFiles;
if (name == QLatin1String(generatorScriptElementC))
return ParseWithinScript;
if (name == QLatin1String(rulesElementC))
return ParseWithinValidationRules;
break;
case ParseWithinFields:
if (name == QLatin1String(fieldElementC))
......@@ -327,11 +371,20 @@ static ParseState nextOpeningState(ParseState in, const QStringRef &name)
if (name == QLatin1String(generatorScriptArgumentElementC))
return ParseWithinScriptArguments;
break;
case ParseWithinValidationRules:
if (name == QLatin1String(ruleElementC))
return ParseWithinValidationRule;
break;
case ParseWithinValidationRule:
if (name == QLatin1String(ruleMessageElementC))
return ParseWithinValidationRuleMessage;
break;
case ParseWithinFieldDescription: // No subelements
case ParseWithinComboEntryText:
case ParseWithinFile:
case ParseError:
case ParseWithinScriptArguments:
case ParseWithinValidationRuleMessage:
break;
}
return ParseError;
......@@ -391,6 +444,12 @@ static ParseState nextClosingState(ParseState in, const QStringRef &name)
if (name == QLatin1String(generatorScriptArgumentElementC))
return ParseWithinScript;
break;
case ParseWithinValidationRuleMessage:
return ParseWithinValidationRule;
case ParseWithinValidationRule:
return ParseWithinValidationRules;
case ParseWithinValidationRules:
return ParseWithinWizard;
case ParseError:
break;
}
......@@ -575,6 +634,18 @@ CustomWizardParameters::ParseResult
filesGeneratorScriptArguments.push_back(argument);
}
break;
case ParseWithinValidationRule: {
CustomWizardValidationRule rule;
rule.condition = reader.attributes().value(QLatin1String(ruleConditionAttributeC)).toString();
rules.push_back(rule);
}
break;
case ParseWithinValidationRuleMessage:
QTC_ASSERT(!rules.isEmpty(), return ParseFailed; )
// This reads away the end tag, set state here.
assignLanguageElementText(reader, language, &(rules.back().message));
state = ParseWithinValidationRule;
break;
default:
break;
}
......@@ -664,6 +735,8 @@ QString CustomWizardParameters::toString() const
}
str << '\n';
}
foreach(const CustomWizardValidationRule &r, rules)
str << " Rule: '" << r.condition << "'->'" << r.message << '\n';
return rc;
}
......
......@@ -40,6 +40,7 @@ QT_BEGIN_NAMESPACE
class QIODevice;
class QDebug;
class QTemporaryFile;
class QScriptEngine;
QT_END_NAMESPACE
namespace ProjectExplorer {
......@@ -71,6 +72,22 @@ struct CustomWizardFile {
bool binary;
};
// A validation rule based on Javascript-expressions over the field placeholders.
// Placeholder replacement is performed on the condition and it is evaluated
// using Javascript. So, for example '"%ProjectName%" != "untitled" would block
// default names. On failure, the message is displayed in a red warning label
// in the wizard page. Placeholder replacement is also performed on the message
// prior to displaying.
struct CustomWizardValidationRule {
// Validate a set of rules and return false + message on the first failing one.
static bool validateRules(const QList<CustomWizardValidationRule> &rules,
const QMap<QString, QString> &replacementMap,
QString *errorMessage);
bool validate(QScriptEngine &, const QMap<QString, QString> &replacementMap) const;
QString condition;
QString message;
};
// Argument to the generator script containing placeholders to
// be replaced by field values or file names
// as in '--class-name=%ClassName%' or '--description=%Description%'.
......@@ -110,6 +127,7 @@ public:
QString fieldPageTitle;
QList<CustomWizardField> fields;
QList<CustomWizardValidationRule> rules;
int firstPageId;
};
......
......@@ -81,7 +81,6 @@ public:
private:
void reset();
bool evaluateExpression(const QString &expression, bool *result, QString *errorMessage);
PreprocessorSection preprocessorLine(const QString & in, QString *ifExpression) const;
mutable QRegExp m_ifPattern;
......@@ -134,17 +133,15 @@ PreprocessorSection PreprocessContext::preprocessorLine(const QString &in,
}
// Evaluate an expression within an 'if'/'elsif' to a bool via QScript
bool PreprocessContext::evaluateExpression(const QString &expression,
bool *result,
QString *errorMessage)
bool evaluateBooleanJavaScriptExpression(QScriptEngine &engine, const QString &expression, bool *result, QString *errorMessage)
{
errorMessage->clear();
*result = false;
m_scriptEngine.clearExceptions();
const QScriptValue value = m_scriptEngine.evaluate(expression);
if (m_scriptEngine.hasUncaughtException()) {
engine.clearExceptions();
const QScriptValue value = engine.evaluate(expression);
if (engine.hasUncaughtException()) {
*errorMessage = QString::fromLatin1("Error in '%1': %2").
arg(expression, m_scriptEngine.uncaughtException().toString());
arg(expression, engine.uncaughtException().toString());
return false;
}
// Try to convert to bool, be that an int or whatever.
......@@ -196,7 +193,7 @@ bool PreprocessContext::process(const QString &in, QString *out, QString *errorM
case IfSection:
// '@If': Push new section
if (top.parentEnabled) {
if (!evaluateExpression(expression, &expressionValue, errorMessage)) {
if (!evaluateBooleanJavaScriptExpression(m_scriptEngine, expression, &expressionValue, errorMessage)) {
*errorMessage = QString::fromLatin1("Error in @if at %1: %2").
arg(l + 1).arg(*errorMessage);
return false;
......@@ -214,7 +211,7 @@ bool PreprocessContext::process(const QString &in, QString *out, QString *errorM
return false;
}
if (top.parentEnabled) {
if (!evaluateExpression(expression, &expressionValue, errorMessage)) {
if (!evaluateBooleanJavaScriptExpression(m_scriptEngine, expression, &expressionValue, errorMessage)) {
*errorMessage = QString::fromLatin1("Error in @elsif at %1: %2").
arg(l + 1).arg(*errorMessage);
return false;
......
......@@ -32,6 +32,8 @@
#include <QtCore/QString>
QT_FORWARD_DECLARE_CLASS(QScriptEngine)
namespace ProjectExplorer {
namespace Internal {
......@@ -51,7 +53,8 @@ Blup
*/
bool customWizardPreprocess(const QString &in, QString *out, QString *errorMessage);
/* Helper to evaluate an expression. */
bool evaluateBooleanJavaScriptExpression(QScriptEngine &engine, const QString &expression, bool *result, QString *errorMessage);
} // namespace Internal
} // namespace ProjectExplorer
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment