QtCreator的核心就是一個插件加載器,其全部功能都是經過插件實現的。
QtCreator架構以下:
QtCreator的核心功能由Core Plugin (Core::ICore)實現。
插件管理器(ExtensionSystem::PluginManager)對插件協做提供了簡單方式,容許插件爲其餘插件擴展提供鉤子。
PluginManager負責插件的加載,管理,銷燬等工做。Core插件是QtCreator最基礎的插件,提供了向界面增長菜單等功能。
QtCreator的核心繫統是由PluginManager和Core插件構成。PluginManager負責插件的管理工做,Core負責提供QtCreator的最小功能集合。PluginManager將Core當作普通插件進行加載。對於自定義插件,Core是一個基礎功能庫,使用Core能夠擴展QtCreator的功能。
QtCreator的全部功能,全是由插件實現的,使用插件機制的優勢是簡化了頂層業務,即插件管理工做的邏輯,缺點是增長了加載插件的複雜度,因爲Core插件須要被其餘插件依賴,因此qtcreator在插件加載時就必需要考慮插件之間的依賴性。git
最基本的插件是一個共享庫,從開發者的角度,插件是一個模塊。
插件模塊的實現須要知足如下功能:
A、在一個類中實現ExtensionSystem::IPlugin接口。
B、使用Q_EXPORT_PLUGIN宏導出插件類。
C、提供一個pluginspec插件描述文件,用於描述插件的元信息。
D、向其它插件暴露一個或多個對象。
E、查找其它插件暴露出來的可用的一個或多個對象。
插件都須要繼承IPlugin的接口,插件是由描述文件和繼承IPlugin的類庫組成。
描述文件內容以下:json
<plugin name="DoNothing" version="1.0.0" compatVersion="2.8.1"> <vendor>Scorpio.org</vendor> <copyright>(C) 2010-2011 Scorpio.org</copyright> <license>Do anything you want.</license> <description>A plugin that does nothing.</description> <url>http://www.scorpio.net</url> <dependencyList> <dependency name="Core" version="2.8.1"/> </dependencyList> </plugin>
插件描述文件描述了插件的基本信息,用於被插件管理器加載。最後一行描述了插件所依賴的其它插件,PluginManager會根據插件之間的依賴關係決定加載順序。
IPlugin是插件的基類接口,主要接口以下:架構
//初始化函數,在插件被加載時會調用 bool IPlugin::initialize(const QStringList &arguments, QString *errorString) //在全部插件的initialize函數被調用後,調用該函數,此時該插件依賴的插件已經初始化完成 void IPlugin::extensionsInitialized() //在全部插件的extensionsInitialized函數調用完成之後進行調用 bool IPlugin::delayedInitialize()
暴露對象是存在於插件管理器對象池中的對象。
插件暴露出的對象會加入到PluginManager的對象池。PluginManager的allObjects()函數用於獲取對象池中全部QObject對象的指針列表。下面的代碼演示瞭如何在QListWidget組件中列出對象池中全部的對象:ide
#include <extensionsystem/pluginmanager.h> ExtensionSystem::PluginManager* pm = ExtensionSystem::PluginManager::instance(); QList<QObject*> objects = pm->allObjects(); QListWidget* listWidget = new QListWidget; Q_FOREACH(QObject* obj, objects) { QString objInfo = QString("%1 (%2)") .arg(obj->objectName()) .arg(obj->metaObject()->className()); listWidget->addItem(objInfo); }
將DoNothing插件中doNothing函數修改以下:函數
#include <extensionsystem/pluginmanager.h> void DoNothingPlugin::doNothing() { ExtensionSystem::PluginManager* pm = ExtensionSystem::PluginManager::instance(); QList<QObject*> objects = pm->allObjects(); QListWidget* listWidget = new QListWidget(); Q_FOREACH(QObject* obj, objects) { QString objInfo = QString(QString::fromUtf8("%1 (%2)")) .arg(obj->objectName()) .arg(QString::fromUtf8(obj->metaObject()->className())); listWidget->addItem(objInfo); } listWidget->resize(300,600); listWidget->show(); }
一個對外暴露的對象是由一個插件對外暴露的QObject(或其子類)的實例,暴露對象存在於對象池中,而且可供其它插件使用。ui
有三種方法從插件中暴露一個對象:
A、IPlugin::addAutoReleasedObject(QObject)
B、IPlugin::addObject(QObject)
C、PluginManager::addObject(QObject)
IPlugin::addObject()和IPlugin::addAutoReleasedObject()其實都是調用的PluginManager::addObject()函數。建議使用IPlugin的函數添加對象。addAutoReleasedObject()和addObject()的惟一區別是,前者添加的對象會在插件銷燬的時候自動按照註冊順序的反向順序從對象池中移除並delete。
在任意時刻,均可以使用IPlugin::removeObject(QObject)函數將對象從對象池中移除。this
插件能夠暴露任何對象。一般,被其它插件使用了某些功能的對象會被暴露。QtCreator中,功能經過接口的方式定義。
下面是其中一些接口:
Core::INavigationWidgetFactory
Core::IEditor
Core::IOptionsPage
Core::IOutputPane
Core::IWizard
若是一個插件包含實現了接口的對象,那麼這個對象就應該被暴露出來。例如,一個插件中的某個類實現了INavigationWidgetFactory接口,而且暴露出來,那麼Core就會自動把這個類提供的組件當作導航組件顯示出來。建立一個導航欄插件TableNav,經過實現 Core::INavigationWidgetFactory接口,將一個簡單的QTableWidget當作導航組件。
Core::INavigationWidgetFactory接口實現以下:
NavWidgetFactory.h文件:url
#ifndef NAVWIDGETFACTORY_H #define NAVWIDGETFACTORY_H #include <coreplugin/inavigationwidgetfactory.h> #include <coreplugin/id.h> using namespace Core; class NavWidgetFactory : public Core::INavigationWidgetFactory { public: NavWidgetFactory(); ~NavWidgetFactory(); Core::NavigationView createWidget(); QString displayName() const; int priority() const; Id id() const; }; #endif // NAVWIDGETFACTORY_H
NavWidgetFactory.cpp文件:spa
#include "NavWidgetFactory.h" #include <QtGui> NavWidgetFactory::NavWidgetFactory() { } NavWidgetFactory::~NavWidgetFactory() { } Core::NavigationView NavWidgetFactory::createWidget() { Core::NavigationView view; view.widget = new QTableWidget(50, 3); return view; } QString NavWidgetFactory::displayName() const { return QString::fromUtf8("TableNav"); } int NavWidgetFactory::priority() const { return 0; } Id NavWidgetFactory::id() const { return Id::fromName("TableNav"); }
TableNav插件實現以下:
TableNavPlugin .h文件:.net
#ifndef TABLENAVPLUGIN_H #define TABLENAVPLUGIN_H #include <extensionsystem/iplugin.h> #include "NavWidgetFactory.h" #include <QtPlugin> #include <QtGui> class TableNavPlugin : public ExtensionSystem::IPlugin { public: TableNavPlugin(); ~TableNavPlugin(); void extensionsInitialized(); bool initialize(const QStringList & arguments, QString * errorString); void shutdown(); }; #endif // TABLENAVPLUGIN_H
TableNavPlugin .cpp文件:
#include "TableNavPlugin.h" #include "NavWidgetFactory.h" #include <QtPlugin> #include <QtGui> TableNavPlugin::TableNavPlugin() { // Do nothing } TableNavPlugin::~TableNavPlugin() { // Do notning } bool TableNavPlugin::initialize(const QStringList& args, QString *errMsg) { Q_UNUSED(args); Q_UNUSED(errMsg); // Provide a navigation widget factory. // Qt Creator’s navigation widget will automatically // hook to our INavigationWidgetFactory implementation, which // is the NavWidgetFactory class, and show the QTableWidget // created by it in the navigation panel. //暴露對象 addAutoReleasedObject(new NavWidgetFactory); return true; } void TableNavPlugin::extensionsInitialized() { // Do nothing } void TableNavPlugin::shutdown() { // Do nothing } Q_EXPORT_PLUGIN(TableNavPlugin)
TableNav插件描述文件以下:
<plugin name="TableNav" version="0.0.1" compatVersion="2.8.1"> <vendor>Scorpio</vendor> <copyright>(C) 2010-2011 Scorpio.org</copyright> <license>MIT</license> <description>Table widget as navigation.</description> <url>http://www.scorpio.net</url> <dependencyList> <dependency name="Core" version="2.8.1"/> </dependencyList> </plugin>
TableNav插件依賴文件以下:
QTC_PLUGIN_NAME = TableNav QTC_PLUGIN_DEPENDS += \ coreplugin
TableNav插件工程文件以下:
EMPLATE = lib TARGET = TableNav include(../../qtcreatorplugin.pri) PROVIDER = Scorpio include(../../plugins/coreplugin/coreplugin.pri) HEADERS += TableNavPlugin.h \ NavWidgetFactory.h SOURCES += TableNavPlugin.cpp \ NavWidgetFactory.cpp OTHER_FILES += TableNav.pluginspec \ TableNav_dependencies.pri
結果以下:
當使用PluginManager::addObject()添加對象時,PluginManager就會發出objectAdded(QObject)信號。應用程序可使用objectAdded(QObject)信號來弄清楚被添加的對象。
只有插件被初始化後,插件管理器纔會發出objectAdded(QObject*)信號。只有被初始化後添加到插件管理器對象池的插件對象,才能收到objectAdded()信號。
一般,鏈接到objectAdded()信號的slot會尋找一個或多個已知接口。假設插件要找的是INavigationWidgetFactory接口,那麼鏈接objectAdded()信號的槽函數以下:
void xxxPlugin::slotObjectAdded(QObject * obj) { INavigationWidgetFactory *factory = Aggregation::query(obj); if(factory) { // use it here... } }
有時,插件須要在應用程序中查找提供了某些功能的對象。目前,已知查找對象的方法有兩種:
A、PluginManager::allObjects()函數返回一個QList<QObject*>形式的對象池。
B、經過鏈接PluginManager::objectAdded()信號,能夠知道被暴露的對象。
假設須要查找一個實現了INavigationWidgetFactory接口的對象,而後把它添加到一個QListWidget中顯示出來。那麼,可使用PluginManager::getObjects<T>()函數。下面是代碼片斷:
ExtensionSystem::PluginManager* pm = ExtensionSystem::PluginManager::instance(); QList<Core::INavigationWidgetFactory*> objects = pm->getObjects<Core::INavigationWidgetFactory>(); QListWidget* listWidget = new QListWidget(); Q_FOREACH(Core::INavigationWidgetFactory* obj, objects) { QString objInfo = QString("%1 (%2)") .arg(obj->displayName()) .arg(obj->metaObject()->className()); listWidget->addItem(objInfo); }
QtCreator的核心繫統由PluginManager和Core插件構成。PluginManager負責插件的管理工做,將Core插件當作普通插件進行加載;Core插件負責提供QtCreator的最小功能集合,爲其它插件提供基礎功能。
QtCreator全部功能由插件實現,優勢是簡化了頂層業務,即插件管理工做的邏輯,只有PlunginManager和Plugin;缺點是增長了加載插件的複雜度,由於Core基礎庫插件須要被其餘插件依賴,因此QtCreator在插件加載時就必需要考慮插件之間的依賴性。
只包括core、Find、Locator、TextEditor四個必須插件的QtCreator界面以下:
C++ 開發者一般會將只包含 public純虛函數的類當作接口。在QtCreator中,接口則是擁有一個或多個純虛函數的QObject子類。若是一個插件實現了IXXX接口的對象,那麼這個對象就應該被暴露出來。例如,一個插件中的某個類實現了INavigationWidgetFactory接口,而且暴露出來,那麼 Core 就會自動把這個類提供的組件當作導航組件顯示出來。
QtCreator中,功能經過接口的方式定義。Core插件模塊定義了QtCreator的經常使用功能接口集合,以下:
Core::IOptionsPage
Core::IWizard
Core::IEditor
Core::IEditorFactory
Core::IDocumentFactory
Core::IExternalEditor
Core::IContext
Core::ICore
Core::ICoreListener
Core::IDocument
Core::IFileWizardExtension
Core::IMode
Core::INavigationWidgetFactory
Core::IOutputPane
Core::IVersionControl
功能接口會在其它插件或Core插件實現,如git插件在GitVersionControl類對Core::IVersionControl接口進行了實現,Core插件在TextDocument類中對IDocument接口進行了實現。
coreplugin.h文件:
#ifndef COREPLUGIN_H #define COREPLUGIN_H #include <extensionsystem/iplugin.h> namespace Core { class DesignMode; namespace Internal { class EditMode; class MainWindow; class CorePlugin : public ExtensionSystem::IPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Core.json") public: CorePlugin(); ~CorePlugin(); //必須實現接口initialize bool initialize(const QStringList &arguments, QString *errorMessage = 0); //必須實現接口extensionsInitialized void extensionsInitialized(); bool delayedInitialize(); ShutdownFlag aboutToShutdown(); QObject *remoteCommand(const QStringList & /* options */, const QStringList &args); public slots: void fileOpenRequest(const QString&); private: void parseArguments(const QStringList & arguments); MainWindow *m_mainWindow;//主窗口 EditMode *m_editMode;//編輯模式 DesignMode *m_designMode;//設計器模式 }; } // namespace Internal } // namespace Core #endif // COREPLUGIN_H
coreplugin.cpp文件:
#include "coreplugin.h" #include "actionmanager.h" #include "designmode.h" #include "editmode.h" #include "editormanager.h" #include "fileiconprovider.h" #include "helpmanager.h" #include "mainwindow.h" #include "mimedatabase.h" #include "modemanager.h" #include "infobar.h" #include <utils/savefile.h> #include <QtPlugin> #include <QDebug> #include <QDateTime> using namespace Core; using namespace Core::Internal; CorePlugin::CorePlugin() : m_mainWindow(new MainWindow), m_editMode(0), m_designMode(0) { } CorePlugin::~CorePlugin() { if (m_editMode) { removeObject(m_editMode); delete m_editMode; } if (m_designMode) { if (m_designMode->designModeIsRequired()) removeObject(m_designMode); delete m_designMode; } // delete FileIconProvider singleton delete FileIconProvider::instance(); delete m_mainWindow; } void CorePlugin::parseArguments(const QStringList &arguments) { for (int i = 0; i < arguments.size(); ++i) { if (arguments.at(i) == QLatin1String("-color")) { const QString colorcode(arguments.at(i + 1)); m_mainWindow->setOverrideColor(QColor(colorcode)); i++; // skip the argument } if (arguments.at(i) == QLatin1String("-presentationMode")) ActionManager::setPresentationModeEnabled(true); } } bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage) { qsrand(QDateTime::currentDateTime().toTime_t()); parseArguments(arguments); const bool success = m_mainWindow->init(errorMessage); if (success) { m_editMode = new EditMode; addObject(m_editMode); //切換到編輯模式 ModeManager::activateMode(m_editMode->id()); m_designMode = new DesignMode; InfoBar::initializeGloballySuppressed(); } // Make sure we respect the process's umask when creating new files Utils::SaveFile::initializeUmask(); return success; } void CorePlugin::extensionsInitialized() { m_mainWindow->mimeDatabase()->syncUserModifiedMimeTypes(); if (m_designMode->designModeIsRequired()) addObject(m_designMode); m_mainWindow->extensionsInitialized(); } bool CorePlugin::delayedInitialize() { HelpManager::instance()->setupHelpManager(); return true; } QObject *CorePlugin::remoteCommand(const QStringList & /* options */, const QStringList &args) { IDocument *res = m_mainWindow->openFiles( args, ICore::OpenFilesFlags(ICore::SwitchMode | ICore::CanContainLineNumbers)); m_mainWindow->raiseWindow(); return res; } void CorePlugin::fileOpenRequest(const QString &f) { remoteCommand(QStringList(), QStringList(f)); } ExtensionSystem::IPlugin::ShutdownFlag CorePlugin::aboutToShutdown() { m_mainWindow->aboutToShutdown(); return SynchronousShutdown; } Q_EXPORT_PLUGIN(CorePlugin)
Core插件對Core::IMode進行了不一樣實現,如EditMode、DesignMode,並在initialize函數加載了相應功能。
在main函數中由ExtensionSystem::PluginManager插件管理器加載。
pluginManager.loadPlugins();
void PluginManager::loadPlugins()函數調用了void PluginManagerPrivate::loadPlugins()函數。
void PluginManagerPrivate::loadPlugins() { //獲取待加載的插件,loadQueue根據插件批次依賴關係進行排序 QList<PluginSpec *> queue = loadQueue(); //加載插件 foreach (PluginSpec *spec, queue) { loadPlugin(spec, PluginSpec::Loaded); } //初始化插件 foreach (PluginSpec *spec, queue) { loadPlugin(spec, PluginSpec::Initialized); } QListIterator<PluginSpec *> it(queue); it.toBack(); while (it.hasPrevious()) { loadPlugin(it.previous(), PluginSpec::Running); } emit q->pluginsChanged(); }
自定義插件使用Core插件提供的功能向界面添加菜單代碼以下:
bool DoNothingPlugin::initialize(const QStringList& args, QString *errMsg) { Q_UNUSED(args); Q_UNUSED(errMsg); Core::ActionManager* am = Core::ICore::instance()->actionManager(); // Create a DoNothing menu Core::ActionContainer* ac = am->createMenu("DoNothingPlugin.DoNothingMenu"); ac->menu()->setTitle(QString::fromUtf8("DoNothing")); // Create a command for "About DoNothing". Core::Command* cmd = am->registerAction( new QAction(this), "DoNothingPlugin.AboutDoNothing", Core::Context(Core::Constants::C_GLOBAL)); cmd->action()->setText(QString::fromUtf8("About DoNothing")); connect(cmd->action(), SIGNAL(triggered(bool)), this, SLOT(doNothing())); // Insert the "DoNothing" menu between "Window" and "Help". QMenu* helpMenu = am->actionContainer(Core::Constants::M_HELP)->menu(); QMenuBar* menuBar = am->actionContainer(Core::Constants::MENU_BAR)->menuBar(); menuBar->insertMenu(helpMenu->menuAction(), ac->menu()); // Add the "About DoNothing" action to the DoNothing menu ac->addAction(cmd); return true; }
DoNothing插件在initialize函數中使用Core插件的Core::ActionManager、Core::ActionContainer、Core::Command功能向主界面菜單欄添加菜單。
Git插件使用Core插件提供的功能向主界面菜單欄的git菜單提供菜單和菜單項,代碼以下:
//register actions Core::ActionContainer *toolsContainer = Core::ActionManager::actionContainer(Core::Constants::M_TOOLS); Core::ActionContainer *gitContainer = Core::ActionManager::createMenu("Git"); gitContainer->menu()->setTitle(tr("&Git")); toolsContainer->addMenu(gitContainer); m_menuAction = gitContainer->menu()->menuAction(); /* "Current File" menu */ Core::ActionContainer *currentFileMenu = Core::ActionManager::createMenu(Core::Id("Git.CurrentFileMenu")); currentFileMenu->menu()->setTitle(tr("Current &File")); gitContainer->addMenu(currentFileMenu);
聚合由Aggregation命名空間提供,提供了一種將不一樣類型的QObject粘合在一塊兒的能力,所以能夠將不一樣類型對象相互轉換。使用Aggregation命名空間中的類和函數,就能夠綁定相關對象到一個單獨實體(聚合)。被綁定到聚合中的對象可以從聚合轉換爲不一樣的對象類類型。
若是想要一個對象提供兩個接口的實現,實現代碼以下:
class Interface1 { .... }; Q_DECLARE_INTERFACE("Interface1", "Interface1"); class Interface2 { .... }; Q_DECLARE_INTERFACE("Interface2", "Interface2"); class Bundle : public QObject, public Interface1, public Interface2 { Q_OBJECT Q_INTERFACES(Interface1 Interface2) .... }; Bundle bundle;
對象bundle同時實現了Interface1和Interface2。可使用類型轉換運算符,將bundle轉換成Interface1或者Interface2:
Interface1* iface1Ptr = qobject_cast<Interface1*>(&bundle); Interface2* iface2Ptr = qobject_cast<Interface2*>(&bundle);
QtCreator的Aggregation庫提供了一種更加簡潔的方式,來定義接口,而後將其打包成一個對象。建立Aggregation::Aggregate實例,而後將對象添加進該對象。加入聚合的每個對象均可以實現一個接口。下面的代碼顯示瞭如何建立聚合。
#include <aggregation/aggregate.h> class Interface1 : public QObject { Q_OBJECT public: Interface1() { } ~Interface1() { } }; class Interface2 : public QObject { Q_OBJECT public: Interface2() { } ~Interface2() { } }; Aggregation::Aggregate bundle; bundle.add(new Interface1); bundle.add(new Interface2);
聚合實例bundle如今有兩個接口的實現。若是須要轉換成相應接口,可使用以下代碼:
Interface1* iface1Ptr = Aggregation::query<Interface1>(&bundle); Interface2* iface2Ptr = Aggregation::query<Interface2>(&bundle);
利用聚合,能夠屢次添加具備相同接口的多個對象。例如:
Aggregation::Aggregate bundle; bundle.add(new Interface1); bundle.add(new Interface2); bundle.add(new Interface1); bundle.add(new Interface1); QList<Interface1*>gt; iface1Ptrs = Aggregation::query_all<Interface1>(&bundle);
使用Aggregation的另外一優勢是,delete聚合中的任一對象,均可以將整個聚合delete掉。例如:
Aggregation::Aggregate* bundle = new Aggregation::Aggregate; bundle->add(new Interface1); bundle->add(new Interface2); Interface1* iface1Ptr = Aggregation::query(bundle); delete iface1Ptr; // 同時會 delete 這個 bundle 及其中全部對象 // 等價於 delete bundle