From 5bc886c5b643d69c841005777538e24cb223d75d Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@nokia.com>
Date: Mon, 22 Mar 2010 15:33:33 +0100
Subject: [PATCH] Custom wizards: Make it possible to use fields in default
 texts.

Pass around shared context containing basic replacement map.
CustomProjectWizard adds %ProjectName% to it obtained from signal
BaseProjectWizardDialog::introPageLeft(). Move replacement code into
context. Add new modifier 'c' for capitalizing words.
---
 .../wizards/helloworld/wizard_sample.xml      |   3 +-
 .../baseprojectwizarddialog.cpp               |  27 +++-
 .../projectexplorer/baseprojectwizarddialog.h |   4 +
 .../customwizard/customwizard.cpp             | 122 +++++++-----------
 .../customwizard/customwizard.h               |  14 +-
 .../customwizard/customwizardpage.cpp         | 112 ++++++++++------
 .../customwizard/customwizardpage.h           |  24 +++-
 .../customwizard/customwizardparameters.cpp   |  74 +++++++++++
 .../customwizard/customwizardparameters.h     |  22 ++++
 9 files changed, 279 insertions(+), 123 deletions(-)

diff --git a/share/qtcreator/templates/wizards/helloworld/wizard_sample.xml b/share/qtcreator/templates/wizards/helloworld/wizard_sample.xml
index 58e7748bccc..23af3d6802b 100644
--- a/share/qtcreator/templates/wizards/helloworld/wizard_sample.xml
+++ b/share/qtcreator/templates/wizards/helloworld/wizard_sample.xml
@@ -53,7 +53,8 @@ leave room for the Qt 4 target page.
     <fieldpagetitle xml:lang="de">Hallo Welt Parameter</fieldpagetitle>
     <fields>
         <field mandatory="true" name="MESSAGE">
-            <fieldcontrol class="QLineEdit" validator='^[^"]+$'  defaulttext="Hello world!" />
+            <fieldcontrol class="QLineEdit" validator='^[^"]+$'
+                          defaulttext="Hello world from project  '%ProjectName:c%'!" />
             <fielddescription>Hello world message:</fielddescription>
             <fielddescription xml:lang="de">Hallo-Welt-Nachricht:</fielddescription>
         </field>
