diff --git a/src/plugins/coreplugin/basefilewizard.cpp b/src/plugins/coreplugin/basefilewizard.cpp
index 18e4285b721b09d7c0a0ea241319a4d075cee7d8..4b04d72ad72c0541304e4b1c25d72cc03a7809bf 100644
--- a/src/plugins/coreplugin/basefilewizard.cpp
+++ b/src/plugins/coreplugin/basefilewizard.cpp
@@ -185,6 +185,7 @@ class BaseFileWizardParameterData : public QSharedData
 {
 public:
     explicit BaseFileWizardParameterData(IWizard::WizardKind kind = IWizard::FileWizard);
+    void clear();
 
     IWizard::WizardKind kind;
     QIcon icon;
@@ -200,6 +201,17 @@ BaseFileWizardParameterData::BaseFileWizardParameterData(IWizard::WizardKind k)
 {
 }
 
+void BaseFileWizardParameterData::clear()
+{
+    kind = IWizard::FileWizard;
+    icon = QIcon();
+    description.clear();
+    displayName.clear();
+    id.clear();
+    category.clear();
+    displayCategory.clear();
+}
+
 BaseFileWizardParameters::BaseFileWizardParameters(IWizard::WizardKind kind) :
    m_d(new BaseFileWizardParameterData(kind))
 {
@@ -221,6 +233,21 @@ BaseFileWizardParameters::~BaseFileWizardParameters()
 {
 }
 
+void BaseFileWizardParameters::clear()
+{
+    m_d->clear();
+}
+
+CORE_EXPORT QDebug operator<<(QDebug d, const BaseFileWizardParameters &p)
+{
+    d.nospace() << "Kind: " << p.kind() << " Id: " << p.id()
+                << " Category: " << p.category()
+                << " DisplayName: " << p.displayName()
+                << " Description: " << p.description()
+                << " DisplayCategory: " << p.displayCategory();
+    return d;
+}
+
 IWizard::WizardKind BaseFileWizardParameters::kind() const
 {
     return m_d->kind;
diff --git a/src/plugins/coreplugin/basefilewizard.h b/src/plugins/coreplugin/basefilewizard.h
index 4f4efc043cff78a037296d7f9ed3d9751011d1e9..268b54f5436de1d14b268a3f7986a51eb1ec69e6 100644
--- a/src/plugins/coreplugin/basefilewizard.h
+++ b/src/plugins/coreplugin/basefilewizard.h
@@ -42,6 +42,7 @@
 QT_BEGIN_NAMESPACE
 class QWizard;
 class QWizardPage;
+class QDebug;
 QT_END_NAMESPACE
 
 namespace Core {
@@ -105,6 +106,8 @@ public:
     BaseFileWizardParameters &operator=(const BaseFileWizardParameters&);
    ~BaseFileWizardParameters();
 
+    void clear();
+
     IWizard::WizardKind kind() const;
     void setKind(IWizard::WizardKind k);
 
@@ -130,6 +133,8 @@ private:
     QSharedDataPointer<BaseFileWizardParameterData> m_d;
 };
 
+CORE_EXPORT QDebug operator<<(QDebug d, const BaseFileWizardParameters &);
+
 /* A generic wizard for creating files.
  *
  * The abstract methods:
diff --git a/src/plugins/projectexplorer/baseprojectwizarddialog.h b/src/plugins/projectexplorer/baseprojectwizarddialog.h
index 8bd5b1dbe9c2dfc97634ad3fa4fed1e52b4fde1f..f4f40c27813ce23bd3188ea4f8d17dfd9f9accc9 100644
--- a/src/plugins/projectexplorer/baseprojectwizarddialog.h
+++ b/src/plugins/projectexplorer/baseprojectwizarddialog.h
@@ -51,12 +51,13 @@ class PROJECTEXPLORER_EXPORT BaseProjectWizardDialog : public QWizard
     Q_OBJECT
 
 protected:
-    explicit BaseProjectWizardDialog(QWidget *parent = 0);
     explicit BaseProjectWizardDialog(Utils::ProjectIntroPage *introPage,
                                      int introId = -1,
                                      QWidget *parent = 0);
 
 public:
+    explicit BaseProjectWizardDialog(QWidget *parent = 0);
+
     virtual ~BaseProjectWizardDialog();
 
     QString projectName() const;
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.cpp b/src/plugins/projectexplorer/customwizard/customwizard.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7f36a7a450d1ae64a7787edd6567910a6b93a25b
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizard.cpp
@@ -0,0 +1,396 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "customwizard.h"
+#include "customwizardparameters.h"
+#include "customwizardpage.h"
+#include "projectexplorer.h"
+#include "baseprojectwizarddialog.h"
+
+#include <coreplugin/icore.h>
+#include <coreplugin/mimedatabase.h>
+#include <cpptools/cpptoolsconstants.h>
+#include <utils/qtcassert.h>
+
+#include <QtCore/QDebug>
+#include <QtCore/QFile>
+#include <QtCore/QMap>
+#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
+
+enum { debug = 0 };
+
+static const char templatePathC[] = "templates/wizards";
+static const char configFileC[] = "wizard.xml";
+
+namespace ProjectExplorer {
+
+struct CustomWizardPrivate {
+    QSharedPointer<Internal::CustomWizardParameters> m_parameters;
+};
+
+CustomWizard::CustomWizard(const Core::BaseFileWizardParameters& baseFileParameters,
+                           QObject *parent) :
+    Core::BaseFileWizard(baseFileParameters, parent),
+    d(new CustomWizardPrivate)
+{
+}
+
+CustomWizard::~CustomWizard()
+{
+    delete d;
+}
+
+void CustomWizard::setParameters(const CustomWizardParametersPtr &p)
+{
+    d->m_parameters = p;
+}
+
+// Add a wizard page with an id, visibly warn if something goes wrong.
+static inline void addWizardPage(QWizard *w, QWizardPage *p, int id)
+{
+    if (id == -1) {
+        w->addPage(p);
+    } else {
+        if (w->pageIds().contains(id)) {
+            qWarning("Page %d already present in custom wizard dialog, defaulting to add.", id);
+            w->addPage(p);
+        } else {
+            w->setPage(id, p);
+        }
+    }
+}
+
+// Initialize a wizard with a custom file page.
+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);
+    customPage->setPath(defaultPath);
+    addWizardPage(wizard, customPage, parameters()->firstPageId);
+    if (!parameters()->fieldPageTitle.isEmpty())
+        customPage->setTitle(parameters()->fieldPageTitle);
+    foreach(QWizardPage *ep, extensionPages)
+        wizard->addPage(ep);
+    Core::BaseFileWizard::setupWizard(wizard);
+    if (debug)
+        qDebug() << "initWizardDialog" << wizard << wizard->pageIds();
+}
+
+QWizard *CustomWizard::createWizardDialog(QWidget *parent,
+                                                 const QString &defaultPath,
+                                                 const WizardPageList &extensionPages) const
+{
+    QTC_ASSERT(!d->m_parameters.isNull(), return 0);
+    QWizard *wizard = new QWizard(parent);
+    initWizardDialog(wizard, defaultPath, extensionPages);
+    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,
+                              const QString &targetDirectory,
+                              const CustomProjectWizard::FieldReplacementMap &fm,
+                              Core::GeneratedFiles *files,
+                              QString *errorMessage)
+{
+    const QChar slash =  QLatin1Char('/');
+    const QString sourcePath = sourceDirectory + slash + cwFile.source;
+    replaceFields(fm, &cwFile.target);
+    const QString targetPath = QDir::toNativeSeparators(targetDirectory + slash + cwFile.target);
+    if (debug)
+        qDebug() << "generating " << targetPath << sourcePath << fm;
+    QFile file(sourcePath);
+    if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) {
+        *errorMessage = QString::fromLatin1("Cannot open %1: %2").arg(sourcePath, file.errorString());
+        return false;
+    }
+    QString contents = QString::fromLocal8Bit(file.readAll());
+    if (!contents.isEmpty() && !fm.isEmpty())
+        replaceFields(fm, &contents);
+    Core::GeneratedFile generatedFile;
+    generatedFile.setContents(contents);
+    generatedFile.setPath(targetPath);
+    files->push_back(generatedFile);
+    return true;
+}
+
+// Helper to find a specific wizard page of a wizard by type.
+template <class WizardPage>
+        WizardPage *findWizardPage(const QWizard *w)
+{
+    foreach (int pageId, w->pageIds())
+        if (WizardPage *wp = qobject_cast<WizardPage *>(w->page(pageId)))
+            return wp;
+    return 0;
+}
+
+Core::GeneratedFiles CustomWizard::generateFiles(const QWizard *dialog, QString *errorMessage) const
+{
+    // Look for the Custom field page to find the path
+    const Internal::CustomWizardPage *cwp = findWizardPage<Internal::CustomWizardPage>(dialog);
+    QTC_ASSERT(cwp, return Core::GeneratedFiles())
+    QString path = cwp->path();
+    const FieldReplacementMap fieldMap = defaultReplacementMap(dialog);
+    if (debug)
+        qDebug() << "CustomWizard::generateFiles" << dialog << path << fieldMap;
+    return generateWizardFiles(path, fieldMap, errorMessage);
+}
+
+Core::GeneratedFiles CustomWizard::generateWizardFiles(const QString &targetPath,
+                                                       const FieldReplacementMap &fieldReplacementMap,
+                                                       QString *errorMessage) const
+{
+    if (debug)
+        qDebug() << "Replacements" << fieldReplacementMap;
+    // Create files
+    Core::GeneratedFiles rc;
+    foreach(const Internal::CustomWizardFile &file, d->m_parameters->files)
+        if (!createFile(file, d->m_parameters->directory, targetPath, fieldReplacementMap, &rc, errorMessage))
+            return Core::GeneratedFiles();
+    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
+{
+    FieldReplacementMap fieldReplacementMap;
+    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;
+}
+
+CustomWizard::CustomWizardParametersPtr CustomWizard::parameters() const
+{
+    return d->m_parameters;
+}
+
+// Static factory map
+typedef QMap<QString, QSharedPointer<ICustomWizardFactory> > CustomWizardFactoryMap;
+Q_GLOBAL_STATIC(CustomWizardFactoryMap, customWizardFactoryMap)
+
+void CustomWizard::registerFactory(const QString &name, const ICustomWizardFactoryPtr &f)
+{
+    customWizardFactoryMap()->insert(name, f);
+}
+
+CustomWizard *CustomWizard::createWizard(const CustomWizardParametersPtr &p, const Core::BaseFileWizardParameters &b)
+{
+    CustomWizard * rc = 0;
+    if (p->klass.isEmpty()) {
+        // Use defaults for empty class names
+        switch (b.kind()) {
+            case Core::IWizard::ProjectWizard:
+                rc = new CustomProjectWizard(b);
+                break;
+            case Core::IWizard::FileWizard:
+            case Core::IWizard::ClassWizard:
+                rc = new CustomWizard(b);
+                break;
+            }
+    } else {
+        // Look up class name in map
+        const CustomWizardFactoryMap::const_iterator it = customWizardFactoryMap()->constFind(p->klass);
+        if (it != customWizardFactoryMap()->constEnd())
+            rc = it.value()->create(b);
+    }
+    if (!rc) {
+        qWarning("Unable to create custom wizard for class %s.", qPrintable(p->klass));
+        return 0;
+    }
+    rc->setParameters(p);
+    return rc;
+}
+
+// Scan the subdirectories of the template directory for directories
+// containing valid configuration files and parse them into wizards.
+QList<CustomWizard*> CustomWizard::createWizards()
+{
+    QList<CustomWizard*> rc;
+    QString errorMessage;
+    const QString templateDirName = Core::ICore::instance()->resourcePath() +
+                                    QLatin1Char('/') + QLatin1String(templatePathC);
+    const QDir templateDir(templateDirName);
+    if (!templateDir.exists()) {
+        if (debug)
+           qWarning("Custom project template path %s does not exist.", qPrintable(templateDir.absolutePath()));
+        return rc;
+    }
+
+    const QList<QFileInfo> dirs = templateDir.entryInfoList(QDir::Dirs|QDir::Readable|QDir::NoDotAndDotDot,
+                                                            QDir::Name|QDir::IgnoreCase);
+    const QString configFile = QLatin1String(configFileC);
+    // Check and parse config file in each directory.
+
+    foreach(const QFileInfo &dirFi, dirs) {
+        const QDir dir(dirFi.absoluteFilePath());
+        if (dir.exists(configFile)) {
+            CustomWizardParametersPtr parameters(new Internal::CustomWizardParameters);
+            Core::BaseFileWizardParameters baseFileParameters;
+            if (parameters->parse(dir.absoluteFilePath(configFile), &baseFileParameters, &errorMessage)) {
+                parameters->directory = dir.absolutePath();
+                if (debug)
+                    qDebug() << (*parameters);
+                if (CustomWizard *w = createWizard(parameters, baseFileParameters))
+                    rc.push_back(w);
+            } else {
+                qWarning("Failed to initialize custom project wizard in %s: %s",
+                         qPrintable(dir.absolutePath()), qPrintable(errorMessage));
+            }
+        }
+    }
+    return rc;
+}
+
+// --------------- CustomProjectWizard
+
+CustomProjectWizard::CustomProjectWizard(const Core::BaseFileWizardParameters& baseFileParameters,
+                                         QObject *parent) :
+    CustomWizard(baseFileParameters, parent)
+{
+}
+
+QWizard *CustomProjectWizard::createWizardDialog(QWidget *parent,
+                                        const QString &defaultPath,
+                                        const WizardPageList &extensionPages) const
+{
+    QTC_ASSERT(!parameters().isNull(), return 0);
+    BaseProjectWizardDialog *projectDialog = new BaseProjectWizardDialog(parent);
+    initProjectWizardDialog(projectDialog, defaultPath, extensionPages);
+    return projectDialog;
+}
+
+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);
+        addWizardPage(w, cp, parameters()->firstPageId);
+        if (!parameters()->fieldPageTitle.isEmpty())
+            cp->setTitle(parameters()->fieldPageTitle);
+    }
+    foreach(QWizardPage *ep, extensionPages)
+        w->addPage(ep);
+    w->setPath(defaultPath);
+    w->setProjectName(BaseProjectWizardDialog::uniqueProjectName(defaultPath));
+    if (debug)
+        qDebug() << "initProjectWizardDialog" << w << w->pageIds();
+}
+
+Core::GeneratedFiles CustomProjectWizard::generateFiles(const QWizard *w, QString *errorMessage) const
+{
+    const BaseProjectWizardDialog *dialog = qobject_cast<const BaseProjectWizardDialog *>(w);
+    QTC_ASSERT(dialog, return Core::GeneratedFiles())
+    const QString targetPath = dialog->path() + QLatin1Char('/') + dialog->projectName();
+    // Add project name as macro.
+    FieldReplacementMap fieldReplacementMap = defaultReplacementMap(dialog);
+    fieldReplacementMap.insert(QLatin1String("ProjectName"), dialog->projectName());
+    if (debug)
+        qDebug() << "CustomProjectWizard::generateFiles" << dialog << targetPath << fieldReplacementMap;
+    return generateWizardFiles(targetPath, fieldReplacementMap, errorMessage);
+}
+
+bool CustomProjectWizard::postGenerateFiles(const QWizard *, const Core::GeneratedFiles &l, QString *errorMessage)
+{
+    // Post-Generate: Open the project
+    const QString proFileName = l.back().path();
+    const bool opened = ProjectExplorer::ProjectExplorerPlugin::instance()->openProject(proFileName);
+    if (debug)
+        qDebug() << "CustomProjectWizard::postGenerateFiles: opened " << proFileName << opened;
+    if (opened) {
+        *errorMessage = tr("The project %1 could not be opened.").arg(proFileName);
+        return false;
+    }
+    return true;
+}
+
+} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.h b/src/plugins/projectexplorer/customwizard/customwizard.h
new file mode 100644
index 0000000000000000000000000000000000000000..2b14e4043544c9a0be9b95cbf70ed6192355156b
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizard.h
@@ -0,0 +1,153 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef CUSTOMPROJECTWIZARD_H
+#define CUSTOMPROJECTWIZARD_H
+
+#include "../projectexplorer_export.h"
+
+#include <coreplugin/basefilewizard.h>
+
+#include <QtCore/QSharedPointer>
+#include <QtCore/QList>
+
+QT_BEGIN_NAMESPACE
+class QDir;
+QT_END_NAMESPACE
+
+namespace ProjectExplorer {
+class CustomWizard;
+struct CustomWizardPrivate;
+class BaseProjectWizardDialog;
+
+namespace Internal {
+    struct CustomWizardParameters;
+}
+
+// Factory for creating wizard. Can be registered under a name
+// in CustomWizard.
+class ICustomWizardFactory {
+public:
+    virtual CustomWizard *create(const Core::BaseFileWizardParameters& baseFileParameters,
+                                 QObject *parent = 0) const = 0;
+    virtual ~ICustomWizardFactory() {}
+};
+
+// Convenience template to create wizard classes.
+template <class Wizard> class CustomWizardFactory : public ICustomWizardFactory {
+    virtual CustomWizard *create(const Core::BaseFileWizardParameters& baseFileParameters,
+                                 QObject *parent = 0) const
+    { return new Wizard(baseFileParameters, parent); }
+};
+
+// A custom wizard presenting CustomWizardDialog (fields page containing
+// path control) for wizards of type "class" or "file". Serves as base class
+// for project wizards.
+
+class PROJECTEXPLORER_EXPORT CustomWizard : public Core::BaseFileWizard
+{
+    Q_OBJECT
+public:
+    typedef QMap<QString, QString> FieldReplacementMap;
+    typedef QSharedPointer<ICustomWizardFactory> ICustomWizardFactoryPtr;
+
+    explicit CustomWizard(const Core::BaseFileWizardParameters& baseFileParameters,
+                          QObject *parent = 0);
+    virtual ~CustomWizard();
+
+    // Can be reimplemented to create custom wizards. initWizardDialog() needs to be
+    // called.
+    virtual QWizard *createWizardDialog(QWidget *parent,
+                                        const QString &defaultPath,
+                                        const WizardPageList &extensionPages) const;
+
+    virtual Core::GeneratedFiles generateFiles(const QWizard *w, QString *errorMessage) const;
+
+
+    // Register a factory for a derived custom widget
+    static void registerFactory(const QString &name, const ICustomWizardFactoryPtr &f);
+    template <class Wizard> static void registerFactory(const QString &name)
+        { registerFactory(name, ICustomWizardFactoryPtr(new CustomWizardFactory<Wizard>)); }
+
+    // Create all wizards. As other plugins might register factories for derived
+    // classes, call it in extensionsInitialized().
+    static QList<CustomWizard*> createWizards();
+
+protected:
+    typedef QSharedPointer<Internal::CustomWizardParameters> CustomWizardParametersPtr;
+
+    void initWizardDialog(QWizard *w, const QString &defaultPath,
+                          const WizardPageList &extensionPages) const;
+
+    // generate files in path
+    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;
+
+    CustomWizardParametersPtr parameters() const;
+
+private:
+    void setParameters(const CustomWizardParametersPtr &p);
+
+    static CustomWizard *createWizard(const CustomWizardParametersPtr &p, const Core::BaseFileWizardParameters &b);
+    CustomWizardPrivate *d;
+};
+
+// 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.
+
+class PROJECTEXPLORER_EXPORT CustomProjectWizard : public CustomWizard
+{
+    Q_OBJECT
+public:
+    explicit CustomProjectWizard(const Core::BaseFileWizardParameters& baseFileParameters,
+                                 QObject *parent = 0);
+
+    // Can be reimplemented to create custom project wizards.
+    // initProjectWizardDialog() needs to be called.
+    virtual QWizard *createWizardDialog(QWidget *parent,
+                                        const QString &defaultPath,
+                                        const WizardPageList &extensionPages) const;
+
+    virtual Core::GeneratedFiles generateFiles(const QWizard *w, QString *errorMessage) const;
+
+protected:
+    virtual bool postGenerateFiles(const QWizard *w, const Core::GeneratedFiles &l, QString *errorMessage);
+
+    void initProjectWizardDialog(BaseProjectWizardDialog *w, const QString &defaultPath,
+                                 const WizardPageList &extensionPages) const;
+};
+
+} // namespace ProjectExplorer
+
+#endif // CUSTOMPROJECTWIZARD_H
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.pri b/src/plugins/projectexplorer/customwizard/customwizard.pri
new file mode 100644
index 0000000000000000000000000000000000000000..61475f3ae4170657f21b38dd870d6c7139f6f7fd
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizard.pri
@@ -0,0 +1,7 @@
+INCLUDEPATH *= $$PWD
+HEADERS += $$PWD/customwizard.h \
+    $$PWD/customwizardparameters.h \
+    $$PWD/customwizardpage.h
+SOURCES += $$PWD/customwizard.cpp \
+    $$PWD/customwizardparameters.cpp \
+    $$PWD/customwizardpage.cpp
diff --git a/src/plugins/projectexplorer/customwizard/customwizardpage.cpp b/src/plugins/projectexplorer/customwizard/customwizardpage.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5c63f30717effb5366af8d7bea66f45c6c439404
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizardpage.cpp
@@ -0,0 +1,160 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "customwizardpage.h"
+#include "customwizardparameters.h"
+
+#include <utils/pathchooser.h>
+
+#include <QtCore/QRegExp>
+#include <QtCore/QDebug>
+
+#include <QtGui/QWizardPage>
+#include <QtGui/QFormLayout>
+#include <QtGui/QLineEdit>
+#include <QtGui/QLabel>
+#include <QtGui/QRegExpValidator>
+#include <QtGui/QComboBox>
+
+enum { debug = 0 };
+
+namespace ProjectExplorer {
+namespace Internal {
+
+TextFieldComboBox::TextFieldComboBox(QWidget *parent) :
+    QComboBox(parent)
+{
+    setEditable(false);
+    connect(this, SIGNAL(currentIndexChanged(QString)), this, SIGNAL(textChanged(QString)));
+}
+
+void TextFieldComboBox::setText(const QString &s)
+{
+    const int index = findText(s);
+    if (index != -1 && index != currentIndex())
+        setCurrentIndex(index);
+}
+
+// --------------- CustomWizardFieldPage
+CustomWizardFieldPage::CustomWizardFieldPage(const FieldList &fields,
+                                             QWidget *parent) :
+    QWizardPage(parent),
+    m_formLayout(new QFormLayout)
+{
+    if (debug)
+        qDebug() << Q_FUNC_INFO << fields;
+    foreach(const CustomWizardField &f, fields)
+        addField(f);
+    setLayout(m_formLayout);
+}
+
+void CustomWizardFieldPage::addRow(const QString &name, QWidget *w)
+{
+    m_formLayout->addRow(name, w);
+}
+
+// Create widget a control based  on the control attributes map
+// and register it with the QWizard.
+QWidget *CustomWizardFieldPage::registerControl(const CustomWizardField &field)
+{
+    //  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"));
+    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
+    QLineEdit *lineEdit = new QLineEdit;
+    const QString validationRegExp = field.controlAttributes.value(QLatin1String("validator"));
+    if (!validationRegExp.isEmpty()) {
+        QRegExp re(validationRegExp);
+        if (re.isValid()) {
+            lineEdit->setValidator(new QRegExpValidator(re, lineEdit));
+        } else {
+            qWarning("Invalid custom wizard field validator regular expression %s.", qPrintable(validationRegExp));
+        }
+    }
+    lineEdit->setText(field.controlAttributes.value(QLatin1String("defaulttext")));
+    registerField(fieldName, lineEdit, "text", SIGNAL(textEdited(QString)));
+    return lineEdit;
+}
+
+void CustomWizardFieldPage::addField(const CustomWizardField &field)
+{
+    addRow(field.description, registerControl(field));
+}
+
+// --------------- CustomWizardPage
+
+CustomWizardPage::CustomWizardPage(const FieldList &f,
+                                   QWidget *parent) :
+    CustomWizardFieldPage(f, parent),
+    m_pathChooser(new Utils::PathChooser)
+{
+    addRow(tr("Path:"), m_pathChooser);
+    connect(m_pathChooser, SIGNAL(validChanged()), this, SIGNAL(completeChanged()));
+}
+
+QString CustomWizardPage::path() const
+{
+    return m_pathChooser->path();
+}
+
+void CustomWizardPage::setPath(const QString &path)
+{
+    m_pathChooser->setPath(path);
+}
+
+bool CustomWizardPage::isComplete() const
+{
+    return m_pathChooser->isValid();
+}
+
+} // namespace Internal
+} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/customwizard/customwizardpage.h b/src/plugins/projectexplorer/customwizard/customwizardpage.h
new file mode 100644
index 0000000000000000000000000000000000000000..26ec8e9910fcad2c2fcc4ba9636113a1abbe030b
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizardpage.h
@@ -0,0 +1,108 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef CUSTOMPROJECTWIZARDDIALOG_H
+#define CUSTOMPROJECTWIZARDDIALOG_H
+
+#include <QtGui/QComboBox>
+#include <QtGui/QWizardPage>
+#include <QtCore/QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QFormLayout;
+QT_END_NAMESPACE
+
+namespace Utils {
+    class PathChooser;
+}
+
+namespace ProjectExplorer {
+namespace Internal {
+
+struct CustomWizardField;
+struct CustomWizardParameters;
+
+// A non-editable combo for text editing purposes that plays
+// with QWizard::registerField (providing a settable text property).
+class TextFieldComboBox : public QComboBox {
+    Q_PROPERTY(QString text READ text WRITE setText)
+    Q_OBJECT
+public:
+    explicit TextFieldComboBox(QWidget *parent = 0);
+
+    QString text() const { return currentText(); }
+    void setText(const QString &s);
+
+signals:
+    void text4Changed(const QString &); // Do not conflict with Qt 3 compat signal.
+};
+
+// A simple custom wizard page presenting the fields to be used
+// as page 2 of a BaseProjectWizardDialog if there are any fields.
+// Uses the 'field' functionality of QWizard.
+class CustomWizardFieldPage : public QWizardPage {
+    Q_OBJECT
+public:
+    typedef QList<CustomWizardField> FieldList;
+
+    explicit CustomWizardFieldPage(const FieldList &f,
+                                   QWidget *parent = 0);
+protected:
+    inline void addRow(const QString &name, QWidget *w);
+
+private:
+    QWidget *registerControl(const CustomWizardField &f);
+
+    void addField(const CustomWizardField &f);
+    QFormLayout *m_formLayout;
+};
+
+// A custom wizard page presenting the fields to be used and a path chooser
+// at the bottom (for use by "class"/"file" wizards). Does validation on
+// the Path chooser only (as the other fields can by validated by regexps).
+
+class CustomWizardPage : public CustomWizardFieldPage {
+    Q_OBJECT
+public:
+    explicit CustomWizardPage(const FieldList &f,
+                              QWidget *parent = 0);
+
+    QString path() const;
+    void setPath(const QString &path);
+
+    virtual bool isComplete() const;
+
+private:
+    Utils::PathChooser *m_pathChooser;
+};
+
+} // namespace Internal
+} // namespace ProjectExplorer
+
+#endif // CUSTOMPROJECTWIZARDDIALOG_H
diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bc9bcc2acbc067f4059f3df5bf073d57fafdd645
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
@@ -0,0 +1,472 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "customwizardparameters.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QLocale>
+#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
+#include <QtCore/QXmlStreamReader>
+#include <QtCore/QXmlStreamAttribute>
+#include <QtGui/QIcon>
+
+enum { debug = 0 };
+
+static const char customWizardElementC[] = "wizard";
+static const char iconElementC[] = "icon";
+static const char descriptionElementC[] = "description";
+static const char displayNameElementC[] = "displayName";
+static const char idAttributeC[] = "id";
+static const char kindAttributeC[] = "kind";
+static const char klassAttributeC[] = "class";
+static const char firstPageAttributeC[] = "firstpage";
+static const char langAttributeC[] = "xml:lang";
+static const char categoryAttributeC[] = "category";
+static const char displayCategoryElementC[] = "displayCategory";
+static const char fieldPageTitleElementC[] = "fieldpagetitle";
+static const char fieldsElementC[] = "fields";
+static const char fieldElementC[] = "field";
+
+static const char fieldDescriptionElementC[] = "fielddescription";
+static const char fieldNameAttributeC[] = "name";
+static const char fieldMandatoryAttributeC[] = "mandatory";
+static const char fieldControlElementC[] = "fieldcontrol";
+
+static const char filesElementC[] = "files";
+static const char fileElementC[] = "file";
+static const char fileNameSourceAttributeC[] = "source";
+static const char fileNameTargetAttributeC[] = "target";
+
+enum ParseState {
+    ParseBeginning,
+    ParseWithinWizard,
+    ParseWithinFields,
+    ParseWithinField,
+    ParseWithinFiles,
+    ParseWithinFile,
+    ParseError
+};
+
+namespace ProjectExplorer {
+namespace Internal {
+
+CustomWizardField::CustomWizardField() :
+    mandatory(false)
+{
+}
+
+void CustomWizardField::clear()
+{
+    mandatory = false;
+    name.clear();
+    description.clear();
+    controlAttributes.clear();
+}
+
+CustomWizardParameters::CustomWizardParameters() :
+        firstPageId(-1)
+{
+}
+
+void CustomWizardParameters::clear()
+{
+    directory.clear();
+    files.clear();
+    fields.clear();
+    firstPageId = -1;
+}
+
+// Resolve icon file path relative to config file directory.
+static inline QIcon wizardIcon(const QString &configFileFullPath,
+                               const QString &xmlIconFileName)
+{
+    const QFileInfo fi(xmlIconFileName);
+    if (fi.isFile())
+        return QIcon(fi.absoluteFilePath());
+    if (!fi.isRelative())
+        return QIcon();
+    // Expand by config path
+    const QFileInfo absFi(QFileInfo(configFileFullPath).absolutePath() +
+                          QLatin1Char('/') + xmlIconFileName);
+    if (absFi.isFile())
+        return QIcon(absFi.absoluteFilePath());
+    return QIcon();
+}
+
+// Forward a reader over element text
+static inline bool skipOverElementText(QXmlStreamReader &reader)
+{
+    QXmlStreamReader::TokenType next = QXmlStreamReader::EndElement;
+    do {
+        next = reader.readNext();
+    } while (next == QXmlStreamReader::Characters || next == QXmlStreamReader::EntityReference
+             || next == QXmlStreamReader::ProcessingInstruction || next == QXmlStreamReader::Comment);
+    return next == QXmlStreamReader::EndElement;
+}
+
+// Helper for reading text element depending on a language.
+// Assign the element text to the string passed on if the language matches,
+// that is, the element has no language attribute or there is an exact match.
+// If there is no match, skip over the element text.
+static inline void assignLanguageElementText(QXmlStreamReader &reader,
+                                             const QString &desiredLanguage,
+                                             QString *target)
+{
+    const QStringRef elementLanguage = reader.attributes().value(langAttributeC);
+    if (elementLanguage.isEmpty() || elementLanguage == desiredLanguage) {
+        *target = reader.readElementText();
+    } else {
+        // Language mismatch: forward to end element.
+        skipOverElementText(reader);
+    }
+}
+
+// Copy&paste from above to call a setter of BaseFileParameters.
+// Implementation of a sophisticated mem_fun pattern is left
+// as an exercise to the reader.
+static inline void assignLanguageElementText(QXmlStreamReader &reader,
+                                             const QString &desiredLanguage,
+                                             Core::BaseFileWizardParameters *bp,
+                                             void (Core::BaseFileWizardParameters::*setter)(const QString &))
+{
+    const QStringRef elementLanguage = reader.attributes().value(langAttributeC);
+    if (elementLanguage.isEmpty() || elementLanguage == desiredLanguage) {
+        (bp->*setter)(reader.readElementText());
+    } else {
+        // Language mismatch: forward to end element.
+        skipOverElementText(reader);
+    }
+}
+
+// Read level sub-elements of "wizard"
+static bool parseCustomProjectElement(QXmlStreamReader &reader,
+                                      const QString &configFileFullPath,
+                                      const QString &language,
+                                      CustomWizardParameters *p,
+                                      Core::BaseFileWizardParameters *bp)
+{
+    const QStringRef elementName = reader.name();
+    if (elementName == QLatin1String(iconElementC)) {
+        const QString path = reader.readElementText();
+        const QIcon icon = wizardIcon(configFileFullPath, path);
+        if (icon.availableSizes().isEmpty()) {
+            qWarning("Invalid icon path '%s' encountered in custom project template %s.",
+                     qPrintable(path), qPrintable(configFileFullPath));
+        } else {
+                bp->setIcon(icon);
+        }
+        return true;
+    }
+    if (elementName == QLatin1String(descriptionElementC)) {
+        assignLanguageElementText(reader, language, bp,
+                                  &Core::BaseFileWizardParameters::setDescription);
+        return true;
+    }
+    if (elementName == QLatin1String(displayNameElementC)) {
+        assignLanguageElementText(reader, language, bp,
+                                  &Core::BaseFileWizardParameters::setDisplayName);
+        return true;
+    }
+    if (elementName == QLatin1String(displayCategoryElementC)) {
+        assignLanguageElementText(reader, language, bp,
+                                  &Core::BaseFileWizardParameters::setDisplayCategory);
+        return true;
+    }
+    if (elementName == QLatin1String(fieldPageTitleElementC)) {
+        assignLanguageElementText(reader, language, &p->fieldPageTitle);
+        return true;
+    }
+    return false;
+}
+
+// Read sub-elements of "fields"
+static bool parseFieldElement(QXmlStreamReader &reader,
+                              const QString &language,
+                              CustomWizardField *m)
+{
+    const QStringRef elementName = reader.name();
+    if (elementName == QLatin1String(fieldDescriptionElementC)) {
+        assignLanguageElementText(reader, language, &m->description);
+        return true;
+    }
+    // Copy widget control attributes
+    if (elementName == QLatin1String(fieldControlElementC)) {
+        foreach(const QXmlStreamAttribute &attribute, reader.attributes())
+            m->controlAttributes.insert(attribute.name().toString(), attribute.value().toString());
+        skipOverElementText(reader);
+        return true;
+    }
+    return false;
+}
+
+// Switch parser state depending on opening element name.
+static ParseState nextOpeningState(ParseState in, const QStringRef &name)
+{
+    switch (in) {
+    case ParseBeginning:
+        if (name == QLatin1String(customWizardElementC))
+            return ParseWithinWizard;
+        break;
+    case ParseWithinWizard:
+        if (name == QLatin1String(fieldsElementC))
+            return ParseWithinFields;
+        if (name == QLatin1String(filesElementC))
+            return ParseWithinFiles;
+        break;
+    case ParseWithinFields:
+        if (name == QLatin1String(fieldElementC))
+            return ParseWithinField;
+        break;
+    case ParseWithinFiles:
+        if (name == QLatin1String(fileElementC))
+            return ParseWithinFile;
+        break;
+    case ParseWithinField:
+    case ParseWithinFile:
+    case ParseError:
+        break;
+    }
+    return ParseError;
+};
+
+// Switch parser state depending on closing element name.
+static ParseState nextClosingState(ParseState in, const QStringRef &name)
+{
+    switch (in) {
+    case ParseBeginning:
+        break;
+    case ParseWithinWizard:
+        if (name == QLatin1String(customWizardElementC))
+            return ParseBeginning;
+        break;
+    case ParseWithinFields:
+        if (name == QLatin1String(fieldsElementC))
+            return ParseWithinWizard;
+        break;
+    case ParseWithinField:
+        if (name == QLatin1String(fieldElementC))
+            return ParseWithinFields;
+        break;
+    case ParseWithinFiles:
+        if (name == QLatin1String(filesElementC))
+            return ParseWithinWizard;
+        break;
+    case ParseWithinFile:
+        if (name == QLatin1String(fileElementC))
+            return ParseWithinFiles;
+        break;
+    case ParseError:
+        break;
+    }
+    return ParseError;
+};
+
+// Parse kind attribute
+static inline Core::IWizard::WizardKind kindAttribute(const QXmlStreamReader &r)
+{
+    const QStringRef value = r.attributes().value(QLatin1String(kindAttributeC));
+    if (!value.isEmpty()) {
+        if (value == QLatin1String("file"))
+            return Core::IWizard::FileWizard;
+        if (value == QLatin1String("class"))
+            return Core::IWizard::ClassWizard;
+    }
+    return Core::IWizard::ProjectWizard;
+}
+
+static inline QString msgError(const QXmlStreamReader &reader,
+                               const QString &fileName,
+                               const QString &what)
+{
+    return QString::fromLatin1("Error in %1 at line %2, column %3: %4").
+            arg(fileName).arg(reader.lineNumber()).arg(reader.columnNumber()).arg(what);
+}
+
+static inline bool booleanAttributeValue(const QXmlStreamReader &r, const char *name)
+{
+    return r.attributes().value(QLatin1String(name)) == QLatin1String("true");
+}
+
+static inline int integerAttributeValue(const QXmlStreamReader &r, const char *name, int defaultValue)
+{
+    const QString sValue = r.attributes().value(QLatin1String(name)).toString();
+    if (sValue.isEmpty())
+        return defaultValue;
+    bool ok;
+    const int value = sValue.toInt(&ok);
+    if (!ok) {
+        qWarning("Invalid integer value specification '%s' for attribute '%s'.",
+                 qPrintable(sValue), name);
+        return defaultValue;
+    }
+    return value;
+}
+
+static inline QString attributeValue(const QXmlStreamReader &r, const char *name)
+{
+    return r.attributes().value(QLatin1String(name)).toString();
+}
+
+// Return locale language attribute "de_UTF8" -> "de", empty string for "C"
+static inline QString localeLanguage()
+{
+    QLocale loc;
+    QString name = loc.name();
+    const int underScorePos = name.indexOf(QLatin1Char('_'));
+    if (underScorePos != -1)
+        name.truncate(underScorePos);
+    if (name.compare(QLatin1String("C"), Qt::CaseInsensitive) == 0)
+        name.clear();
+    return name;
+}
+
+// Main parsing routine
+bool CustomWizardParameters::parse(QIODevice &device,
+                                   const QString &configFileFullPath,
+                                   Core::BaseFileWizardParameters *bp,
+                                   QString *errorMessage)
+{
+    QXmlStreamReader reader(&device);
+    QXmlStreamReader::TokenType token = QXmlStreamReader::EndDocument;
+    ParseState state = ParseBeginning;
+    clear();
+    bp->clear();
+    bp->setKind(Core::IWizard::ProjectWizard);
+    const QString language = localeLanguage();
+    CustomWizardField field;
+    do {
+        token = reader.readNext();
+        switch (token) {
+        case QXmlStreamReader::Invalid:
+            *errorMessage = msgError(reader, configFileFullPath, reader.errorString());
+            return false;
+        case QXmlStreamReader::StartElement:
+            do {
+                // Read out subelements applicable to current state
+                if (state == ParseWithinWizard && parseCustomProjectElement(reader, configFileFullPath, language, this, bp))
+                    break;
+                if (state == ParseWithinField && parseFieldElement(reader, language, &field))
+                    break;
+                // switch to next state
+                state = nextOpeningState(state, reader.name());
+                // Read attributes post state-switching
+                switch (state) {
+                case ParseError:
+                    *errorMessage = msgError(reader, configFileFullPath,
+                                             QString::fromLatin1("Unexpected start element %1").arg(reader.name().toString()));
+                    return false;
+                case ParseWithinWizard:
+                    bp->setId(attributeValue(reader, idAttributeC));
+                    bp->setCategory(attributeValue(reader, categoryAttributeC));
+                    bp->setKind(kindAttribute(reader));
+                    klass = attributeValue(reader, klassAttributeC);
+                    firstPageId = integerAttributeValue(reader, firstPageAttributeC, -1);
+                    break;
+                case ParseWithinField: // field attribute
+                    field.name = attributeValue(reader, fieldNameAttributeC);
+                    field.mandatory = booleanAttributeValue(reader, fieldMandatoryAttributeC);
+                    break;
+                case ParseWithinFile: { // file attribute
+                        CustomWizardFile file;
+                        file.source = attributeValue(reader, fileNameSourceAttributeC);
+                        file.target = attributeValue(reader, fileNameTargetAttributeC);
+                        if (file.target.isEmpty())
+                            file.target = file.source;
+                        if (file.source.isEmpty()) {
+                            qWarning("Skipping empty file name in custom project.");
+                        } else {
+                            files.push_back(file);
+                        }
+                    }
+                    break;
+                default:
+                    break;
+                }
+            } while (false);
+            break;
+        case QXmlStreamReader::EndElement:
+            state = nextClosingState(state, reader.name());
+            if (state == ParseError) {
+                *errorMessage = msgError(reader, configFileFullPath,
+                                         QString::fromLatin1("Unexpected end element %1").arg(reader.name().toString()));
+                return false;
+            }
+            if (state == ParseWithinFields) { // Leaving a field element
+                fields.push_back(field);
+                field.clear();
+            }
+            break;
+        default:
+            break;
+        }
+    } while (token != QXmlStreamReader::EndDocument);
+    return true;
+}
+
+bool CustomWizardParameters::parse(const QString &configFileFullPath,
+                                   Core::BaseFileWizardParameters *bp,
+                                   QString *errorMessage)
+{
+    QFile configFile(configFileFullPath);
+    if (!configFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
+        *errorMessage = QString::fromLatin1("Cannot open %1: %2").arg(configFileFullPath, configFile.errorString());
+        return false;
+    }
+    return parse(configFile, configFileFullPath, bp, errorMessage);
+}
+
+QDebug operator<<(QDebug d, const CustomWizardField &m)
+{
+    QDebug nsp = d.nospace();
+    nsp << "Name: " << m.name;
+    if (m.mandatory)
+        nsp << '*';
+    nsp << " Desc: " << m.description << " Control: " << m.controlAttributes;
+    return d;
+}
+
+QDebug operator<<(QDebug d, const CustomWizardFile &f)
+{
+    d.nospace() << "source: " << f.source << " target: " << f.target;
+    return d;
+}
+
+QDebug operator<<(QDebug d, const CustomWizardParameters &p)
+{
+    QDebug nsp = d.nospace();
+    nsp << "Dir: " << p.directory << " klass:" << p.klass << " Files: " << p.files;
+    if (!p.fields.isEmpty())
+        nsp << " Fields: " << p.fields;
+    nsp << "First page: " << p.firstPageId;
+    return d;
+}
+
+} // namespace Internal
+} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.h b/src/plugins/projectexplorer/customwizard/customwizardparameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..e526da0d0f11db91807b9cd801ec813fdff990c2
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.h
@@ -0,0 +1,88 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef CUSTOMPROJECTWIZARDPARAMETERS_H
+#define CUSTOMPROJECTWIZARDPARAMETERS_H
+
+#include <coreplugin/basefilewizard.h>
+
+#include <QtCore/QList>
+#include <QtCore/QMap>
+
+QT_BEGIN_NAMESPACE
+class QIODevice;
+class QDebug;
+QT_END_NAMESPACE
+
+namespace ProjectExplorer {
+namespace Internal {
+
+struct CustomWizardField {
+    // Parameters of the widget control are stored as map
+    typedef QMap<QString, QString> ControlAttributeMap;
+    CustomWizardField();
+    void clear();
+
+    QString description;
+    QString name;
+    ControlAttributeMap controlAttributes;
+    bool mandatory;
+};
+
+struct CustomWizardFile {
+    QString source;
+    QString target;
+};
+
+struct CustomWizardParameters
+{
+public:
+    CustomWizardParameters();
+    void clear();
+    bool parse(QIODevice &device, const QString &configFileFullPath,
+               Core::BaseFileWizardParameters *bp, QString *errorMessage);
+    bool parse(const QString &configFileFullPath,
+               Core::BaseFileWizardParameters *bp, QString *errorMessage);
+
+    QString directory;
+    QString klass;
+    QList<CustomWizardFile> files;
+    QString fieldPageTitle;
+    QList<CustomWizardField> fields;
+    int firstPageId;
+};
+
+QDebug operator<<(QDebug d, const CustomWizardField &);
+QDebug operator<<(QDebug d, const CustomWizardFile &);
+QDebug operator<<(QDebug d, const CustomWizardParameters &);
+
+} // namespace Internal
+} // namespace ProjectExplorer
+
+#endif // CUSTOMPROJECTWIZARDPARAMETERS_H
diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp
index d34d3fa663a10bfc62b1134d87f3754322238c61..5e89c9742c2ffcc81c6842c6ae9c17d0e456fbfa 100644
--- a/src/plugins/projectexplorer/projectexplorer.cpp
+++ b/src/plugins/projectexplorer/projectexplorer.cpp
@@ -52,6 +52,7 @@
 #include "pluginfilefactory.h"
 #include "processstep.h"
 #include "projectexplorerconstants.h"
+#include "customwizard.h"
 #include "projectfilewizardextension.h"
 #include "projecttreewidget.h"
 #include "projectwindow.h"
@@ -902,6 +903,10 @@ void ProjectExplorerPlugin::extensionsInitialized()
         d->m_profileMimeTypes += pf->mimeTypes();
         addAutoReleasedObject(pf);
     }
+    // Add custom wizards, for which other plugins might have registered
+    // class factories
+    foreach(Core::IWizard *cpw, ProjectExplorer::CustomWizard::createWizards())
+        addAutoReleasedObject(cpw);
 }
 
 void ProjectExplorerPlugin::shutdown()
diff --git a/src/plugins/projectexplorer/projectexplorer.pro b/src/plugins/projectexplorer/projectexplorer.pro
index c912a1985d6e108698f8ef682f4a470a5cdd7d95..26001ffaa9bc80e2f76adf24414577a0ede01654 100644
--- a/src/plugins/projectexplorer/projectexplorer.pro
+++ b/src/plugins/projectexplorer/projectexplorer.pro
@@ -6,6 +6,7 @@ include(../../qtcreatorplugin.pri)
 include(projectexplorer_dependencies.pri)
 include(../../shared/scriptwrapper/scriptwrapper.pri)
 include(../../libs/utils/utils.pri)
+include(customwizard/customwizard.pri)
 INCLUDEPATH += $$PWD/../../libs/utils
 HEADERS += projectexplorer.h \
     projectexplorer_export.h \
diff --git a/src/plugins/qt4projectmanager/qt4projectmanagerplugin.cpp b/src/plugins/qt4projectmanager/qt4projectmanagerplugin.cpp
index 81940970b5f97dabca4ab862516755f23caa8a8c..1783d4f5fba7516901b7548a78988f6fe364f252 100644
--- a/src/plugins/qt4projectmanager/qt4projectmanagerplugin.cpp
+++ b/src/plugins/qt4projectmanager/qt4projectmanagerplugin.cpp
@@ -133,6 +133,8 @@ bool Qt4ProjectManagerPlugin::initialize(const QStringList &arguments, QString *
     addAutoReleasedObject(new TestWizard);
     addAutoReleasedObject(new CustomWidgetWizard);
 
+    CustomQt4ProjectWizard::registerSelf();
+
     addAutoReleasedObject(new QMakeStepFactory);
     addAutoReleasedObject(new MakeStepFactory);
 
diff --git a/src/plugins/qt4projectmanager/wizards/qtwizard.cpp b/src/plugins/qt4projectmanager/wizards/qtwizard.cpp
index 1113597ed0a57a363cd47b7d3696e98eb49ddb77..6eb0abe27c2647f6980f1f4d8476e483fa8d193b 100644
--- a/src/plugins/qt4projectmanager/wizards/qtwizard.cpp
+++ b/src/plugins/qt4projectmanager/wizards/qtwizard.cpp
@@ -38,7 +38,6 @@
 #include <coreplugin/icore.h>
 #include <cpptools/cpptoolsconstants.h>
 #include <extensionsystem/pluginmanager.h>
-
 #include <QtCore/QCoreApplication>
 #include <QtCore/QVariant>
 
@@ -103,6 +102,11 @@ QString QtWizard::profileSuffix()
 }
 
 bool QtWizard::postGenerateFiles(const QWizard *w, const Core::GeneratedFiles &l, QString *errorMessage)
+{
+    return QtWizard::qt4ProjectPostGenerateFiles(w, l, errorMessage);
+}
+
+bool QtWizard::qt4ProjectPostGenerateFiles(const QWizard *w, const Core::GeneratedFiles &l, QString *errorMessage)
 {
     const QString proFileName = l.back().path();
     const BaseQt4ProjectWizardDialog *dialog = qobject_cast<const BaseQt4ProjectWizardDialog *>(w);
@@ -144,6 +148,35 @@ bool QtWizard::showModulesPageForLibraries()
     return true;
 }
 
+// ------------ CustomQt4ProjectWizard
+CustomQt4ProjectWizard::CustomQt4ProjectWizard(const Core::BaseFileWizardParameters& baseFileParameters,
+                                               QObject *parent) :
+    ProjectExplorer::CustomProjectWizard(baseFileParameters, parent)
+{
+}
+
+QWizard *CustomQt4ProjectWizard::createWizardDialog(QWidget *parent,
+                                                    const QString &defaultPath,
+                                                    const WizardPageList &extensionPages) const
+{
+    BaseQt4ProjectWizardDialog *wizard =  new BaseQt4ProjectWizardDialog(false, parent);
+    initProjectWizardDialog(wizard, defaultPath, extensionPages);
+    if (wizard->pageIds().contains(targetPageId))
+        qWarning("CustomQt4ProjectWizard: Unable to insert target page at %d", int(targetPageId));
+    wizard->addTargetsPage(QSet<QString>(), targetPageId);
+    return wizard;
+}
+
+bool CustomQt4ProjectWizard::postGenerateFiles(const QWizard *w, const Core::GeneratedFiles &l, QString *errorMessage)
+{
+    return QtWizard::qt4ProjectPostGenerateFiles(w, l, errorMessage);
+}
+
+void CustomQt4ProjectWizard::registerSelf()
+{
+    ProjectExplorer::CustomWizard::registerFactory<CustomQt4ProjectWizard>(QLatin1String("qt4project"));
+}
+
 // ----------------- BaseQt4ProjectWizardDialog
 BaseQt4ProjectWizardDialog::BaseQt4ProjectWizardDialog(bool showModulesPage, QWidget *parent) :
     ProjectExplorer::BaseProjectWizardDialog(parent),
diff --git a/src/plugins/qt4projectmanager/wizards/qtwizard.h b/src/plugins/qt4projectmanager/wizards/qtwizard.h
index e305324a5af429dbd345d56ffb24f169858b1d24..760a72095da0ee2bcc6d498caa785d1cc13acb88 100644
--- a/src/plugins/qt4projectmanager/wizards/qtwizard.h
+++ b/src/plugins/qt4projectmanager/wizards/qtwizard.h
@@ -32,6 +32,7 @@
 
 #include "qtprojectparameters.h"
 #include <projectexplorer/baseprojectwizarddialog.h>
+#include <projectexplorer/customwizard/customwizard.h>
 
 #include <coreplugin/basefilewizard.h>
 
@@ -77,6 +78,8 @@ public:
     // Query CppTools settings for the class wizard settings
     static bool lowerCaseFiles();
 
+    static bool qt4ProjectPostGenerateFiles(const QWizard *w, const Core::GeneratedFiles &l, QString *errorMessage);
+
 protected:
     static bool showModulesPageForApplications();
     static bool showModulesPageForLibraries();
@@ -85,6 +88,25 @@ private:
     bool postGenerateFiles(const QWizard *w, const Core::GeneratedFiles &l, QString *errorMessage);
 };
 
+// A custom wizard with an additional Qt 4 target page
+class CustomQt4ProjectWizard : public ProjectExplorer::CustomProjectWizard {
+    Q_OBJECT
+public:
+    explicit CustomQt4ProjectWizard(const Core::BaseFileWizardParameters& baseFileParameters,
+                                    QObject *parent = 0);
+
+    virtual QWizard *createWizardDialog(QWidget *parent,
+                                        const QString &defaultPath,
+                                        const WizardPageList &extensionPages) const;
+    static void registerSelf();
+
+protected:
+    virtual bool postGenerateFiles(const QWizard *, const Core::GeneratedFiles &l, QString *errorMessage);
+
+private:
+    enum { targetPageId = 2 };
+};
+
 /* BaseQt4ProjectWizardDialog: Additionally offers modules page
  * and getter/setter for blank-delimited modules list, transparently
  * handling the visibility of the modules page list as well as a page
@@ -93,13 +115,13 @@ private:
 
 class BaseQt4ProjectWizardDialog : public ProjectExplorer::BaseProjectWizardDialog {
     Q_OBJECT
-
 protected:
-    explicit BaseQt4ProjectWizardDialog(bool showModulesPage, QWidget *parent = 0);
     explicit BaseQt4ProjectWizardDialog(bool showModulesPage,
                                         Utils::ProjectIntroPage *introPage,
                                         int introId = -1,
                                         QWidget *parent = 0);
+public:
+    explicit BaseQt4ProjectWizardDialog(bool showModulesPage, QWidget *parent = 0);
     virtual ~BaseQt4ProjectWizardDialog();
 
     int addModulesPage(int id = -1);
@@ -107,7 +129,6 @@ protected:
 
     static QSet<QString> desktopTarget();
 
-public:
     QString selectedModules() const;
     void setSelectedModules(const QString &, bool lock = false);