Qt Creator 插件开发(22):添加新的工程类型(续三)

Core::BaseFileWizard

Core::BaseFileWizard在 coreplugin/basefilewizard.h 文件中声明:

class CORE_EXPORT BaseFileWizard : public IWizard
{
    Q_DISABLE_COPY(BaseFileWizard)
    Q_OBJECT

public:
    virtual ~BaseFileWizard();

    // IWizard
    virtual WizardKind kind() const;
    virtual QIcon icon() const;
    virtual QString description() const;
    virtual QString displayName() const;
    virtual QString id() const;

    virtual QString category() const;
    virtual QString displayCategory() const;

    virtual void runWizard(const QString &path, QWidget *parent);

    // Build a file name, adding the extension unless baseName already has one
    static QString buildFileName(const QString &path,
                                 const QString &baseName,
                                 const QString &extension);

    // Sets some standard options on a QWizard
    static void setupWizard(QWizard *);

    // Read "shortTitle" dynamic property of the pageId
    // and apply it as the title of corresponding progress item
    static void applyExtensionPageShortTitle(Utils::Wizard *wizard, int pageId);

protected:
    typedef QList WizardPageList;

    explicit BaseFileWizard(const BaseFileWizardParameters &parameters,
                            QObject *parent = 0);

    // Overwrite to create the wizard dialog on the parent, adding
    // the extension pages.
    virtual QWizard *createWizardDialog(QWidget *parent,
                                    const QString &defaultPath,
                                    const WizardPageList &extensionPages) const = 0;

    // Overwrite to query the parameters from the dialog and generate the files.
    virtual GeneratedFiles generateFiles(const QWizard *w,
                                         QString *errorMessage) const = 0;

    /* Physically write files. Re-implement (calling the base implementation)
     * to create files with CustomGeneratorAttribute set. */
    virtual bool writeFiles(const GeneratedFiles &files, QString *errorMessage);

    /* Overwrite to perform steps to be done after files are actually created.
     * The default implementation opens editors with the newly generated files. */
    virtual bool postGenerateFiles(const QWizard *w,
                                   const GeneratedFiles &l,
                                   QString *errorMessage);

    // Utility that returns the preferred suffix for a mime type
    static QString preferredSuffix(const QString &mimeType);

    // Utility that performs an overwrite check on a set of files. It checks if
    // the file exists, can be overwritten at all and prompts the user.
    enum OverwriteResult { OverwriteOk,  OverwriteError,  OverwriteCanceled };
    OverwriteResult promptOverwrite(const QStringList &files,
                                    QString *errorMessage) const;

    // Utility to open the editors for the files whose attribute is set accordingly.
    static bool postGenerateOpenEditors(const GeneratedFiles &l,
                                        QString *errorMessage = 0);

private:
    BaseFileWizardPrivate *m_d;
};

BaseFileWizard类实现了IWizard接口,并且增加了几个新的函数:

  • createWizardDialog– 该函数必须被子类重写,用于提供一个供runWizard()函数显示的向导,其中
    • parent参数应当作为该函数返回的QWizard的父类
    • defaultPath参数应当作为生成的文件的默认路径
    • extensionPages参数列出了应该被向导默认显示的所有页面
  • generateFiles– 该函数在用户点击了向导的“完成”按钮之后自动调用,该函数的实现必须按要求创建Core::GeneratedFile类的实例
  • postGenerateFiles– 该函数在generateFiles()返回之后被调用,子类可以通过覆盖该函数,做你需要做的任何事情

下面,我们使用BaseFileWizard来实现我们自己的向导:

#ifndef MODELCLASSWIZARD_H
#define MODELCLASSWIZARD_H

#include <coreplugin/basefilewizard.h>
#include <QMap>