diff --git a/src/plugins/projectexplorer/baseprojectwizarddialog.cpp b/src/plugins/projectexplorer/baseprojectwizarddialog.cpp
index 166d2eb6222..a2324054709 100644
--- a/src/plugins/projectexplorer/baseprojectwizarddialog.cpp
+++ b/src/plugins/projectexplorer/baseprojectwizarddialog.cpp
@@ -41,13 +41,17 @@ namespace ProjectExplorer {
 struct BaseProjectWizardDialogPrivate {
     explicit BaseProjectWizardDialogPrivate(Utils::ProjectIntroPage *page, int id = -1);
 
-    const int introId;
+    const int desiredIntroPageId;
     Utils::ProjectIntroPage *introPage;
+    int introPageId;
+    int lastId;
 };
 
 BaseProjectWizardDialogPrivate::BaseProjectWizardDialogPrivate(Utils::ProjectIntroPage *page, int id) :
-    introId(id),
-    introPage(page)
+    desiredIntroPageId(id),
+    introPage(page),
+    introPageId(-1),
+    lastId(-1)
 {
 }
 
@@ -70,8 +74,14 @@ BaseProjectWizardDialog::BaseProjectWizardDialog(Utils::ProjectIntroPage *introP
 void BaseProjectWizardDialog::init()
 {
     Core::BaseFileWizard::setupWizard(this);
-    addPage(d->introPage);
+    if (d->introPageId == -1) {
+        d->introPageId = addPage(d->introPage);
+    } else {
+        d->introPageId = d->desiredIntroPageId;
+        setPage(d->desiredIntroPageId, d->introPage);
+    }
     connect(this, SIGNAL(accepted()), this, SLOT(slotAccepted()));
+    connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(slotBaseCurrentIdChanged(int)));
 }
 
 BaseProjectWizardDialog::~BaseProjectWizardDialog()
@@ -107,12 +117,21 @@ void BaseProjectWizardDialog::setProjectName(const QString &name)
 void BaseProjectWizardDialog::slotAccepted()
 {
     if (d->introPage->useAsDefaultPath()) {
+        // Store the path as default path for new projects if desired.
         Core::FileManager *fm = Core::ICore::instance()->fileManager();
         fm->setProjectsDirectory(path());
         fm->setUseProjectsDirectory(true);
     }
 }
 
+void BaseProjectWizardDialog::slotBaseCurrentIdChanged(int id)
+{
+    if (d->lastId == d->introPageId) {
+        emit introPageLeft(d->introPage->projectName(), d->introPage->path());
+    }
+    d->lastId = id;
+}
+
 Utils::ProjectIntroPage *BaseProjectWizardDialog::introPage() const
 {
     return d->introPage;
diff --git a/src/plugins/projectexplorer/baseprojectwizarddialog.h b/src/plugins/projectexplorer/baseprojectwizarddialog.h
index f4f40c27813..a0b669644ae 100644
--- a/src/plugins/projectexplorer/baseprojectwizarddialog.h
+++ b/src/plugins/projectexplorer/baseprojectwizarddialog.h
@@ -71,11 +71,15 @@ public slots:
     void setPath(const QString &path);
     void setProjectName(const QString &name);
 
+signals:
+    void introPageLeft(const QString &projectName, const QString &path);
+
 protected:
     Utils::ProjectIntroPage *introPage() const;
 
 private slots:
     void slotAccepted();
+    void slotBaseCurrentIdChanged(int);
 
 private:
     void init();
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.cpp b/src/plugins/projectexplorer/customwizard/customwizard.cpp
index ff468835af0..87dfbb2f2b1 100644
--- a/src/plugins/projectexplorer/customwizard/customwizard.cpp
+++ b/src/plugins/projectexplorer/customwizard/customwizard.cpp
@@ -35,8 +35,6 @@
 
 #include <coreplugin/icore.h>
 #include <coreplugin/messagemanager.h>
-#include <coreplugin/mimedatabase.h>
-#include <cpptools/cpptoolsconstants.h>
 #include <extensionsystem/pluginmanager.h>
 #include <utils/qtcassert.h>
 
@@ -52,7 +50,10 @@ static const char configFileC[] = "wizard.xml";
 namespace ProjectExplorer {
 
 struct CustomWizardPrivate {
+    CustomWizardPrivate() : m_context(new Internal::CustomWizardContext) {}
+
     QSharedPointer<Internal::CustomWizardParameters> m_parameters;
+    QSharedPointer<Internal::CustomWizardContext> m_context;
     static int verbose;
 };
 
@@ -105,7 +106,9 @@ void CustomWizard::initWizardDialog(QWizard *wizard, const QString &defaultPath,
                                     const WizardPageList &extensionPages) const
 {
     QTC_ASSERT(!parameters().isNull(), return);
-    Internal::CustomWizardPage *customPage = new Internal::CustomWizardPage(parameters()->fields);
+
+    d->m_context->reset();
+    Internal::CustomWizardPage *customPage = new Internal::CustomWizardPage(d->m_context, parameters()->fields);
     customPage->setPath(defaultPath);
     addWizardPage(wizard, customPage, parameters()->firstPageId);
     if (!parameters()->fieldPageTitle.isEmpty())
@@ -127,57 +130,6 @@ QWizard *CustomWizard::createWizardDialog(QWidget *parent,
     return wizard;
 }
 
-// Replace field values delimited by '%' with special modifiers:
-// %Field% -> simple replacement
-// %Field:l% -> lower case replacement, 'u' for upper case and so on.
-static void replaceFields(const CustomProjectWizard::FieldReplacementMap &fm, QString *s)
-{
-    const QChar delimiter = QLatin1Char('%');
-    const QChar modifierDelimiter = QLatin1Char(':');
-    int pos = 0;
-    while (pos < s->size()) {
-        pos = s->indexOf(delimiter, pos);
-        if (pos < 0)
-            break;
-        int nextPos = s->indexOf(delimiter, pos + 1);
-        if (nextPos == -1)
-            break;
-        nextPos++; // Point past 2nd delimiter
-        if (nextPos == pos + 2) {
-            pos = nextPos; // Skip '%%'
-            continue;
-        }
-        // Evaluate field specification for modifiers
-        // "%field:l%"
-        QString fieldSpec = s->mid(pos + 1, nextPos - pos - 2);
-        const int fieldSpecSize = fieldSpec.size();
-        char modifier = '\0';
-        if (fieldSpecSize >= 3 && fieldSpec.at(fieldSpecSize - 2) == modifierDelimiter) {
-            modifier = fieldSpec.at(fieldSpecSize - 1).toLatin1();
-            fieldSpec.truncate(fieldSpecSize - 2);
-        }
-        const CustomProjectWizard::FieldReplacementMap::const_iterator it = fm.constFind(fieldSpec);
-        if (it == fm.constEnd()) {
-            pos = nextPos; // Not found, skip
-            continue;
-        }
-        // Assign
-        QString replacement = it.value();
-        switch (modifier) {
-        case 'l':
-            replacement = it.value().toLower();
-            break;
-        case 'u':
-            replacement = it.value().toUpper();
-            break;
-        default:
-            break;
-        }
-        s->replace(pos, nextPos - pos, replacement);
-        pos += replacement.size();
-    }
-}
-
 // Read out files and store contents with field contents replaced.
 static inline bool createFile(Internal::CustomWizardFile cwFile,
                               const QString &sourceDirectory,
@@ -188,7 +140,8 @@ static inline bool createFile(Internal::CustomWizardFile cwFile,
 {
     const QChar slash =  QLatin1Char('/');
     const QString sourcePath = sourceDirectory + slash + cwFile.source;
-    replaceFields(fm, &cwFile.target);
+    // Field replacement on target path
+    Internal::CustomWizardContext::replaceFields(fm, &cwFile.target);
     const QString targetPath = QDir::toNativeSeparators(targetDirectory + slash + cwFile.target);
     if (CustomWizardPrivate::verbose)
         qDebug() << "generating " << targetPath << sourcePath << fm;
@@ -197,9 +150,10 @@ static inline bool createFile(Internal::CustomWizardFile cwFile,
         *errorMessage = QString::fromLatin1("Cannot open %1: %2").arg(sourcePath, file.errorString());
         return false;
     }
+    // Field replacement on contents
     QString contents = QString::fromLocal8Bit(file.readAll());
     if (!contents.isEmpty() && !fm.isEmpty())
-        replaceFields(fm, &contents);
+        Internal::CustomWizardContext::replaceFields(fm, &contents);
     Core::GeneratedFile generatedFile;
     generatedFile.setContents(contents);
     generatedFile.setPath(targetPath);
@@ -223,9 +177,16 @@ Core::GeneratedFiles CustomWizard::generateFiles(const QWizard *dialog, QString
     const Internal::CustomWizardPage *cwp = findWizardPage<Internal::CustomWizardPage>(dialog);
     QTC_ASSERT(cwp, return Core::GeneratedFiles())
     QString path = cwp->path();
-    const FieldReplacementMap fieldMap = defaultReplacementMap(dialog);
-    if (CustomWizardPrivate::verbose)
-        qDebug() << "CustomWizard::generateFiles" << dialog << path << fieldMap;
+    const FieldReplacementMap fieldMap = replacementMap(dialog);
+    if (CustomWizardPrivate::verbose) {
+        QString logText;
+        QTextStream str(&logText);
+        str << "CustomWizard::generateFiles: " << path << '\n';
+        const FieldReplacementMap::const_iterator cend = fieldMap.constEnd();
+        for (FieldReplacementMap::const_iterator it = fieldMap.constBegin(); it != cend; ++it)
+            str << "  '" << it.key() << "' -> '" << it.value() << "'\n";
+        qWarning("%s", qPrintable(logText));
+    }
     return generateWizardFiles(path, fieldMap, errorMessage);
 }
 
@@ -243,20 +204,14 @@ Core::GeneratedFiles CustomWizard::generateWizardFiles(const QString &targetPath
     return rc;
 }
 
-// Create a default replacement map from the wizard dialog via fields
-// and add some useful fields.
-CustomWizard::FieldReplacementMap CustomWizard::defaultReplacementMap(const QWizard *w) const
+// Create a replacement map of static base fields + wizard dialog fields
+CustomWizard::FieldReplacementMap CustomWizard::replacementMap(const QWizard *w) const
 {
-    FieldReplacementMap fieldReplacementMap;
+    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);
     }
-    const Core::MimeDatabase *mdb = Core::ICore::instance()->mimeDatabase();
-    fieldReplacementMap.insert(QLatin1String("CppSourceSuffix"),
-                               mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE)));
-    fieldReplacementMap.insert(QLatin1String("CppHeaderSuffix"),
-                               mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)));
     return fieldReplacementMap;
 }
 
@@ -265,6 +220,11 @@ CustomWizard::CustomWizardParametersPtr CustomWizard::parameters() const
     return d->m_parameters;
 }
 
