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 ¶meters, 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 ¶meters, 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 ¶meters, 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; }
最后来测试一下我们的插件。
因为我们的向导是文件类型的,所以需要首先打开一个工程:
然后在工程名字上面点右键,添加新文件,可以看到我们的插件提供的文件类型:
点击下方的“选择...”按钮,弹出我们设计的用户输入界面,填入 MyItemModel,可以看到下面的 Header 和 Implementation 都会自动修改:
点击“下一步”,看看新的界面。这个页面就是系统默认添加的,还记得我们写的函数中最后那个循环吗?注意默认路径,Qt Creator 已经帮我们写好了:
点击完成,就可以看到,我们的文件已经生成好了!
结语
这是我们 《Qt Creator 插件开发》的最后一个章节。从我们以往的内容可以看出,Qt Creator 的插件开发并不是那么高不可攀,唯一的问题在于文档:Qt Creator 插件开发的文档一直写的不清不楚。虽然最新版本已经改善了很多,但是就学习而言,还是远远不够的。希望本文也能起到抛砖引玉的作用,最好能够让大家一起加入到 Qt Creator 的开发活动中来,一起完善这个 IDE。
感谢大家一直以来的支持!
附件下载:ModelClassWizardPlugin 文件