class ModelClassWizard : public Core::BaseFileWizard
{
    Q_OBJECT
public:
    ModelClassWizard(const Core::BaseFileWizardParameters &parameters,
                     QObject *parent = 0);
    ~ModelClassWizard();
    QWizard *createWizardDialog(QWidget *parent,
                                const QString &defaultPath,
                                const WizardPageList &extensionPages) const;
    Core::GeneratedFiles generateFiles(const QWizard *w,
                                       QString *errorMessage) const;
private:
    QString readFile(const QString& fileName,
                     const QMap& replacementMap) const;
};

#endif // MODELCLASSWIZARD_H

我们的构造函数和析构函数很简单:

ModelClassWizard::ModelClassWizard(const Core::BaseFileWizardParameters &parameters,
                                   QObject *parent)
    : Core::BaseFileWizard(parameters, parent)
{
}

ModelClassWizard::~ModelClassWizard()
{
}

函数createWizardDialog()创建一个很简单的QWizard,将我们前面写好的ModelNamePage作为其第一个页面,后面则添加其他默认页面:

QWizard* ModelClassWizard::createWizardDialog(QWidget *parent,
                                              const QString &defaultPath,
                                              const WizardPageList &extensionPages) const
{
    // Create a wizard
    QWizard* wizard = new QWizard(parent);
    wizard->setWindowTitle("Model Class Wizard");
    // Make our page as first page
    ModelNamePage* page = new ModelNamePage(wizard);
    int pageId = wizard->addPage(page);
    wizard->setProperty("_PageId_", pageId);
    page->setPath(defaultPath);
    // Now add the remaining pages
    foreach (QWizardPage *p, extensionPages) {
        wizard->addPage(p);
    }
    return wizard;
}

函数readFile()读取文件,将其内容以字符串的形式返回。在返回字符串之前,函数需要使用第二个参数提供的替换字符表修正这个字符串:

QString ModelClassWizard::readFile(const QString& fileName,
                                   const QMap& replacementMap) const
{
    QFile file(fileName);
    file.open(QFile::ReadOnly);
    QString retStr = file.readAll();
    QMap::const_iterator it = replacementMap.begin();
    QMap::const_iterator end = replacementMap.end();
    while(it != end) {
        retStr.replace(it.key(), it.value());
        ++it;
    }
    return retStr;
}

这个函数将会实现这么一个功能:假设我们有一个文件 sample.txt:

#ifndef {{UPPER_CLASS_NAME}}_H
#define {{UPPER_CLASS_NAME}}_H

#include <{{BASE_CLASS_NAME}}>

struct {{CLASS_NAME}}Data;

class {{CLASS_NAME}} : public {{BASE_CLASS_NAME}}
{
    Q_OBJECT

public:
    {{CLASS_NAME}}(QObject* parent=0);
    ~{{CLASS_NAME}}();

    int rowCount(const QModelIndex& parent) const;
    QVariant data(const QModelIndex& index, int role) const;

private:
    {{CLASS_NAME}}Data* d;
};

#endif // {{UPPER_CLASS_NAME}}_H

其中的 {{xyz}} 作为一种占位符。如果我们有如下代码片段:

QMap replacementMap;
replacementMap["{{UPPER_CLASS_NAME}}"] = "LIST_MODEL";
replacementMap["{{BASE_CLASS_NAME}}"] = "QAbstractListModel";
replacementMap["{{CLASS_NAME}}"] = "ListModel";

QString contents = readFile("Sample.txt", replacementTable);

那么,执行过后 contents 字符串的内容应该是:

#ifndef LIST_MODEL_H
#define LIST_MODEL_H

#include <QAbstractListModel>

struct ListModelData;

class ListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    ListModel(QObject* parent=0);
    ~ListModel();

    int rowCount(const QModelIndex& parent) const;
    QVariant data(const QModelIndex& index, int role) const;

private:
    ListModelData* d;
};

#endif // LIST_MODEL_H

看起来很神奇吗?我们的实际策略与此类似:提供这种模板文件,然后使用用户输入的信息替换其中的占位符,来生成相应的文件。

下面,我们来看看函数generateFiles()的实现。这个函数创建了两个Core::GeneratedFile对象,并且在返回之前赋予了正确的数据:

Core::GeneratedFiles ModelClassWizard::generateFiles(const QWizard *w,
                                                     QString *errorMessage) const
{
    Q_UNUSED(errorMessage);
    Core::GeneratedFiles ret;
    int pageId = w->property("_PageId_").toInt();
    ModelNamePage* page = qobject_cast(w->page(pageId));
    if(!page) {
        return ret;
    }
    ModelClassParameters params = page->parameters();
    QMap replacementMap;
    replacementMap["{{UPPER_CLASS_NAME}}"] = params.className.toUpper();
    replacementMap["{{BASE_CLASS_NAME}}"] = params.baseClass;
    replacementMap["{{CLASS_NAME}}"] = params.className;
    replacementMap["{{CLASS_HEADER}}"] = QFileInfo(params.headerFile).fileName();
    Core::GeneratedFile headerFile(params.path + "/" + params.headerFile);
    headerFile.setEditorId(CppEditor::Constants::CPPEDITOR_ID);
    Core::GeneratedFile sourceFile(params.path + "/" + params.sourceFile);
    sourceFile.setEditorId(CppEditor::Constants::CPPEDITOR_ID);
    if(params.baseClass == "QAbstractItemModel") {
        headerFile.setContents(readFile(":/ModelClassWizard/ItemModelHeader",
                                        replacementMap));
        sourceFile.setContents(readFile(":/ModelClassWizard/ItemModelSource",
                                        replacementMap));
    } else if(params.baseClass == "QAbstractTableModel") {
        headerFile.setContents(readFile(":/ModelClassWizard/TableModelHeader",
                                        replacementMap));
        sourceFile.setContents(readFile(":/ModelClassWizard/TableModelSource",
                                        replacementMap));
    } else if(params.baseClass == "QAbstractListModel") {
        headerFile.setContents(readFile(":/ModelClassWizard/ListModelHeader",
                                        replacementMap));
        sourceFile.setContents(readFile(":/ModelClassWizard/ListModelSource",
                                        replacementMap));
    }
    ret << headerFile << sourceFile;
    return ret;
}

实现插件

同前面一样,我们的插件实现类也只是替换掉initialize()函数的内容:

bool CustomProjectWizardPlugin::initialize(const QStringList& args, QString *errMsg)
{
    Q_UNUSED(args);
    Q_UNUSED(errMsg);

    Core::BaseFileWizardParameters params;
    params.setKind(Core::IWizard::ClassWizard);
    params.setIcon(qApp->windowIcon());
    params.setDescription("Generates an item-model class");
    params.setDisplayName("Item Model");
    params.setCategory("GalaxyWorld");
    params.setDisplayCategory(tr("GalaxyWorld"));

    addAutoReleasedObject(new ModelClassWizard(params, this));

    return true;
}

最后来测试一下我们的插件。

因为我们的向导是文件类型的,所以需要首先打开一个工程:

Open Project for Test

然后在工程名字上面点右键,添加新文件,可以看到我们的插件提供的文件类型:

Add Item Model New File

点击下方的“选择…”按钮,弹出我们设计的用户输入界面,填入 MyItemModel,可以看到下面的 Header 和 Implementation 都会自动修改:

Input Item Model Data

点击“下一步”,看看新的界面。这个页面就是系统默认添加的,还记得我们写的函数中最后那个循环吗?注意默认路径,Qt Creator 已经帮我们写好了:

Add Files to Project

点击完成,就可以看到,我们的文件已经生成好了!

结语

这是我们 《Qt Creator 插件开发》的最后一个章节。从我们以往的内容可以看出,Qt Creator 的插件开发并不是那么高不可攀,唯一的问题在于文档:Qt Creator 插件开发的文档一直写的不清不楚。虽然最新版本已经改善了很多,但是就学习而言,还是远远不够的。希望本文也能起到抛砖引玉的作用,最好能够让大家一起加入到 Qt Creator 的开发活动中来,一起完善这个 IDE。

感谢大家一直以来的支持!

附件下载:ModelClassWizardPlugin 文件

Leave a Reply