+CustomWizard::CustomWizardContextPtr CustomWizard::context() const
+{
+    return d->m_context;
+}
+
 // Static factory map
 typedef QMap<QString, QSharedPointer<ICustomWizardFactory> > CustomWizardFactoryMap;
 Q_GLOBAL_STATIC(CustomWizardFactoryMap, customWizardFactoryMap)
@@ -407,17 +367,25 @@ void CustomProjectWizard::initProjectWizardDialog(BaseProjectWizardDialog *w,
                                                   const QString &defaultPath,
                                                   const WizardPageList &extensionPages) const
 {
-    QTC_ASSERT(!parameters().isNull(), return);
-    if (!parameters()->fields.isEmpty()) {
-        Internal::CustomWizardFieldPage *cp = new Internal::CustomWizardFieldPage(parameters()->fields);
+    const CustomWizardParametersPtr pa = parameters();
+    QTC_ASSERT(!pa.isNull(), return);
+
+    const CustomWizardContextPtr ctx = context();
+    ctx->reset();
+
+    if (!pa->fields.isEmpty()) {
+        Internal::CustomWizardFieldPage *cp = new Internal::CustomWizardFieldPage(ctx, pa->fields);
         addWizardPage(w, cp, parameters()->firstPageId);
-        if (!parameters()->fieldPageTitle.isEmpty())
-            cp->setTitle(parameters()->fieldPageTitle);
+        if (!pa->fieldPageTitle.isEmpty())
+            cp->setTitle(pa->fieldPageTitle);
     }
     foreach(QWizardPage *ep, extensionPages)
         w->addPage(ep);
     w->setPath(defaultPath);
     w->setProjectName(BaseProjectWizardDialog::uniqueProjectName(defaultPath));
+
+    connect(w, SIGNAL(introPageLeft(QString,QString)), this, SLOT(introPageLeft(QString,QString)));
+
     if (CustomWizardPrivate::verbose)
         qDebug() << "initProjectWizardDialog" << w << w->pageIds();
 }
@@ -428,7 +396,7 @@ Core::GeneratedFiles CustomProjectWizard::generateFiles(const QWizard *w, QStrin
     QTC_ASSERT(dialog, return Core::GeneratedFiles())
     const QString targetPath = dialog->path() + QLatin1Char('/') + dialog->projectName();
     // Add project name as macro.
-    FieldReplacementMap fieldReplacementMap = defaultReplacementMap(dialog);
+    FieldReplacementMap fieldReplacementMap = replacementMap(dialog);
     fieldReplacementMap.insert(QLatin1String("ProjectName"), dialog->projectName());
     if (CustomWizardPrivate::verbose)
         qDebug() << "CustomProjectWizard::generateFiles" << dialog << targetPath << fieldReplacementMap;
@@ -449,4 +417,10 @@ bool CustomProjectWizard::postGenerateFiles(const QWizard *, const Core::Generat
     return true;
 }
 
+void CustomProjectWizard::introPageLeft(const QString &project, const QString & /* path */)
+{
+    // Make '%ProjectName%' available in base replacements.
+    context()->baseReplacements.insert(QLatin1String("ProjectName"), project);
+}
+
 } // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.h b/src/plugins/projectexplorer/customwizard/customwizard.h
index b294a7bef3d..842afd85f51 100644
--- a/src/plugins/projectexplorer/customwizard/customwizard.h
+++ b/src/plugins/projectexplorer/customwizard/customwizard.h
@@ -49,6 +49,7 @@ class BaseProjectWizardDialog;
 
 namespace Internal {
     struct CustomWizardParameters;
+    struct CustomWizardContext;
 }
 
 // Factory for creating wizard. Can be registered under a name
@@ -105,6 +106,7 @@ public:
 
 protected:
     typedef QSharedPointer<Internal::CustomWizardParameters> CustomWizardParametersPtr;
+    typedef QSharedPointer<Internal::CustomWizardContext> CustomWizardContextPtr;
 
     void initWizardDialog(QWizard *w, const QString &defaultPath,
                           const WizardPageList &extensionPages) const;
@@ -113,10 +115,11 @@ protected:
     Core::GeneratedFiles generateWizardFiles(const QString &path,
                                              const FieldReplacementMap &defaultFields,
                                              QString *errorMessage) const;
-    // Create replacement map from QWizard fields with additional useful fields.
-    FieldReplacementMap defaultReplacementMap(const QWizard *w) const;
+    // Create replacement map as static base fields + QWizard fields
+    FieldReplacementMap replacementMap(const QWizard *w) const;
 
     CustomWizardParametersPtr parameters() const;
+    CustomWizardContextPtr context() const;
 
 private:
     void setParameters(const CustomWizardParametersPtr &p);
@@ -128,7 +131,9 @@ private:
 // A custom project wizard presenting CustomProjectWizardDialog
 // (Project intro page and fields page) for wizards of type "project".
 // Overwrites postGenerateFiles() to open the project file which is the
-// last one by convention.
+// last one by convention. Also inserts '%ProjectName%' into the base
+// replacement map once the intro page is left to have it available
+// for QLineEdit-type fields' default text.
 
 class PROJECTEXPLORER_EXPORT CustomProjectWizard : public CustomWizard
 {
@@ -150,6 +155,9 @@ protected:
 
     void initProjectWizardDialog(BaseProjectWizardDialog *w, const QString &defaultPath,
                                  const WizardPageList &extensionPages) const;
+
+private slots:
+    void introPageLeft(const QString &project, const QString &path);
 };
 
 } // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/customwizard/customwizardpage.cpp b/src/plugins/projectexplorer/customwizard/customwizardpage.cpp
