Writing QT Creator Plugins
Writing QT Creator Plugins
Writing QT Creator Plugins
This document explains how to write plugins for Qt Creator. Most of the content of the documentation is derived from Qt Creator source code and documentation, which is ofcourse publicly available. We dont vouch for the accuracy of the content of this document. We __do__ want to tell you that the mechanisms described here worked for us. . The document is a work in progress, we will be updating it from time to time as we learn more about Qt Creator plugin development. Another note before you read ahead: The purpose set out for each plugin is really not production type. We have just chosen a simple purpose for each plugin so that the related concepts can be explained. None of the plugins described in this document may be of any real use to you or anyone else; but the concepts explained will be very useful
Page | 1
Technology Documentation
CONTENTS
1. Compiling Qt Creator .......................................................................................................................................................... 3 2. First Plugin........................................................................................................................................................................... 5 3 Adding Menu and Menu Entries ........................................................................................................................................ 11 4 Architecture of Qt Creator ................................................................................................................................................. 20 5 Adding a new Editor ........................................................................................................................................................... 28 6 Adding a Navigation Sidebar .............................................................................................................................................. 48 7 Adding Page to Preferences Dialog .................................................................................................................................... 55 8 Adding Filter to Find Dialog Box ......................................................................................................................................... 60 9 Add a new Project Type ..................................................................................................................................................... 72
Page | 2
Technology Documentation
1. COMPILING QT CREATOR
It recommended that you build Qt Creator in a separate directory. Step 1: Create build directory inside main source directory
Step 3: Finally run make (or mingw32-make or nmake, depending on your platform).
After successful compilation, you should be able to find qtcreator.exe inside build\bin directory.
Page | 3
Technology Documentation
You can now launch Qt Creator. It is important that you compile Qt Creator from its sources. Otherwise you wont be able to write and test plugins for Qt Creator.
Page | 4
Technology Documentation
2. FIRST PLUGIN
The best way to learn about writing Qt Creator plugins is to actually start by writing the very first plugin. Lets keep our goals very simple for this one. We are going to provide a plugin for Qt Creator that does nothing. The purpose behind this do nothing plugin is to discover the basic classes in Qt Creator and to feel happy when our plugin shows up in the plugin list
Technology Documentation
2. Configures DoNothing to make use of settings defined in qtcreatorplugin.pri 3. Overrides the default destination directory to $$IDE_PLUGIN_PATH/VCreateLogic. By default the value will be to $$IDE_PLUGIN_PATH/Nokia 4. Configures DoNothing to make use of settings defined in coreplugin.pri 5. Provides information about the .h and .cpp files that make up the plugin
As you can see the DoNothingPlugin class implements the IPlugin interface and nothing else. Lets look at how the functions are implemented.
#include "DoNothingPlugin.h" #include <QtPlugin> #include <QStringList> DoNothingPlugin::DoNothingPlugin() { // Do nothing
Page | 6
Technology Documentation
} DoNothingPlugin::~DoNothingPlugin() { // Do notning }
Apart from initializing local (non widget and non action) variables; the constructor and destructor dont do much else.
bool DoNothingPlugin::initialize(const QStringList& args, QString *errMsg) { Q_UNUSED(args); Q_UNUSED(errMsg); return true; }
The initialize() method is called when Qt Creator wants the plugin to initialize itself. This function is ideally used to initialize the internal state of the plugin and register actions/objects with Qt Creator. The function is called after all the dependencies of this plugin have been loaded. Since our plugin really does nothing, we return true signifying that the initialization was successful. If the initialization was unsuccessful (for some wired reason); the errMsg string should be set to a human readable error message.
void DoNothingPlugin::extensionsInitialized() { // Do nothing }
The extensionsInitialized() method is called after this plugin has been initialized (ie. after initialize() method has been called). This method is called on plugins that depend on this plugin first.
void DoNothingPlugin::shutdown() { // Do nothing }
Finally we export the plugin class by making use of the Q_EXPORT_PLUGIN() macro.
Page | 7
Technology Documentation
The pluginspec file provides the following fields of information 1. Name of the plugin, which is also used as the name of the library file that provides the plugin implementation. (In our case DoNothing.dll on Windows, libDoNothing.so on Unix) 2. Version of the plugin 3. Required Qt Creator version 4. Vendor name 5. Copyright 6. License text 7. Description 8. URL of the plugin vendor 9. Dependency List provides all the plugins that this plugin depends on. Qt Creator ensures that dependencies are loaded and initialized before this plugin. Note: The pluginspec file should be in the same directory as the plugins project file. Just to make things clear, the contents of the DoNothing plugin directory is as shown below
Page | 8
Technology Documentation
In the coming chapters we will learn to write more complicated plugins for Qt Creator.
Technology Documentation
cannot expect to have your code in Qt Creator source tree all the time. In this section we will look at how to build plugins that are outside the Qt Creator source tree. 2.6.1 The plugin project file The whole magic of out-of-source plugin builds lies in the project (.pro) file of your plugin. Lets the DoNothing plugin discussed in the previous section and modify (its .pro file) so that plugins can be built from a directory outside Qt Creator source. The following table lists out the directory structure Description Qt Creator Source Code Qt Creator Build Directory DoNothing Plugin Source Directory C:\Work\QtCreator C:\Work\QtCreator\build C:\Work\Research\QtCreator\Plugins\DoNothing This directory currently contains DoNothing.pluginspec DoNothing.pro DoNothingPlugin.cpp DoNothingPlugin.h Target plugin directory C:\Work\QtCreator\build\lib\qtcreator\plugins\VCreateLogic
The QTC_SOURCE and QTC_BUILD variables in the project file point to the source and build directories of Qt Creator. If you prefer setting these as environment variables, then use $$(QTC_BUILD) instead of $$QTC_BUILD in the project file.
Page | 10
Technology Documentation
The IDE_SOURCE_TREE and IDE_BUILD_TREE variables are used by qtcreatorplugin.pri to establish the include and library paths. The PROVIDER and DESTDIR directories must be set before including qtcreatorplugin.pri. This is because the variables will be provided default values are Nokia and $$IDE_BUILD_TREE/lib/qtcreator/plugins/Nokia otherwise. By default qtcreatorplugin.pri assumes that all the libs that a plugin may depend on are present inside the DESTDIR. If our DESTDIR is different from the default (Nokia) one, then we will need to explicitly set that. The remaining things are just the same. 2.6.2 Compiling the plugin Once the project file has been created, we make use of the standard qmake and make commands to compile the plugin.
C:\Work\Research\QtCreator\Plugins\DoNothing>qmake C:\Work\Research\QtCreator\Plugins\DoNothing>nmake Microsoft (R) Program Maintenance Utility Version 8.00.50727.762 Copyright (C) Microsoft Corporation. All rights reserved. "C:\Program Files\Microsoft Visual Studio 8\VC\BIN\nmake.exe" -f Makefile.Debug Microsoft (R) Program Maintenance Utility Version 8.00.50727.762 Copyright (C) Microsoft Corporation. All rights reserved. copy /y "DoNothing.pluginspec" "..\..\..\..\QtCreator\build\lib\qtcreator\plugins\VCreateLogic\DoNothing.pluginspec" 1 file(s) copied. ........................................ mt.exe -nologo -manifest "debug\DoNothingd.intermediate.manifest" outputresource:..\..\..\..\QtCreator\build\lib\qtcreator\plugins\VCreateLog ic\DoNothingd.dll;2 C:\Work\Research\QtCreator\Plugins\DoNothing>
Page | 11
Technology Documentation
The menu bar consists of the following set of default menus File o o o Edit o Advanced Tools Window o Panes Help New Open Recent Files
Note: Other menu items like Build and Debug come from plugins. They are not a part of the default menu set. As Qt developers we know that the above menus are shown within a QMenuBar; and that there is a QMenu associated with each of the above menus.
3.1 Core::ActionManager
The main Qt Creator program is nothing but a plugin loader. All of the functionality provided by Qt Creator is provided by the plugins. The main plugin for Qt Creator is called core. Without core, Qt Creator really doesnt have a personality One of the key components of the core is the ActionManager. ActionManager is responsible for registration of menus, menu-items and keyboard shortcuts. So if we wanted to add a new menu-item or menu, we would have to use ActionManager. The coming subsections explain this better. To gain access to the ActionManager, the following piece of code can be used.
#include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/icore.h> Core::ActionManager* am = Core::ICore::instance()->actionManager();
Page | 12
Technology Documentation
3.2 Core::ActionContainer
ActionContianer represents menu or menubar in Qt Creator. Instances of ActionContainer are never created directly, instead they are accessed using ActionManager::createMenu(), ActionManager::createMenuBar() etc; but more on that later. There is an ActionContainer associated with each of the default menus in Qt Creator. Fetching ActionContainer for a given menu can be done using the following code snippet
#include <coreplugin/coreconstants.h> #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/icore.h> Core::ActionManager* am = Core::ICore::instance()->actionManager(); Core::ActionContainer* ac = am->actionContainer( ID );
The following table lists out the ID to use for each of the menus in Qt Creator. Each of the IDs are defined as const char* static variables within the Core namespace. Menu File File -> New File -> Open File -> Recent Files Edit Edit -> Advanced Tools Window Window Panes Help ID Core::Constants::M_FILE Core::Constants::M_FILE_NEW Core::Constants::M_FILE_OPEN Core::Constants::M_FILE_RECENTFILES Core::Constants::M_EDIT Core::Constants::M_EDIT_ADVANCED Core::Constants::M_TOOLS Core::Constants::M_WINDOW Core::Constants::M_WINDOW_PANES Core::Constants::M_HELP
So if we want to catch hold of the Help menu, we can use the code snippet as follows
#include <coreplugin/coreconstants.h> #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/icore.h> Core::ActionManager* am = Core::ICore::instance()->actionManager(); Core::ActionContainer* ac = am->actionContainer( Core::Constants::M_HELP );
Page | 13
Technology Documentation
Q_UNUSED(errMsg); Core::ActionManager* am = Core::ICore::instance()->actionManager(); Core::ActionContainer* ac = am->actionContainer(Core::Constants::M_HELP); QAction* aboutDoNothing = ac->menu()->addAction("About DoNothing"); return true; }
Thats it; the About DoNothing action has now been added to the Help menu. Just compile the changes in the plugin and launch Qt Creator to notice the new menu-item.
Although adding menu-items this way works, it is not the recommended way to do so. Menu-items in Qt Creator must get listed within in the Keyboard Shortcuts preferences.
By adding menu-items the way we did here, our actions dont get listed in Keyboard Shortcuts. For that to happen, we will have to register menu-items.
Page | 14
Technology Documentation
#include <QKeySequence> bool DoNothingPlugin::initialize(const QStringList& args, QString *errMsg) { Q_UNUSED(args); Q_UNUSED(errMsg); // Fetch the action manager Core::ActionManager* am = Core::ICore::instance()->actionManager(); // Create a command for "About DoNothing". Core::Command* cmd = am->registerAction(new QAction(this), "DoNothingPlugin.AboutDoNothing", QList<int>() << Core::Constants::C_GLOBAL_ID); cmd->action()->setText("About DoNothing"); // Add the command to Help menu am->actionContainer(Core::Constants::M_HELP)->addAction(cmd); return true; }
After compiling the changes, we can notice that the About DoNothing action shows up in the Help menu; but at the beginning.
When added this way, we will be able to find the About DoNothing action in the Keyboard Shortcuts dialog box and also associate a keyboard shortcut with it. Page | 15
Technology Documentation
After compiling the changes and clicking on the About DoNothing menu-item, we can see the information dialog box as shown below.
Page | 16
Technology Documentation
If you wanted the message box to have the Qt Creator main window as its parent, then you can use the following code for the about() slot.
void DoNothingPlugin::about() { QMessageBox::information( Core::ICore::instance()->mainWindow(), "About DoNothing Plugin", "Seriously dude, this plugin does nothing" ); }
Page | 17
Technology Documentation
cmd->action()->setText("About DoNothing"); // Add DoNothing menu to the menubar am->actionContainer(Core::Constants::MENU_BAR)->addMenu(ac); // Add the "About DoNothing" action to the DoNothing menu ac->addAction(cmd); // Connect the action connect(cmd->action(), SIGNAL(triggered(bool)), this, SLOT(about())); return true; }
After recompiling the changes, you will be able to notice the DoNothing menu as shown in the screenshot below.
Page | 18
Technology Documentation
= am->actionContainer(Core::Constants::MENU_BAR)->menuBar(); menuBar->insertMenu(windowMenu->menuAction(), ac->menu()); // Add the "About DoNothing" action to the DoNothing menu ac->addAction(cmd); // Connect the action connect(cmd->action(), SIGNAL(triggered(bool)), this, SLOT(about())); return true; }
After compiling the changes, we can now notice that change in position of the DoNothing menu.
You can use a similar technique for customizing the position of menu-items.
Page | 19
Technology Documentation
4 ARCHITECTURE OF QT CREATOR
Every large system has a well defined system architecture; which if understood well makes it easy for us to find our way in it. Qt Creator is no different. In this chapter we will understand the basic architecture of Qt Creator so that we can continue our understanding of writing plugins.
The core personality of Qt Creator is implemented in the Core Plugin (Core::ICore). We have already had a brush with the core plugin in the previous chapter. In the rest of this document we will refer to core plugin as Core. The plugin manager (ExtensionSystem::PluginManager) provides simple means for plugin cooperation that allow plugins to provide hooks for other plugin's extensions.
Page | 20
Technology Documentation
4.2.1 What are exposed objects? Exposed objects are those that land up in the PluginManagers object pool. The allObjects() method in PluginManager returns the object pool as a list of QObject pointers. Shown below is the code that we can use to list all objects in the object-pool in a QListWidget.
#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); }
When such a list widget is constructed and shown; you will see a window as shown below.
From the class names it is easy to picture the fact that each of those objects came from different plugins. I guess now we can define the term exposed object even better. An exposed object is an instance of QObject (or one of its subclasses) exposed by a plugin and is available in the object-pool for other plugins to make use of. 4.2.2 How to expose an object form a plugin? There are three ways to expose an object from a plugin IPlugin::addAutoReleasedObject(QObject*) IPlugin::addObject(QObject*)
Page | 21
Technology Documentation
PluginManager::addObject(QObject*)
The IPlugin::addObject() and IPlugin::addAutoReleasedObject() essentially call the PluginManager::addObject() method. The IPlugin methods are only provided for convenience. It is recommended that plugins make use of the IPlugin methods for adding objects. The only difference between addAutoReleasedObject() and addObject() is that objects added using the former method are automatically removed and deleted in the reverse order of registration when the plugin is destroyed. At anytime plugins can make use of the IPlugin::removeObject(QObject*) method to remove its object from the object pool. 4.2.3 What objects to expose? Plugins can expose just about any object. Normally objects that provide some sort of functionality used by other plugin(s) are exposed. Functionalities in Qt Creator are defined by means of interfaces. Listed below are some interfaces Core:: INavigationWidgetFactory Core::IEditor Core::IOptionsPage Core::IOutputPane Core::IWizard C++ developers normally assume interfaces to be classes with all its functions are public pure virtual functions. In Qt Creator interfaces are subclasses of QObject that offer one or more methods are pure virtual. If a plugin has objects that implement an interface, then such an object has to be exposed. For example if a plugin implements the INavigationWidgetFactory interface in an object and exposed it, the Core will automatically use that object to show the widget provided by it as navigation widget. Take a look at the code snippet below. We provide a simple QTableWidget as navigation widget via an implementation of Core::INavigationWidgetFactory.
#include <coreplugin/inavigationwidgetfactory.h> class NavWidgetFactory : public Core::INavigationWidgetFactory { public: NavWidgetFactory(); ~NavWidgetFactory(); Core::NavigationView createWidget(); QString displayName(); }; #include <QTableWidget> NavWidgetFactory::NavWidgetFactory() { } NavWidgetFactory::~NavWidgetFactory() { }
Page | 22
Technology Documentation
Core::NavigationView NavWidgetFactory::createWidget() { Core::NavigationView view; view.widget = new QTableWidget(50, 3); return view; } QString NavWidgetFactory::displayName() { return "Spreadsheet"; } bool MyPlugin::initialize(const QStringList& args, QString *errMsg) { Q_UNUSED(args); Q_UNUSED(errMsg); // Provide a navigation widget factory. // Qt Creators 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; }
4.2.4 Becoming aware of exposed objects Whenever the PluginManager::addObject() is used to add an object, it (PluginManager) emits the objectAdded(QObject*) signal. This signal can be used within our applications to figure out the objects that got added.
Page | 23
Technology Documentation
Obviously a plugin will begin receiving the signal only after it makes a connection to it. That happens only after the plugin is initialized; which also means that the plugin will receive the objectAdded() signal only for objects added after the plugin was initialized. Usually the slot that is connected to the objectAdded() signal will look for one or more known interfaces. Suppose that your plugin is looking for the INavigationWidgetFactory interface, the slot connected to objectAdded() will be like the one shown below.
void Plugin::slotObjectAdded(QObject * obj) { INavigationWidgetFactory *factory = Aggregation::query<INavigationWidgetFactory>(obj); if(factory) { // use it here... } }
4.2.4 Searching for objects Sometimes a plugin might want to search for an object in the application that offers some functionality. We already know by now that PluginManager::allObjects() returns the object pool as a QList<QObject*> Connecting to PluginManager::objectAdded() signal helps in known objects as they get exposed
Using both of the above mentioned methods you can look for objects. Lets now understand yet another way to find objects. Suppose that you wanted to look for objects that implement the INavigationWidgetFactory interface and show it in a QListWidget. You can make use of the PluginManager::getObjects<T>() method for this purpose. The following code snippet explains this
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); }
When the list widget is shown you will notice that the navigation widgets are shown in the same order as they are shown in the navigation combo box. Take a look at the screenshot below. Page | 24
Technology Documentation
4.3 Aggregations
Aggregations are provided by the Aggregation namespace. It adds functionality for "gluing" QObjects of different types together, so you can "cast" between them. Using the classes and methods in this namespace you can bundle related objects into a single entity. Objects that are bundled into an aggregate can be cast from the aggregate into the object class type. 4.3.1 Aggregations - the old fashioned way Suppose that you wanted an object that provided implementations of two interfaces. Normally we would go about coding the object like this
class Interface1 { .... }; Q_DECLARE_INTERFACE("Interface1", "Interface1"); class Interface2 { .... }; Q_DECLARE_INTERFACE("Interface2", "Interface2"); class Bundle : public QObject, public Interface1, public Interface2 { Q_OBJECT(Interface1 Interface2) .... }; Bundle bundle;
Now we can think of bundle as an object that provides Interface1 and Interface2 implementations. We can make use of casting operators on the bundle object to extract Interface1 and Interface2.
Interface1* iface1Ptr = qobject_cast<Interface1*>(&bundle); Interface2* iface2Ptr = qobject_cast<Interface2*>(&bundle);
Page | 25
Technology Documentation
4.3.2 Aggregations - the Qt Creator way Qt Creators Aggregation library offers a cleaner way to define interfaces and bundle them into a single object. Instances of Aggregation::Aggregate can be created and objects can be added to it. Each of the objects added to the aggregation can implement an interface. The following code snippet shows how to create an aggregation.
#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);
The aggregation instance bundle now conceptually contains implementations of two interfaces. To extract the interfaces we can make use of the following code
Interface1* iface1Ptr = Aggregation::query<Interface1>( &bundle ); Interface2* iface2Ptr = Aggregation::query<Interface2>( &bundle );
With aggregation you can also several objects of the same interface into a single bundle. For example
Aggregation::Aggregate bundle; bundle.add(new Interface1); bundle.add(new Interface2); bundle.add(new Interface1); bundle.add(new Interface1); QList<Interface1*> iface1Ptrs = Aggregation::query_all<Interface1>( &bundle );
Another key advantage of Aggregation is that, you can delete any one of the objects in the bundle to delete the whole bundle. Example
Aggregation::Aggregate* bundle = new Aggregation::Aggregate; bundle->add(new Interface1); bundle->add(new Interface2); Interface1* iface1Ptr = Aggregation::query<Interface1>(bundle);
Page | 26
Technology Documentation
delete iface1Ptr; // deletes the bundle and all objects in it // same as delete bundle
The use of aggregation will become clearer when we deal with real plugin examples in the coming chapters.
Page | 27
Technology Documentation
In this chapter we will understand how to provide editors for custom file formats, specifically the HTML file format. When we are done, we will be able to load HTML files from the local file system and view/edit them.
Page | 28
Technology Documentation
Implement the editor factory, Core::IEditorFactory, interface. This interface implementation provides methods to help create instances of editor object for a specified mime-type. Implement the editor, Core::IEditor, interface. This interface implementation provides a widget that helps edit a file type (for example: HTML, ODF etc). Editors must provide access to the file that it is currently being shown or edited. Implement the file, Core::IFile, interface to help customize the loading and saving of data into disk files.
In the following subsections, we will take a look at each of the above mentioned core interfaces. 5.1.1 The Core::IFile interface This interface abstracts file operations from the user-interface point of view. It provides virtual methods to load and save files, given a file name. It also helps in understanding the mime-type of file and value of certain flags like modified and read-only. The Core::IFile interface is declared as follows in src/plugins/coreplugin/ifile.h
namespace Core { class IFile : public QObject { Q_OBJECT public: enum ReloadBehavior { AskForReload, ReloadAll, ReloadPermissions, ReloadNone }; IFile(QObject *parent = 0) : QObject(parent) {} virtual ~IFile() {} virtual bool save(const QString &fileName = QString()) = 0; virtual QString fileName() const = 0; virtual QString defaultPath() const = 0; virtual QString suggestedFileName() const = 0; virtual QString mimeType() const = 0; virtual bool isModified() const = 0; virtual bool isReadOnly() const = 0; virtual bool isSaveAsAllowed() const = 0; virtual void modified(ReloadBehavior *behavior) = 0; virtual void checkPermissions() {} signals: void changed(); }; } // namespace Core
You may be wondering: Why go for another interface called IFile when we already have a class called QFile that provides the exact same functionality? Good point. The responses to this question are listed below IFile has to take care of loading contents of a filename into an editor (Core::IEditor interface discussed next). QFile on the other hand simply loads contents into a QByteArray. Page | 29
Technology Documentation
IFile has to emit the modified() signal when the user edits the contents of the file in the editor, but the actual disk-file contents have not been modified. QFile emits the bytesWritten() signal only when the disk-file contents have been modified. IFile has to handle how a modified file, on the disk, is reloaded. QFile on the other-hand doesnt need to handle this.
We will learn more about implementing the Core::IFile interface in a future section. 5.1.2 The Core::IEditor interface Implementations of the Core::IEditor interface provide editors for different types of files. It is declared as follows in src/plugins/coreplugin/editormanager/ieditor.h.
namespace Core { class IContext : public QObject { Q_OBJECT public: IContext(QObject *parent = 0) : QObject(parent) {} virtual ~IContext() {} virtual QList<int> context() const = 0; virtual QWidget *widget() = 0; virtual QString contextHelpId() const { return QString(); } }; class IEditor : public IContext { Q_OBJECT public: IEditor(QObject *parent = 0) : IContext(parent) {} virtual ~IEditor() {} virtual virtual virtual virtual virtual virtual bool createNew(const QString &contents = QString()) = 0; bool open(const QString &fileName = QString()) = 0; IFile *file() = 0; const char *kind() const = 0; QString displayName() const = 0; void setDisplayName(const QString &title) = 0;
virtual bool duplicateSupported() const = 0; virtual IEditor *duplicate(QWidget *parent) = 0; virtual QByteArray saveState() const = 0; virtual bool restoreState(const QByteArray &state) = 0; virtual int currentLine() const { return 0; } virtual int currentColumn() const { return 0; } virtual bool isTemporary() const = 0;
Page | 30
Technology Documentation
The Core::IEditor interface primary provides access to An editor widget (Core::IEditor::widget() method) that Qt Creator can use to display contents of the file being edited. The file (Core::IEditor::file() method), which is a Core::IFile implementation, that Qt Creator can use to trigger the loading and saving of data from disk-files. An optional custom toolbar that Qt Creator can show whenever the editor becomes active. The current position of the edit-cursor within the file (Core::IEditor::currentLine() and Core::IEditor::currentColumn()) The name that needs to be displayed in the open-files combo box.
We will understand more about implementing the Core::IEditor interface in a future section. 5.1.3 The Core::IEditorFactory interface Implementations of Core::IEditorFactory interface provide methods to create instances of Core::IEditor for a supported mime-type. It is declared as follows in src/plugins/coreplugin/editormanager/ieditorfactory.h.
namespace Core {
Page | 31
Technology Documentation
class IFileFactory : public QObject { Q_OBJECT public: IFileFactory(QObject *parent = 0) : QObject(parent) {} virtual ~IFileFactory() {} virtual QStringList mimeTypes() const = 0; virtual QString kind() const = 0; virtual Core::IFile *open(const QString &fileName) = 0; }; class IEditorFactory : public Core::IFileFactory { Q_OBJECT public: IEditorFactory(QObject *parent = 0) : IFileFactory(parent) {} virtual ~IEditorFactory() {} virtual IEditor *createEditor(QWidget *parent) = 0; }; } // namespace Core
The IEditorFactory::mimeType() method should be implemented to return the mime-type supported by the editor for which the factory is implemented. The IEditorFactory::createEditor() method should be implemented to actually create a concrete editor and return the same. 5.1.4 The Core::MimeDatabase class The Core::MimeDatabase class keeps track of all the mime-types supported by Qt Creator. It also helps figure out the mime-type of a given file. Take the following code for example:
#include <coreplugin/mimedatabase.h> Core::ICore* core = Core::ICore::instance(); Core::MimeDatabase* mdb = core->mimeDatabase(); Core::MimeType type1 = mdb->findByFile( QFileInfo("C:/Temp/sample.html") ); qDebug("File Type for sample.html = %s", qPrintable(type1.type())); Core::MimeType type2 = mdb->findByFile( QFileInfo("C:/Temp/TextEdit/Main.cpp") ); qDebug("File Type for Main.cpp = %s", qPrintable(type2.type())); Core::MimeType type3 = mdb->findByFile( QFileInfo("C:/Temp/TextEdit/TextEdit.pro") ); qDebug("File Type for TextEdit.pro = %s", qPrintable(type3.type()));
When the above code is compiled and executed, we get the following as output. File Type for sample.html = text/plain File Type for Main.cpp = text/x-c++src File Type for TextEdit.pro = text/plain
Page | 32
Technology Documentation
The Core::MimeDatabase uses filename suffix, glob patterns and magic matchers to figure out the mime-type of a given filename. At this point however, lets not dig into how the mime-database manages to figure out the mime-type from a filename; it is enough if we know that mime-type discovery is possible. The Core::IEditorFactory interface, as described in the previous section, provides an editor (Core::IEditor implementation) for a specific mime-type. The following points help us understand how Qt Creator manages to pick the appropriate Core::IEditorFactory for a given filename. 1. User selects File -> Open and chooses a file to open 2. Qt Creator uses Core::MimeDatabase to figure out the mime-type of the selected file 3. Qt Creator runs through all Core::IEditorFactory implementations and picks the editor-factory that supports the mime-type evaluated in step 2 4. Qt Creator asks the selected editor factory to create an editor (Core::IEditor implementation) 5. The widget returned by Core::IEditor::widget() is then shown in the workspace area of the mainwindow 6. The Core::IEditor::open() method is then called to open the file selected in step 1. 5.1.5 Adding a new mime-type If we wanted to support a new editor type, then we need to register the mime-type supported by the new editor with the Core::MimeDatabase. Several mechanisms can be used to register a new mime-type. In this chapter we will learn the simplest way to register a new mime-type from an XML file. Suppose that we wanted to register the text/html mime-type and associate it with *.html filenames. We create an XML file as and save it as text-html-mimetype.xml.
<?xml version="1.0" encoding="UTF-8"?> <mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'> <mime-type type="text/html"> <sub-class-of type="text/plain"/> <comment>HTML File</comment> <glob pattern="*.html"/> </mime-type> </mime-info>
We then register the mime-type described in the XML file (above) Core::MimeDatabase::addMimeTypes() method. Take a look at the code snippet below
Core::ICore* core = Core::ICore::instance(); Core::MimeDatabase* mdb = core->mimeDatabase(); QString errMsg; bool success = mdb->addMimeTypes("text-html-mimetype.xml", errMsg);
using
the
Once registered, Qt Creator will begin to map all *.html filenames to text/plain mime-type.
Technology Documentation
The user select a HTML file to open using the standard File -> Open menuitem.
Upon selecting a HTML file, Qt Creator will show a custom editor as shown below.
Notice that the editor has two tabs called Preview and Source at the bottom edge.
Clicking on the Source tab shows the HTML code in a QPlainTextEdit widget.
Page | 34
Technology Documentation
Users can edit the HTML in this tab, and when he moves back to the preview tab the changes are reflected. Users can continue to make use of File -> Save menu item to save changes to the HTML file. For achieving the above we implement the following classes Class HtmlEditorWidget Interface / Baseclass QTabWidget Description Provides a tab widget with two tabs, one for showing the HTML preview and other for showing the HTML code. Implements the IFile interface for the file shown in HtmlEditorWidget. Implements the IEditor interface for managing the HtmlEditorWidget and hooking up an IFile with it. Implements the IEditorFactory interface for creating instances of IEditor for the text/html mime-type. Implements the IPlugin interface for hooking all of the above into Qt Creator.
HtmlFile
Core::IFile
HtmlEditor
Core::IEditor
HtmlEditorFactory
Core::IEditorFactory
HtmlEditorPlugin
Core::IPlugin
For every html file there is an instance each of HtmlEditorWidget, HtmlFile and HtmlEditor handling the loading, editing and saving functionality. In our implementation we provide mechanisms to help establish association between instances of the above classes that handle the same file.
Page | 35
Technology Documentation
5.2.1 Implementing the HTML Editor Widget By default, Qt Creator uses a plain-text editor widget to display the contents of the HTML file being edited. We would like to offer a tabbed widget as editor. One of the tabs shows the HTML preview, the other shows the HTML code. The class HtmlEditorWidget is our editor; and it is declared as follows.
struct HtmlEditorWidgetData; class HtmlEditorWidget : public QTabWidget { Q_OBJECT public: HtmlEditorWidget(QWidget* parent = 0); ~HtmlEditorWidget(); void setContent(const QByteArray& ba, const QString& path=QString()); QByteArray content() const; QString title() const; protected slots: void slotCurrentTabChanged(int tab); void slotContentModified(); signals: void contentModified(); void titleChanged(const QString&); private: HtmlEditorWidgetData* d; };
The constructor basically creates two different views from QWebView and QPlainTextEdit and adds them as tabs at the bottom position of the tab widget. We will learn about the signal-slot connections later.
HtmlEditorWidget::HtmlEditorWidget(QWidget* parent) :QTabWidget(parent) { d = new HtmlEditorWidgetData; d->webView = new QWebView; d->textEdit = new QPlainTextEdit; addTab(d->webView, "Preview"); addTab(d->textEdit, "Source"); setTabPosition(QTabWidget::South); setTabShape(QTabWidget::Triangular); d->textEdit->setFont( QFont("Courier", 12) ); connect(this, SIGNAL(currentChanged(int)), this, SLOT(slotCurrentTabChanged(int))); connect(d->textEdit, SIGNAL(textChanged()), this, SLOT(slotContentModified())); connect(d->webView, SIGNAL(titleChanged(QString)),
Page | 36
Technology Documentation
The destructor does nothing other than deleting the private d object.
HtmlEditorWidget::~HtmlEditorWidget() { delete d; }
The setContent method simply sets the contents of html file to webView and textEdit.
void HtmlEditorWidget::setContent(const QByteArray& ba, const QString& path) { if(path.isEmpty()) d->webView->setHtml(ba); else d->webView->setHtml(ba, "file:///" + path); d->textEdit->setPlainText(ba); d->modified = false; d->path = path; }
The title() method returns the title from the webView. The string returned from this method will be used in the open-file combo box.
The following connection made in the constructor of HtmlEditorWidget makes sure that when the user moves from source to preview tab, the HTML content viewed in the preview tab is updated.
Page | 37
Technology Documentation
Following connection makes sure that setContentModified() slot is called whenever user edits the html source. The slot setContentModified() simply sets modified to true and emits the signal contentModified. We will know the usability of this signal later in the section while understanding HtmlFile class.
connect(d->textEdit, SIGNAL(textChanged()), this, SLOT(slotContentModified())); void HtmlEditorWidget::slotContentModified() { d->modified = true; emit contentModified(); }
Following connection simply emits titleChanged() signal on title change of webView. We will know more about this signal later.
connect(d->webView, SIGNAL(titleChanged(QString)), this, SIGNAL(titleChanged(QString)));
5.2.2 Implementing the Core::IFile interface We implement the Core::IFile interface in the HtmlFile class. Apart from implementing the pure virtual functions from IFile (introduced in section 5.1.1); the HtmlFile class has methods to get/set the modified flag which indicates the modification status of the file contents.
struct HtmlFileData; class HtmlFile : public Core::IFile { Q_OBJECT public: HtmlFile(HtmlEditor* editor, HtmlEditorWidget* editorWidget); ~HtmlFile(); void setModified(bool val=true); // Declare all the virtual functions from IFile here.. protected slots: void modified() { setModified(true); } private: HtmlFileData* d; }; struct HtmlFileData { HtmlFileData()
Page | 38
Technology Documentation
: mimeType(HtmlEditorConstants::C_HTMLEDITOR_MIMETYPE), editorWidget(0), editor(0), modified(false) { } const QString mimeType; HtmlEditorWidget* editorWidget; HtmlEditor* editor; QString fileName; bool modified; };
In the constructor implementation is simple. It establishes the association between an instance of HtmlFile with the corresponding HtmlEditor and the HtmlEditorWidget instances.
HtmlFile::HtmlFile(HtmlEditor* editor, HtmlEditorWidget* editorWidget) : Core::IFile(editor) { d = new HtmlFileData; d->editor = editor; d->editorWidget = editorWidget; }
The destructor does nothing other than deleting the private d object.
HtmlFile::~HtmlFile() { delete d; }
The setModified() function stores the modification flag and emits the changed() signal.
void HtmlFile::setModified(bool val) { if(d->modified == val) return; d->modified = val; emit changed(); } bool HtmlFile::isModified() const { return d->modified; }
The save method is called when file->save action (Ctrl+s) is triggered. This saves the contents of HtmlEditorWidget (the contents shown by plain text editor) in the file as shown below. The modified flag is set to false after the contents are saved into the file. Page | 39
Technology Documentation
bool HtmlFile::save(const QString &fileName) { QFile file(fileName); if(file.open(QFile::WriteOnly)) { d->fileName = fileName; QByteArray content = d->editorWidget->content(); file.write(content); setModified(false); return true; } return false; }
The open method is called when file->open action is triggered. This opens the file and calls the setContent() method of HtmlEditorWidget. The display name is set to the title of the html file.
bool HtmlFile::open(const QString &fileName) { QFile file(fileName); if(file.open(QFile::ReadOnly)) { d->fileName = fileName; QString path = QFileInfo(fileName).absolutePath(); d->editorWidget->setContent(file.readAll(), path); d->editor->setDisplayName(d->editorWidget->title()); return true; } return false; }
We implement the defaultPath(), suggestedFileName(), fileFilter() and fileExtension() methods to do nothing at the moment. Page | 40
Technology Documentation
QString HtmlFile::defaultPath() const { return QString(); } QString HtmlFile::suggestedFileName() const { return QString(); } QString HtmlFile::fileFilter() const { return QString(); } QString HtmlFile::fileExtension() const { return QString(); }
Since we want to edit the file, we return false in isReadOnly() method and true from isSaveAsAllowed() method.
bool HtmlFile::isReadOnly() const { return false; } bool HtmlFile::isSaveAsAllowed() const { return true; }
The modified() function has to be implemented to customize the way in which the Html editor should handle reloading of a modified file. This function is called if a html-file was modified outside of Qt Creator, while it is being edited. For now we do nothing.
void HtmlFile::modified(ReloadBehavior* behavior) { Q_UNUSED(behavior); }
5.2.3 Implementing the Core::IEditor interface The HtmlEditor class implements IEditor interface to provide an editor widget for html (*.html) files and associate a HtmlFile instance with it.
#include <coreplugin/editormanager/ieditor.h> struct HtmlEditorData; class HtmlEditor : public Core::IEditor { Q_OBJECT public:
Page | 41
Technology Documentation
HtmlEditor(HtmlEditorWidget* editorWidget); ~HtmlEditor(); bool createNew(const QString& /*contents*/ = QString()); QString displayName() const; IEditor* duplicate(QWidget* /*parent*/); bool duplicateSupported() const; Core::IFile* file(); bool isTemporary() const; const char* kind() const; bool open(const QString& fileName = QString()) ; bool restoreState(const QByteArray& /*state*/); QByteArray saveState() const; void setDisplayName(const QString &title); QToolBar* toolBar(); // From Core::IContext QWidget* widget(); QList<int> context() const; protected slots: void slotTitleChanged(const QString& title) { setDisplayName(title); } private: HtmlEditorData* d; };
HtmlEditorData holds object of HtmlEditorWidget and HtmlFile. displayName is used as visual description of the document.
struct HtmlEditorData { HtmlEditorData() : editorWidget(0), file(0) { } HtmlEditorWidget* editorWidget; QString displayName; HtmlFile* file; QList<int> context; };
The constructor creates initializes itself on an HtmlEditorWidget. It creates an HtmlFile instance so that its association with HtmlEditor and widget is set.
HtmlEditor::HtmlEditor(HtmlEditorWidget* editorWidget) : Core::IEditor(editorWidget) { d = new HtmlEditorData; d->editorWidget = editorWidget; d->file = new HtmlFile(this, editorWidget); Core::UniqueIDManager* uidm = Core::UniqueIDManager::instance(); d->context << uidm->uniqueIdentifier(HtmlEditorConstants::C_HTMLEDITOR); connect(d->editorWidget, SIGNAL(contentModified()),
Page | 42
Technology Documentation
d->file, SLOT(modified())); connect(d->editorWidget, SIGNAL(titleChanged(QString)), this, SLOT(slotTitleChanged(QString))); connect(d->editorWidget, SIGNAL(contentModified()), this, SIGNAL(changed())); }
The destructor does nothing other than deleting the private d object.
HtmlEditor::~HtmlEditor() { delete d; }
The createNew() method is implemented to reset the contents of the HtmlEditorWidget and HtmlFile objects. For now we ignore the contents parameter.
bool HtmlEditor::createNew(const QString& contents) { Q_UNUSED(contents); d->editorWidget->setContent(QByteArray()); d->file->setFilename(QString()); return true; }
The open() method causes the HtmlFile to open a given filename. It is assumed that the filename is a HTML filename.
bool HtmlEditor::open(const QString &fileName) { return d->file->open(fileName); }
Page | 43
Technology Documentation
{ const char* const C_HTMLEDITOR_MIMETYPE = "text/html"; const char* const C_HTMLEDITOR = "HTML Editor"; } const char* HtmlEditor::kind() const { return HtmlEditorConstants::C_HTMLEDITOR; }
The string returned by displayName is used in the open-file combobox. The following methods help set and fetch the display name.
QString HtmlEditor::displayName() const { return d->displayName; } void HtmlEditor::setDisplayName(const QString& title) { if(d->displayName == title) return; d->displayName = title; emit changed(); }
Page | 44
Technology Documentation
5.2.4 Implementing the Core::IEditorFactory interface The HtmlEditorFactory class implements the Core::IEditorFactory interface; and is declared as follows.
#include <coreplugin/editormanager/ieditorfactory.h> struct HtmlEditorFactoryData; class HtmlEditorFactory : public Core::IEditorFactory { Q_OBJECT public: HtmlEditorFactory(HtmlEditorPlugin* owner); ~HtmlEditorFactory(); QStringList mimeTypes() const; QString kind() const; Core::IEditor* createEditor(QWidget* parent); Core::IFile* open(const QString &fileName); private: HtmlEditorFactoryData* d; };
HtmlEditorFactoryData structure holds the private data of the HtmlEditorFactory class. Notice that the constructor initializes the mime-types to HtmlEditorConstants::C_HTMLEDITOR_MYMETYPE. It also initializes the kind of the editor. This kind should be same as kind of HtmlEditor.
namespace HtmlEditorConstants { const char* const C_HTMLEDITOR_MIMETYPE = "text/html"; const char* const C_HTMLEDITOR = "HTML Editor"; } struct HtmlEditorFactoryData { HtmlEditorFactoryData() : kind(HtmlEditorConstants::C_HTMLEDITOR) { mimeTypes << QString(HtmlEditorConstants::C_HTMLEDITOR_MIMETYPE); } QString kind; QStringList mimeTypes; };
Page | 45
Technology Documentation
The open() method should be implemented to return the IFile that is currently handling the given filename. If there are none, then a new editor for the file is created and its IFile is returned. To fully understand this process take a look at the implementation of Core::EditorManager::openEditor() method.
Core::IFile* HtmlEditorFactory::open(const QString& fileName) { Core::EditorManager* em = Core::EditorManager::instance(); Core::IEditor* iface = em->openEditor(fileName, d->kind); return iface ? iface->file() : 0; }
5.2.5 Implementing the plugin We implement the HtmlEditorPlugin plugin class using the same means described in Chapter 2. The only change is the initialize() method implementation.
bool HtmlEditorPlugin::initialize(const QStringList &arguments, QString* errMsg) { Q_UNUSED(arguments); Core::ICore* core = Core::ICore::instance(); Core::MimeDatabase* mdb = core->mimeDatabase();
Page | 46
Technology Documentation
When the plugin is compiled and Qt Creator is (re)started; we will be able to load HTML files using the newly implemented editor plugin.
Page | 47
Technology Documentation
Qt Creator makes it possible for us to divide the navigation panel into windows and view more than one side bar at the same time. Take a look at the screenshot below.
In this chapter we will understand how to add a new side bar to Qt Creator.
Page | 48
Technology Documentation
{ Q_OBJECT public: INavigationWidgetFactory(); virtual ~INavigationWidgetFactory(); virtual virtual virtual virtual virtual }; QString displayName() = 0; QKeySequence activationSequence(); NavigationView createWidget() = 0; void saveSettings(int position, QWidget *widget); void restoreSettings(int position, QWidget *widget);
Plugins that provide a navigation siderbar (or widget) must implement this interface. In addition to implementing the interface, the plugin has to use expose an instance of that interface using methods described in section 4.2.2.
When the user enters the URL of a FTP directory in the FTP Path line edit and clicks Go, the contents of the FTP directory are shown in the QTreeView below. For the sake of this example we call the widget as FtpExplorerSideBar.
Page | 49
Technology Documentation
Step 2: Implementing the FtpExplorerSideBar widget The FtpExplorerSideBar class definition is as follows
#include <QWidget> struct FtpExplorerSideBarData; class FtpExplorerSideBar : public QWidget { Q_OBJECT public: FtpExplorerSideBar(QWidget* parent=0); ~FtpExplorerSideBar(); void setUrl(const QUrl& url); QUrl url() const; protected slots: void on_cmdGo_clicked(); private: FtpExplorerSideBarData* d; };
The UI design is done in Qt Designer, so we only leave open a slot for responding to the clicked() signal from the cmdGo push button. The implementation of FtpExplorerSideBar is as follows
#include "FtpExplorerSideBar.h" #include "ui_FtpExplorerSideBar.h" #include "FtpDirModel.h" struct FtpExplorerSideBarData { FtpDirModel* model; Ui::FtpExplorerSideBar ui; };
We make use of the FtpDirModel class from VCL for serving contents of a FTP directory via an QAbstractItemModel interface. Visit http://www.vcreatelogic.com/p/2009/07/ftpdirmodel-aqabstractitemmodel-implementation-for-browsing-ftp-directories/ to download a copy of the FtpDirModel code for your use. The constructor basically initializes the UI and creates the model-view association.
FtpExplorerSideBar::FtpExplorerSideBar(QWidget* parent) :QWidget(parent) { d = new FtpExplorerSideBarData;
Page | 50
Technology Documentation
The destructor does nothing other than deleting the private d object.
FtpExplorerSideBar::~FtpExplorerSideBar() { delete d; }
The setter and getter methods for URL basically transfer the URL to the model.
void FtpExplorerSideBar::setUrl(const QUrl& url) { d->model->setUrl(url); } QUrl FtpExplorerSideBar::url() const { return d->model->url(); }
When the Go button is clicked, the on_cmdGo_clicked() slot is called. We basically set the URL for the FtpDirModel to fetch and have the view update itself.
void FtpExplorerSideBar::on_cmdGo_clicked() { QUrl url(d->ui.txtFtpPath->text()); d->model->setUrl(url); }
With this the FtpExplorerSideBar widget is ready. Step 3: Implementing the INavigationWidgetFactory interface We implement the INavigationWidgetFactory interface in a class whose definition is as follows
#include <coreplugin/inavigationwidgetfactory.h> class FtpViewNavigationWidgetFactory : public Core::INavigationWidgetFactory { public: FtpViewNavigationWidgetFactory() { } ~FtpViewNavigationWidgetFactory() { } Core::NavigationView createWidget(); QString displayName(); };
The createWidget() method is implemented to return an instance of the FtpExplorerSideBar widget that was explained in the previous step. Page | 51
Technology Documentation
The displayName() method is implemented to return a descriptive name that Qt Creator should use for showing the side-bar.
QString FtpViewNavigationWidgetFactory::displayName() { return "Ftp View"; }
With this the INavigationWidgetFactory implementation is ready. Step 4: Implementing the ftp-view plugin We implement the ftp-view plugin class similar to the DoNothingPlugin class described in Chapter 2. Hence, we only describe the implementation of the initialize method of the FtpViewPlugin class here.
bool FtpViewPlugin::initialize(const QStringList& args, QString *errMsg) { Q_UNUSED(args); Q_UNUSED(errMsg); addAutoReleasedObject(new FtpViewNavigationWidgetFactory); return true; }
In the initialize() method, an instance of the INavigationWidgetFactory implementation is created and added to the object pool. Once the object is added to the pool, ExtensionSystem::PluginManager emits the objectAdded() signal, which is then trapped by the Core of Qt Creator. The Core then makes use of our implementation of INavigationWidgetFactory interface and places an instance of FtpExplorerSideBar in the navigation panel. Step 5: Testing the plugin Upon compiling the plugin and restarting Qt Creator, we can notice the Ftp View side bar as shown below.
Page | 52
Technology Documentation
The saveSettings() method can be re-implemented to save the settings of the widget (whose pointer is provided as second parameter) into the QSettings returned by Core::ICore:: settings(). The restoreSettings() method can be re-implemented to restore the settings of the widget.
Shown below is a sample implementation of the above methods for the FtpViewNavigationWidgetFactory class.
void FtpViewNavigationWidgetFactory::saveSettings (int position, QWidget *widget); { FtpExplorerSideBar* ftpExp = qobject_cast<FtpExplorerSideBar*>(widget); if(!ftpExp) return;
Page | 53
Technology Documentation
QSettings *settings = Core::ICore::instance()->settings(); settings->setValue("FtpView.URL", ftpExp->url().toString()); } void FtpViewNavigationWidgetFactory::restoreSettings (int position, QWidget *widget) { FtpExplorerSideBar* ftpExp = qobject_cast<FtpExplorerSideBar*>(widget); if(!ftpExp) return; QSettings *settings = Core::ICore::instance()->settings(); QString urlStr = settings->value("FtpView.URL").toString(); ftpExp->setUrl( QUrl(urlStr) ); }
By implementing the saveSettings() and restoreSettings() method, we can ensure that the FTP path browsed in the last session is reopened in the new session.
Page | 54
Technology Documentation
Each plugin provides one or more options pages that get shown in the preferences dialog. In the following sub-sections we will learn how to add our own pages to the dialog.
7. 1 Core::IOptionsPage interface
The Core of Qt Creator exposes an interface called Core::IOptionsPage. The interface is defined in plugins/coreplugin/dialogs/ioptionspage.h.
class CORE_EXPORT IOptionsPage : public QObject { Q_OBJECT public: IOptionsPage( *parent = 0) : QObject(parent) {} virtual ~IOptionsPage() {} virtual virtual virtual virtual QString QString QString QString id() const = 0; trName() const = 0; category() const = 0; trCategory() const = 0;
virtual QWidget *createPage(QWidget *parent) = 0; virtual void apply() = 0; virtual void finish() = 0; };
By implementing the above interface and exposing an instance of it, we will be able to register new pages with the preferences dialog.
Page | 55
Technology Documentation
Within the constructor we populate the list widget with names of the modified pages
#include <coreplugin/filemanager.h> #include <coreplugin/icore.h> #include <coreplugin/ifile.h> ModifiedFileListWidget::ModifiedFileListWidget(QWidget* parent) :QListWidget(parent) { // Show the list of modified pages Core::FileManager* fm = Core::ICore::instance()->fileManager(); QList<Core::IFile*> files = fm->modifiedFiles(); for(int i=0; i<files.count();i++) this->addItem(files.at(i)->fileName()); }
Step 2: Implementing the Core::IOptionsPage interface We implement the Core::IOptionsPage interface in a class called ModifiedFileLister. The class declaration is as follows
#include <coreplugin/dialogs/ioptionspage.h> class ModifiedFileLister : public Core::IOptionsPage { Q_OBJECT public:
Page | 56
Technology Documentation
ModifiedFileLister(QObject *parent = 0); ~ModifiedFileLister(); // IOptionsPage implementation QString id() const; QString trName() const; QString category() const; QString trCategory() const; QWidget *createPage(QWidget *parent); void apply(); void finish(); };
The id() method should be implemented to return a unique identifier for the options page provided by this class. The string will be used internally to identify the page.
QString ModifiedFileLister::id() const { return "ModifiedFiles"; }
The trName() method should be implemented to return a translated string name that will be shown in the options dialog.
QString ModifiedFileLister::trName() const { return tr("Modified Files"); }
The category() and trCategory() methods should be implemented to return the group under which we want to show the page. The latter returns the translated version of the string returned by the former.
QString ModifiedFileLister::category() const { return "Help"; } QString ModifiedFileLister::trCategory() const { return tr("Help"); }
Page | 57
Technology Documentation
The createPage() method should be implemented to return a new instance of the page implemented in step 1.
QWidget *ModifiedFileLister::createPage(QWidget *parent) { return new ModifiedFileListWidget(parent); }
The methods apply() and finish() can be implemented to accept the changes made by the user made on the page. In our case we dont have any changes to accept, so we leave the methods empty.
void ModifiedFileLister::apply() { // Do nothing } void ModifiedFileLister::finish() { // Do nothing }
Step 3: Implementing the modified-file-lister plugin We implement the plugin class similar to the DoNothingPlugin class described in Chapter 2. Hence, we only describe the implementation of the initialize method of the ModifiedFileListerPlugin class here.
bool ModifiedFileListerPlugin::initialize(const QStringList& args, QString *errMsg) { Q_UNUSED(args); Q_UNUSED(errMsg); addAutoReleasedObject(new ModifiedFileLister); return true; }
Step 4: Testing the plugin Upon compiling the plugin and restarting Qt Creator, we can notice in the options dialog the newly added Modified Files page.
Page | 58
Technology Documentation
Page | 59
Technology Documentation
In the find dialog box the scope and configuration widget are extensible. It is possible to add more items to the scope combo box and against every item it is possible to provide a configuration widget that needs to be shown below. Each item in the scope combo box is called find filter in Qt Creator lingo.
Page | 60
Technology Documentation
virtual void findAll(const QString &txt, QTextDocument::FindFlags findFlags) = 0; virtual QWidget *createConfigWidget() { return 0; } virtual void writeSettings(QSettings *settings) { Q_UNUSED(settings); } virtual void readSettings(QSettings *settings) { Q_UNUSED(settings); } signals: void changed(); };
By implementing the IFindFilter interface and adding an instance of it to the object pool, we will be able to add a new find-filter; which means another entry in the scope combo box of the find dialog box.
Step 2: Implementing the HeaderFilter class The constructor and destructors are currently empty. We will fill in more code as we progress with our understanding of the IFindFilter interface.
struct HeaderFilterData { }; HeaderFilter:: HeaderFilter()
Page | 61
Technology Documentation
The id() method should be implemented to return a unique identifier for the find filter.
QString HeaderFilter::id() const { return "HeaderFilter"; }
The name() method should be implemented to return the string that gets shown in the scope combo box of the find dialog box.
QString HeaderFilter::name() const { return tr("Header Filter"); }
The isEnabled() method should be implemented to return whether the find filter is enabled or not. In our case we would like to show the filter enabled if projects are loaded in Qt Creator, false otherwise. To fully understand the implementation of the function, we must first study the ProjectExplorer namespace. For now lets just return true and revisit the function after learning about the ProjectExplorer namespace.
bool HeaderFilter::isEnabled() const { return true; }
The defaultShortcut() method should be implemented to return a key-sequence that the user can use to launch the find dialog box with the header filter selected in scope. In our implementation we return an invalid keysequence.
QKeySequence HeaderFilter::defaultShortcut() const { return QKeySequence(); }
The createConfigWidget() method should be implemented to return a configuration widget that gets shown at the bottom edge of the find dialog box.
Page | 62
Technology Documentation
Configuration Widget
For our header-filter; lets return a simple QLabel because we dont want to provide any configuration options.
QWidget *HeaderFilter::createConfigWidget() { return (new QLabel("This is a header filter")); }
The findAll() method should be implemented to perform the actual find or search operation. We need to understand few key classes in the ProjectExplorer, TextEditor, Find and Core::Utils namespace before attempting to implement the filter. For now implement the method to do nothing.
void HeaderFilter::findAll( const QString &text, QTextDocument::FindFlags findFlags ) { // Do nothing }
Step 3: Implementing the header-filter plugin. We implement the header-filter plugin very similar to the DoNothingPlugin class described in Chapter 2. Here we only look at the implementation if the initialize() method.
bool HeaderFilterPlugin::initialize( const QStringList& args, QString *errMsg) { Q_UNUSED(args); Q_UNUSED(errMsg); addAutoReleasedObject(new HeaderFilter); return true; }
Step 4: Testing the plugin Upon compiling the plugin and restarting Qt Creator, we can notice the Header Filter item in the scope combo box of the find dialog box.
Page | 63
Technology Documentation
Currently no finding or searching is done by our plugin because we have not yet implemented the HeaderFilter::findAll() method.
Lets take a look at few key classes and interfaces in the ProjectManager namespace Class/Interface ProjectExplorer::IProjectManager Description This interface must be implemented to provide support for a kind of project. Implementations of this interface help load projects into Qt Creator. This interface describes a project in terms of A file (Core::IFile) that describes the project. A Boolean flag describing whether the project builds an application or library Build steps (ProjectExplorer:: BuildStep) that need to be performed in order to build and clean the project Run configurations that need to be used for running the project The environment within which the project needs to be run The root node in the project explorer panel Include paths and macros to be used while building the project This class is the implementation of the Core::IPlugin interface for the
ProjectExplorer::Project
ProjectManager:: Page | 64
Technology Documentation
ProjectExplorerPlugin
project explorer plugin. Through this class we can gain access to all the open projects gain access the current project gain access to the currently selected node (file/folder) in the project explorer panel gain access to the build manager (ProjectManager::BuildManager)
From the projects list we can gain access to the project file and all the other (source, header, resource etc) files in the project. To gain access to the project file(s) we can
Q_FOREACH(ProjectExplorer::Project* project, projects) { QString name = project->name(); Core::IFile* projectFile = project->file(); // Do something with the above. For example: qDebug("Project %s has project file as %s", qPrintable(name), qPrintable(projectFile->fileName())); }
While the above code snippet helps with fetching the project file (CMakeLists.txt, .pro etc..), it doesnt help us fetch all the files associated with the project.
Page | 65
Technology Documentation
Page | 66
Technology Documentation
The number of files that need to be searched can be varying. It may be as little as 1 and as high as 1000 or more! Hence searching for something in files within the findAll() method is a bad idea. If the findAll() method takes too long then it may cause Qt Creator to appear frozen until searching is finished. The solution to this is We make use of QtConcurrent and spawn multiple threads to perform the actual searching We initialize a QFutureWatcher on the QFuture returned by QtConcurrent to emit signals as and when search results are generated We catch the signals generated by QFutureWatcher and list search results as they come
Qt Creators core utils library provides a readymade function called findInFiles() that looks for a string within a list of files and returns a QFuture to monitor search results. The function is declared as follows in src/libs/utils/filesearch.h
namespace Core { namespace Utils { class FileSearchResult { public: QString fileName; int lineNumber; QString matchingLine; int matchStart; int matchLength; }; QFuture<FileSearchResult> findInFiles( const QString &searchTerm, const QStringList &files, QTextDocument::FindFlags flags ); QFuture<FileSearchResult> findInFilesRegExp( const QString &searchTerm, const QStringList &files, QTextDocument::FindFlags flags ); } // namespace Utils } // namespace Core
Lets now continue with the implementation of HeaderFilter::findAll() by making use of the findInFiles() method.
struct HeaderFilterData
Page | 67
Technology Documentation
{ QFutureWatcher<FileSearchResult> watcher; ProjectExplorer::ProjectExplorerPlugin* projectExplorer() { ... } }; HeaderFilter::HeaderFilter() { d->watcher.setPendingResultsLimit(1); connect(&d->watcher, SIGNAL(resultReadyAt(int)), this, SLOT(displayResult(int))); } void HeaderFilter::findAll( const QString &text, QTextDocument::FindFlags findFlags ) { .... // Remove duplicates files.removeDuplicates(); // Variable to hold search results that will // come as the search task progresses QFuture<FileSearchResult> searchResults; // Begin searching QString includeline = "#include <" + text + ">"; searchResults = Core::Utils::findInFiles(includeline, files, findFlags); // Let the watcher monitor the search results d->watcher.setFuture(searchResults); } void HeaderFilter::displayResult(int index) { // TODO }
In the revised findAll() implementation we make use of the findInFiles() method to spawn multiple background threads to do all the finding. As search results are generated, the displayResult(int) slot is called. In this slot we can now show search results to the user.
Page | 68
Technology Documentation
Find::SearchResultWindow
We would like to show our search results in the search result window as well. To do so, we modify the HeaderFilter code as follows
#include <find/searchresultwindow.h> struct HeaderFilterData { // Method to search and return the search window Find::SearchResultWindow* searchResultWindow() { if(m_searchResultWindow) return m_searchResultWindow; ExtensionSystem::PluginManager* pm = ExtensionSystem::PluginManager::instance(); m_searchResultWindow = pm->getObject<Find::SearchResultWindow>(); return m_searchResultWindow; } private: Find::SearchResultWindow *m_searchResultWindow; }; HeaderFilter::HeaderFilter() { // displayResult(int) is called when every a new // search result is generated connect(&d->watcher, SIGNAL(resultReadyAt(int)), this, SLOT(displayResult(int))); } void HeaderFilter::findAll( const QString &text, QTextDocument::FindFlags findFlags )
Page | 69
Technology Documentation
{ ... // Clear the current search results d->searchResultWindow()->clearContents(); // Begin searching QString includeline = "#include <" + text + ">"; searchResults = findInFiles(text, files, findFlags); ... } void HeaderFilter::displayResult(int index) { FileSearchResult result = d->watcher.future().resultAt(index); ResultWindowItem *item = d->searchResultWindow()->addResult(result.fileName, result.lineNumber, result.matchingLine, result.matchStart, result.matchLength); if (item) connect(item, SIGNAL(activated(const QString&,int,int)), this, SLOT(openEditor(const QString&,int,int))); } void HeaderFilter::openEditor(const QString &fileName, int line, int column) { // TODO }
Whenever the user double clicks on the search results, the openEditor() method is called. In that method we should have Qt Creator open the corresponding file and mark the searched text.
Page | 70
Technology Documentation
} // TextEditor namespace
Page | 71
Technology Documentation
In this chapter we will learn how to add new project types into the dialog box above.
Page | 72
Technology Documentation
Project
Core::IWizard has to be implemented to support any of the above project types. 9.1.1 Sample implementation of Core::IWizard Lets implement the IWizard interface to support a new project type called Custom Project. The idea is to see the new project type listed in the new project wizard that shows up on clicking File -> New. Step 1: Implementing the Core::IWizard interface Lets create a class called CustomProjectWizard and subclass it from Core::IWizard.
class CustomProjectWizard : public Core::IWizard { public: CustomProjectWizard() { } ~CustomProjectWizard() { } Core::IWizard::Kind kind() const; QIcon icon() const; QString description() const; QString name() const; QString category() const; QString trCategory() const; QStringList runWizard(const QString &path, QWidget *parent); };
Below we will discuss the implementation of each of the functions. The kind() function should be implemented to return the type of new project we support in our implementation of IWizard. Valid values are FileWizard, ClassWizard and ProjectWizard. In our implementation we return ProjectWizard.
Core::IWizard::Kind CustomProjectWizard::kind() const { return IWizard::ProjectWizard; }
The icon() implementation must return an icon to use against the project type in the New project dialog box. In our implementation we return the Qt Creator icon itself.
QIcon CustomProjectWizard::icon() const { return qApp->windowIcon(); }
The description(), name() and category() methods must return some meta data of the new project/file/class type we are providing in the IWizard implementation.
QString CustomProjectWizard::description() const {
Page | 73
Technology Documentation
return "A custom project"; } QString CustomProjectWizard::name() const { return "CustomProject"; } QString CustomProjectWizard::category() const { return "VCreate Logic"; }
The trCategory() method should be implemented to return a translated category string. This is the name that is shown on the New dialog box.
QString CustomProjectWizard::trCategory() const { return tr("VCreate Logic"); }
If the user selects the CustomProject category supported by our implementation of IWizard and selects Ok in the New dialog box; then the runWizard() method is called. This method must be implemented to show a dialog box or QWizard, ask questions from the user about the new project/file/class being created and return a list of newly created files. In our implementation of the IWizard we will return an empty string list.
QStringList CustomProjectWizard::runWizard(const QString &path, QWidget *parent) { Q_UNUSED(path); Q_UNUSED(parent); QMessageBox::information(parent, "Custom Wizard Dialog", "Hi there!"); return QStringList(); }
Step 2: Providing the wizard from a plugin We implement a custom-project plugin using the same means as described in Chapter 2. The only change is in the initialize() method implementation of the plugin.
bool CustomProjectPlugin::initialize(const QStringList& args, QString *errMsg) { Q_UNUSED(args); Q_UNUSED(errMsg); addAutoReleasedObject(new CustomProjectWizard); return true; }
Step 3: Testing the plugin Upon compiling the plugin and restarting Qt Creator, we can notice the new project type in the New.. dialog box. Take a look at the screenshot below. Page | 74
Technology Documentation
Page | 75
Technology Documentation
Files that need to be generated by subclasses of Core::BaseFileWizard are represented by the Core::GeneratedFile class. The class contains three key properties of a file that needs generation 1. Name of the file (with its absolute path). 2. The kind of editor needed for editing the file. Some valid values for editor kind are a. CppEditor::Constants::CPPEDITOR_KIND b. GenericProjectManager::Constants::PROJECT_KIND c. Git::Constants:: GIT_COMMAND_LOG_EDITOR_KIND d. Git::Constants:: C_GIT_COMMAND_LOG_EDITOR 3. Contents of the file. Suppose that we wanted to generate a C++ source file with the following contents
#include <iostream> int main() { cout << "Hello World\n"; return 0; }
9.2.2 The Item Model class wizard Suppose that we wanted to provide a new class wizard that helps automatically generate the skeleton of an item model based on few hints like Model Class Name
Page | 76
Technology Documentation
Base Class Name (can QAbstractTableModel) Header file name and Source file name
be
QAbstractItemModel,
QAbstractListModel
and
Lets implement a plugin that will provide the new Item Model class wizard in Qt Creator. Step 1: Design the class wizard page Lets design a simple page in Qt Designer that accepts hints as described above.
The design is saved as ModelNamePage.ui. Step 2: Implement the class wizard page Lets import the UI in a Qt/C++ and provide easy to use methods to help fetch information from the page. First we design a structure that captures all the item model class hints.
struct ModelClassParameters { QString className; QString headerFile; QString sourceFile; QString baseClass; QString path; };
Next we declare a wizard page class that imports the UI designed in the previous step and provides methods to access the hints provided by the user in the page.
#include <QWizardPage> #include "ui_ModelNamePage.h" class ModelNamePage : public QWizardPage { Q_OBJECT
Page | 77
Technology Documentation
public: ModelNamePage(QWidget* parent=0); ~ModelNamePage(); void setPath(const QString& path); ModelClassParameters parameters() const; private slots: void on_txtModelClass_textEdited(const QString& txt); private: Ui::ModelNamePage ui; QString path; };
The constructor and destructor are straight forward and easy to understand.
ModelNamePage::ModelNamePage(QWidget* parent) :QWizardPage(parent) { setTitle("Enter model class information"); setSubTitle("The header and source file names will be derived from the class name"); ui.setupUi(this); } ModelNamePage::~ModelNamePage() { }
The setPath() method basically stores the path in the private variable.
void ModelNamePage::setPath(const QString& path) { this->path = path; }
The on_txtModelClass_textEdited() slot computes the header and source file names based on the classname.
void ModelNamePage::on_txtModelClass_textEdited(const QString& txt) { d->ui.txtHeaderFile->setText(txt + ".h"); d->ui.txtImplFile->setText(txt + ".cpp"); }
Finally the parameters() method returns all the hints entered by the user in a ModelClassParameters instance.
ModelClassParameters ModelNamePage::parameters() const { ModelClassParameters params; params.className = ui.txtModelClass->text(); params.headerFile = ui.txtHeaderFile->text();
Page | 78
Technology Documentation
Note: Some methods from the actual BaseFileWizard class are not shown here. The BaseFileWizard class implements the IWizard interface and offers three new functions createWizardDialog This function can be over-ridden by subclasses to provide a wizard that the runWizard() method is supposed to show. o The parent parameter should be used as the parent widget of the returned QWizard o The defaultPath parameter should be the default location for generated files o The extensionPages parameter lists out all the pages that should be shown in the wizard by default. generateFiles This method is called after the user is done with the wizard. Implementations of this method must create the required files as instances of Core::GeneratedFile class. postGenerateFiles This method is called after generateFiles() returns. The default implementation opens the newly generated files; however subclasses can choose to do anything they want.
Page | 79
Technology Documentation
The constructor and destructor methods are straight forward and easy to understand.
ModelClassWizard::ModelClassWizard( const Core::BaseFileWizardParameters ¶meters, QObject *parent) : Core::BaseFileWizard(parameters, parent) { } ModelClassWizard::~ModelClassWizard() { }
The createWizardDialog() method is implemented to create a QWizard with its first page as the ModelNamePage class implemented step 2. Other default pages are added as usual.
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);
Page | 80
Technology Documentation
wizard->setProperty("_PageId_", pageId); page->setPath(defaultPath); // Now add the remaining pages foreach (QWizardPage *p, extensionPages) wizard->addPage(p); return wizard; }
The readFile() method is implemented to read a file and return its contents as a string. Before returning the files contents as string, the function uses the replacement table passed as second parameter to fix the string.
QString ModelClassWizard::readFile(const QString& fileName, const QMap<QString,QString>& replacementMap) const { QFile file(fileName); file.open(QFile::ReadOnly); QString retStr = file.readAll(); QMap<QString,QString>::const_iterator it = replacementMap.begin(); QMap<QString,QString>::const_iterator end = replacementMap.end(); while(it != end) { retStr.replace(it.key(), it.value()); ++it; } return retStr; }
Page | 81
Technology Documentation
#endif // {{UPPER_CLASS_NAME}}_H
Lets say we wanted to replace the hints in {{xyz}} with something more appropriate, we could use the following code snippet.
QMap<QString,QString> replacementMap; replacementMap["{{UPPER_CLASS_NAME}}"] = "LIST_MODEL"; replacementMap["{{BASE_CLASS_NAME}}"] = "QAbstractListModel"; replacementMap["{{CLASS_NAME}}"] = "ListModel"; QString contents = readFile("Sample.txt", replacementTable);
When the above code is executed, the contents string will contain
#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
Seems like magic isnt it? . We create similar template header and source files for item, list and table model classes and create a resource for use in our project. Now, lets look at the implementation of the generateFiles() method. This method basically creates two Core::GeneratedFile instances and populates them with appropriate data before returning them in a list.
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<ModelNamePage*>(w->page(pageId));
Page | 82
Technology Documentation
if(!page) return ret; ModelClassParameters params = page->parameters(); QMap<QString,QString> 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.setEditorKind(CppEditor::Constants::CPPEDITOR_KIND); Core::GeneratedFile sourceFile(params.path + "/" + params.sourceFile); sourceFile.setEditorKind(CppEditor::Constants::CPPEDITOR_KIND); if(params.baseClass == "QAbstractItemModel") { headerFile.setContents( readFile(":/CustomProject/ItemModelHeader", replacementMap) ); sourceFile.setContents( readFile(":/CustomProject/ItemModelSource", replacementMap) ); } else if(params.baseClass == "QAbstractTableModel") { headerFile.setContents( readFile(":/CustomProject/TableModelHeader", replacementMap) ); sourceFile.setContents( readFile(":/CustomProject/TableModelSource", replacementMap) ); } else if(params.baseClass == "QAbstractListModel") { headerFile.setContents( readFile(":/CustomProject/ListModelHeader", replacementMap) ); sourceFile.setContents( readFile(":/CustomProject/ListModelSource", replacementMap) ); } ret << headerFile << sourceFile; return ret; }
Step 4: Implementing the plugin We implement the item model wizard plugin using the same means as described in Chapter 2. The only change is in the initialize() method implementation of the plugin.
bool ItemModelWizard::initialize(const QStringList& args, QString *errMsg) { Q_UNUSED(args); Q_UNUSED(errMsg); Core::BaseFileWizardParameters params; params.setKind(Core::IWizard::ClassWizard);
Page | 83
Technology Documentation
params.setIcon(qApp->windowIcon()); params.setDescription("Generates an item-model class"); params.setName("Item Model"); params.setCategory("VCreate Logic"); params.setTrCategory(tr("VCreate Logic")); addAutoReleasedObject(new ModelClassWizard(params, this)); return true; }
Step 5: Testing the plugin Upon compiling the plugin and restarting Qt Creator, we can notice the new project type in the New.. dialog box. The following screenshots showcase the wizard that was just implemented.
Upon selecting the Item Model class wizard, we can see the ModelNamePage in a custom wizard.
Page | 84
Technology Documentation
We enter the appropriate details and click Next. Qt Creator then shows us a built-in page to allow addition of the newly generated files into the current project.
Upon clicking Finish, we can notice the newly generated files in the editor.
Page | 85