index 290aba4884e..bb33911f04f 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardpage.cpp
+++ b/src/plugins/projectexplorer/customwizard/customwizardpage.cpp
@@ -63,9 +63,17 @@ void TextFieldComboBox::setText(const QString &s)
 }
 
 // --------------- CustomWizardFieldPage
-CustomWizardFieldPage::CustomWizardFieldPage(const FieldList &fields,
+
+CustomWizardFieldPage::LineEditData::LineEditData(QLineEdit* le, const QString &defText) :
+    lineEdit(le), defaultText(defText)
+{
+}
+
+CustomWizardFieldPage::CustomWizardFieldPage(const QSharedPointer<CustomWizardContext> &ctx,
+                                             const FieldList &fields,
                                              QWidget *parent) :
     QWizardPage(parent),
+    m_context(ctx),
     m_formLayout(new QFormLayout)
 {
     if (debug)
@@ -75,6 +83,10 @@ CustomWizardFieldPage::CustomWizardFieldPage(const FieldList &fields,
     setLayout(m_formLayout);
 }
 
+CustomWizardFieldPage::~CustomWizardFieldPage()
+{
+}
+
 void CustomWizardFieldPage::addRow(const QString &name, QWidget *w)
 {
     m_formLayout->addRow(name, w);
@@ -82,36 +94,50 @@ void CustomWizardFieldPage::addRow(const QString &name, QWidget *w)
 
 // Create widget a control based  on the control attributes map
 // and register it with the QWizard.
-QWidget *CustomWizardFieldPage::registerControl(const CustomWizardField &field)
+void CustomWizardFieldPage::addField(const CustomWizardField &field)\
 {
-    //  Register field,  Indicate mandatory by '*' (only when registering)
+    //  Register field, indicate mandatory by '*' (only when registering)
     QString fieldName = field.name;
     if (field.mandatory)
         fieldName += QLatin1Char('*');
     // Check known classes: QComboBox
     const QString className = field.controlAttributes.value(QLatin1String("class"));
+    QWidget *fieldWidget = 0;
     if (className == QLatin1String("QComboBox")) {
-        TextFieldComboBox *combo = new TextFieldComboBox;
-        // Set up items
-        do {
-            const QString choices = field.controlAttributes.value(QLatin1String("combochoices"));
-            if (choices.isEmpty())
-                break;
-            combo->addItems(choices.split(QLatin1Char(',')));
-            bool ok;
-            const QString currentIndexS = field.controlAttributes.value(QLatin1String("defaultindex"));
-            if (currentIndexS.isEmpty())
-                break;
-            const int currentIndex = currentIndexS.toInt(&ok);
-            if (!ok || currentIndex < 0 || currentIndex >= combo->count())
-                break;
-            combo->setCurrentIndex(currentIndex);
-        } while (false);
-        registerField(fieldName, combo, "text", SIGNAL(text4Changed(QString)));
-        return combo;
-    } // QComboBox
-    // Default to QLineEdit
+        fieldWidget = registerComboBox(fieldName, field);
+    } else {
+        fieldWidget = registerLineEdit(fieldName, field);
+    }
+    addRow(field.description, fieldWidget);
+}
+
+QWidget *CustomWizardFieldPage::registerComboBox(const QString &fieldName,
+                                                 const CustomWizardField &field)
+{
+    TextFieldComboBox *combo = new TextFieldComboBox;
+    do { // Set up items and current index
+        const QString choices = field.controlAttributes.value(QLatin1String("combochoices"));
+        if (choices.isEmpty())
+            break;
+        combo->addItems(choices.split(QLatin1Char(',')));
+        bool ok;
+        const QString currentIndexS = field.controlAttributes.value(QLatin1String("defaultindex"));
+        if (currentIndexS.isEmpty())
+            break;
+        const int currentIndex = currentIndexS.toInt(&ok);
+        if (!ok || currentIndex < 0 || currentIndex >= combo->count())
+            break;
+        combo->setCurrentIndex(currentIndex);
+    } while (false);
+    registerField(fieldName, combo, "text", SIGNAL(text4Changed(QString)));
+    return combo;
+} // QComboBox
+
+QWidget *CustomWizardFieldPage::registerLineEdit(const QString &fieldName,
+                                                 const CustomWizardField &field)
+{
     QLineEdit *lineEdit = new QLineEdit;
+
     const QString validationRegExp = field.controlAttributes.value(QLatin1String("validator"));
     if (!validationRegExp.isEmpty()) {
         QRegExp re(validationRegExp);
@@ -120,29 +146,40 @@ QWidget *CustomWizardFieldPage::registerControl(const CustomWizardField &field)
         } else {
             qWarning("Invalid custom wizard field validator regular expression %s.", qPrintable(validationRegExp));
         }
-        m_validatorLineEdits.push_back(lineEdit);
     }
-    lineEdit->setText(field.controlAttributes.value(QLatin1String("defaulttext")));
     registerField(fieldName, lineEdit, "text", SIGNAL(textEdited(QString)));
+
+    const QString defaultText = field.controlAttributes.value(QLatin1String("defaulttext"));
+    m_lineEdits.push_back(LineEditData(lineEdit, defaultText));
     return lineEdit;
 }
 
-void CustomWizardFieldPage::addField(const CustomWizardField &field)
+void CustomWizardFieldPage::initializePage()
 {
-    addRow(field.description, registerControl(field));
+    QWizardPage::initializePage();
+    // 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.
+    foreach(const LineEditData &led, m_lineEdits) {
+        if (!led.defaultText.isEmpty()) {
+            QString defaultText = led.defaultText;
+            CustomWizardContext::replaceFields(m_context->baseReplacements, &defaultText);
+            led.lineEdit->setText(defaultText);
+        }
+    }
 }
 
 bool CustomWizardFieldPage::validatePage()
 {
     // Check line edits with validators
-    foreach(QLineEdit *le, m_validatorLineEdits) {
-        int pos = 0;
-        const QValidator *val = le->validator();
-        QTC_ASSERT(val, return false);
-        QString text = le->text();
-        if (val->validate(text, pos) != QValidator::Acceptable) {
-            le->setFocus();
-            return false;
+    foreach(const LineEditData &led, m_lineEdits) {
+        if (const QValidator *val = led.lineEdit->validator()) {
+            int pos = 0;
+            QString text = led.lineEdit->text();
+            if (val->validate(text, pos) != QValidator::Acceptable) {
+                led.lineEdit->setFocus();
+                return false;
+            }
         }
     }
     return QWizardPage::validatePage();
@@ -150,9 +187,10 @@ bool CustomWizardFieldPage::validatePage()
 
 // --------------- CustomWizardPage
 
-CustomWizardPage::CustomWizardPage(const FieldList &f,
+CustomWizardPage::CustomWizardPage(const QSharedPointer<CustomWizardContext> &ctx,
+                                   const FieldList &f,
                                    QWidget *parent) :
-    CustomWizardFieldPage(f, parent),
+    CustomWizardFieldPage(ctx, f, parent),
     m_pathChooser(new Utils::PathChooser)
 {
     addRow(tr("Path:"), m_pathChooser);
diff --git a/src/plugins/projectexplorer/customwizard/customwizardpage.h b/src/plugins/projectexplorer/customwizard/customwizardpage.h
index ce1484ab0c3..fe27a0873f3 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardpage.h
+++ b/src/plugins/projectexplorer/customwizard/customwizardpage.h
@@ -48,6 +48,7 @@ namespace Internal {
 
 struct CustomWizardField;
 struct CustomWizardParameters;
+struct CustomWizardContext;
 
 // A non-editable combo for text editing purposes that plays
 // with QWizard::registerField (providing a settable text property).
@@ -74,19 +75,33 @@ class CustomWizardFieldPage : public QWizardPage {
 public:
     typedef QList<CustomWizardField> FieldList;
 
-    explicit CustomWizardFieldPage(const FieldList &f,
+    explicit CustomWizardFieldPage(const QSharedPointer<CustomWizardContext> &ctx,
+                                   const FieldList &f,
                                    QWidget *parent = 0);
+    virtual ~CustomWizardFieldPage();
+
     virtual bool validatePage();
+    virtual void initializePage();
 
 protected:
     inline void addRow(const QString &name, QWidget *w);
 
 private:
-    QWidget *registerControl(const CustomWizardField &f);
+    struct LineEditData {
+        explicit LineEditData(QLineEdit* le = 0, const QString &defText = QString());
+        QLineEdit* lineEdit;
+        QString defaultText;
+    };
+    typedef QList<LineEditData> LineEditDataList;
+
+    QWidget *registerLineEdit(const QString &fieldName, const CustomWizardField &field);
+    QWidget *registerComboBox(const QString &fieldName, const CustomWizardField &field);
 
     void addField(const CustomWizardField &f);
+
+    const QSharedPointer<CustomWizardContext> m_context;
     QFormLayout *m_formLayout;
-    QList<QLineEdit*> m_validatorLineEdits;
+    LineEditDataList m_lineEdits;
 };
 
 // A custom wizard page presenting the fields to be used and a path chooser
@@ -96,7 +111,8 @@ private:
 class CustomWizardPage : public CustomWizardFieldPage {
     Q_OBJECT
 public:
-    explicit CustomWizardPage(const FieldList &f,
+    explicit CustomWizardPage(const QSharedPointer<CustomWizardContext> &ctx,
+                              const FieldList &f,
                               QWidget *parent = 0);
 
     QString path() const;
diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
index bf89b9fe902..c64c62c8c15 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
+++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
@@ -29,6 +29,10 @@
 
 #include "customwizardparameters.h"
 
+#include <coreplugin/mimedatabase.h>
+#include <coreplugin/icore.h>
+#include <cpptools/cpptoolsconstants.h>
+
 #include <QtCore/QDebug>
 #include <QtCore/QLocale>
 #include <QtCore/QFile>
@@ -467,5 +471,75 @@ QString CustomWizardParameters::toString() const
     return rc;
 }
 
+// ------------ CustomWizardContext
+
+void CustomWizardContext::replaceFields(const FieldReplacementMap &fm, QString *s)
+{
+    if (debug) {
+        qDebug().nospace() << "CustomWizardContext::replaceFields with " <<
+                fm << *s;
+    }
+    const QChar delimiter = QLatin1Char('%');
+    const QChar modifierDelimiter = QLatin1Char(':');
+    int pos = 0;
+    while (pos < s->size()) {
+        pos = s->indexOf(delimiter, pos);
+        if (pos < 0)
+            break;
+        int nextPos = s->indexOf(delimiter, pos + 1);
+        if (nextPos == -1)
+            break;
+        nextPos++; // Point past 2nd delimiter
+        if (nextPos == pos + 2) {
+            pos = nextPos; // Skip '%%'
+            continue;
+        }
+        // Evaluate field specification for modifiers
+        // "%field:l%"
+        QString fieldSpec = s->mid(pos + 1, nextPos - pos - 2);
+        const int fieldSpecSize = fieldSpec.size();
+        char modifier = '\0';
+        if (fieldSpecSize >= 3 && fieldSpec.at(fieldSpecSize - 2) == modifierDelimiter) {
+            modifier = fieldSpec.at(fieldSpecSize - 1).toLatin1();
+            fieldSpec.truncate(fieldSpecSize - 2);
+        }
+        const FieldReplacementMap::const_iterator it = fm.constFind(fieldSpec);
+        if (it == fm.constEnd()) {
+            pos = nextPos; // Not found, skip
+            continue;
+        }
+        // Assign
+        QString replacement = it.value();
+        switch (modifier) {
+        case 'l':
+            replacement = it.value().toLower();
+            break;
+        case 'u':
+            replacement = it.value().toUpper();
+            break;
+        case 'c': // Capitalize first letter
+            replacement = it.value();
+            if (!replacement.isEmpty())
+                replacement[0] = replacement.at(0).toUpper();
+            break;
+        default:
+            break;
+        }
+        s->replace(pos, nextPos - pos, replacement);
+        pos += replacement.size();
+    }
+}
+
+void CustomWizardContext::reset()
+{
+    // Basic replacement fields: Suffixes.
+    baseReplacements.clear();
+    const Core::MimeDatabase *mdb = Core::ICore::instance()->mimeDatabase();
+    baseReplacements.insert(QLatin1String("CppSourceSuffix"),
+                            mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE)));
+    baseReplacements.insert(QLatin1String("CppHeaderSuffix"),
+                            mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)));
+}
+
 } // namespace Internal
 } // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.h b/src/plugins/projectexplorer/customwizard/customwizardparameters.h
index 3ee6a19b23d..63c6c0adbc1 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardparameters.h
+++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.h
@@ -79,6 +79,28 @@ public:
     int firstPageId;
 };
 
+// Context used for one wizard run, shared between CustomWizard
+// and the CustomWizardPage as it is used for the QLineEdit-type fields'
+// default texts as well. Contains basic replacement fields
+// like  '%CppSourceSuffix%', '%CppHeaderSuffix%' (settings-dependent)
+// reset() should be called before each wizard run to refresh them.
+// CustomProjectWizard additionally inserts '%ProjectName%' from
+// the intro page to have it available for default texts.
+
+struct CustomWizardContext {
+    typedef QMap<QString, QString> FieldReplacementMap;
+
+    void reset();
+
+    // Replace field values delimited by '%' with special modifiers:
+    // %Field% -> simple replacement
+    // %Field:l% -> lower case replacement, 'u' upper case,
+    // 'c' capitalize first letter.
+    static void replaceFields(const FieldReplacementMap &fm, QString *s);
+
+    FieldReplacementMap baseReplacements;
+};
+
 } // namespace Internal
 } // namespace ProjectExplorer
 
-- 
GitLab