📜 ⬆️ ⬇️

Qt Creator Extension System

Foreword


Hello. I admit right away that I started writing this post for quite a long time, but I don’t have enough time to fully complete its writing. Therefore, now I am publishing it in the current state, and I will still postpone the description of the three incomplete sections and will try to publish it in a separate post.

Introduction


This is a note in which I would like to briefly describe the architecture and system extensions of the Qt Creator development environment. Initially, I assumed only to translate the Writing-Qt-Cretor-plugins.pdf document, but it just so happened that the development of Qt Creator does not stand still and firstly, this document is not so relevant (it’s outdated, the API has changed, pieces of code are not full and often do not work), and secondly, since the time it was written, additional features of Qt Creator extension have appeared that I would like to describe.

However, without this document, there would not have been a given note: I took a lot out of it, right down to the structure of the post, while trying somewhere to throw out / replace / add something to make the post relevant for the latter at the moment Qt Creator 2.4.0 time release.
')
Who can be useful for this document? First of all, these are of course Qt-programmers who have chosen this IDE as their main development environment.

In addition, thanks to the well-thought-out system of Qt Creator extensions, this material will be useful to those who are going to create their own development tools, but do not want to start writing them from scratch: a person can turn off all Qt Creator extensions that are unnecessary for him and write their own ready-made examples in the source code Qt Creator.

So, what awaits us under the cut (finished sections are marked in bold):

  1. Qt Creator build
  2. First expansion
  3. Adding new menus and menu items
  4. Qt Creator Architecture
  5. Adding a new editor (Editor)
  6. Adding a side navigation bar
  7. Adding a page to the settings dialog
  8. Adding a filter to the search dialog
  9. Adding a new project type

Let me remind you that Qt Creator is a cross-platform free IDE for working with the Qt framework developed by Trolltech (Nokia). What does not prevent to make from it a simple text editor with syntax highlighting, simply disabling all extensions. Attention, hundreds of pictures!

1. Build Qt Creator


Building an entire IDE is fairly straightforward. First of all, we need to download the source code of the latest version of Qt Creator. at the moment it is version 2.4. Download the file from qt.nokia.com from Downloads / Qt-Creator :
image
Next, unpack the resulting archive, create a build subdirectory in the source directory, go to it, run qmake and then make:
$ tar -xvf qt-creator-2.4.0-src.tar.gz $ mkdir qt-creator-2.4.0-src/build $ cd qt-creator-2.4.0-src/build/ $ qmake ../qtcreator.pro –recursive $ make 

For Windows users, this code may differ only in the last line - instead of make, you will need to call mingw32-make or nmake, depending on the user's preferences.

That's all. You can run Qt Creator from the build / bin directory.
image
It should be noted that this is a very important step, because if you do not build Qt Creator from source, you will not be able to go further and compile and test extensions for it.

2. First expansion


As in many cases, exploring the Qt Creator extension system should start by creating a very simple extension. Now we will try to make an extension that does nothing, but using the example of our DoNothing extension, we will learn about Qt Creator base classes related to writing extensions and see the “DoNothing” line in the list of available extensions.
image
image

2.1 Creating a Qt Creator Extension Project

Previously, it seems down to version 2.0, to perform this step, you had to manually create the following files:

Also, then it was necessary either to add the extension project to the $$ QT_CREATOR_ROOT / src / plugins directory, or placing it elsewhere, you need to specify the source and build directories of Qt Creator in the .pro file. In modern versions of Qt Creator, project files can be placed anywhere, since a new type of master, the Qt Creator Module, has appeared to create it. The process of creating a new extension is currently as follows /
At the very beginning, everything is as usual - choose the type of project being created,
image
after we enter the name and the path to its directory
image
as well as the purpose of the assembly, of course it's Desktop.
image
But after that, a little bit of magic begins - we need to fill in the module-specific information:
image
Here, in general, everything is more than clear from the picture. The only vital fields here are the path to the Qt Creator source / build directories. Finishing the wizard:
image
And we look at the resulting project structure:
image

2.2 Service Files

At first glance there are a lot of files and directories, but in fact there is nothing to worry about. Let's look at what we saw above and start with the project file:
 TARGET = DoNothing TEMPLATE = lib DEFINES += DONOTHING_LIBRARY # DoNothing files SOURCES += donothingplugin.cpp HEADERS += donothingplugin.h\ donothing_global.h\ donothingconstants.h OTHER_FILES = DoNothing.pluginspec # Qt Creator linking ## set the QTC_SOURCE environment variable to override the setting here QTCREATOR_SOURCES = $$(QTC_SOURCE) isEmpty(QTCREATOR_SOURCES):QTCREATOR_SOURCES=/home/kafeg/devel/Qt/qt-creator-2.4.0-src ## set the QTC_BUILD environment variable to override the setting here IDE_BUILD_TREE = $$(QTC_BUILD) isEmpty(IDE_BUILD_TREE):IDE_BUILD_TREE=/home/kafeg/devel/Qt/qt-creator-2.4.0-src/build ## uncomment to build plugin into user config directory ## <localappdata>/plugins/<ideversion> ## where <localappdata> is eg ## "%LOCALAPPDATA%\Nokia\qtcreator" on Windows Vista and later ## "$XDG_DATA_HOME/Nokia/qtcreator" or "~/.local/share/Nokia/qtcreator" on Linux ## "~/Library/Application Support/Nokia/Qt Creator" on Mac # USE_USER_DESTDIR = yes PROVIDER = DoNothingCompany include($$QTCREATOR_SOURCES/src/qtcreatorplugin.pri) include($$QTCREATOR_SOURCES/src/plugins/coreplugin/coreplugin.pri) LIBS += -L$$IDE_PLUGIN_PATH/Nokia 

As you can see, in this generated file, the name (DoNothing) and the type (library) of the project are described, three header files and one source code file are specified, pluginspec is mentioned. The location of Qt Creator source codes is indicated, commented instructions allow installing the assembled library not into the Qt Creator directory, but into the local user directory, the provider is described, on which the final location of the library files depends. And finally, the qtcreatorplugin.pri and coreplugin.pri files, which are basic for all extensions, are included, which are already responsible for properly linking our extension with all the necessary libraries.

The following files are donothing_global.h :
 #ifndef DONOTHING_GLOBAL_H #define DONOTHING_GLOBAL_H #include <QtCore/QtGlobal> #if defined(DONOTHING_LIBRARY) # define DONOTHINGSHARED_EXPORT Q_DECL_EXPORT #else # define DONOTHINGSHARED_EXPORT Q_DECL_IMPORT #endif #endif // DONOTHING_GLOBAL_H 

and donothingconstants.h :
 #ifndef DONOTHINGCONSTANTS_H #define DONOTHINGCONSTANTS_H namespace DoNothing { namespace Constants { const char * const ACTION_ID = "DoNothing.Action"; const char * const MENU_ID = "DoNothing.Menu"; } // namespace DoNothing } // namespace Constants #endif // DONOTHINGCONSTANTS_H 

Here I think everything is clear without additional explanations. And what is not clear - it will become clear when we will need this code in the future. The list of service files can be completed with the DoNothing.pluginspec.in file:
 <plugin name=\"DoNothing\" version=\"0.0.1\" compatVersion=\"0.0.1\"> <vendor>DoNothingCompany</vendor> <copyright>(C) DoNothing Company</copyright> <license>DoNothing Company license text here</license> <description>DoNothing Company short description of plugin here</description> <url>http://www.donothing.com</url> <dependencyList> <dependency name=\"Core\" version=\"$$QTCREATOR_VERSION\"/> </dependencyList> </plugin> 

This file will eventually come with a binary library file and is a simple description of our extension. And this is what he describes:
  1. The name of the extension that will be used in the name of the library that implements it. (In our case, DoNothing.dll in Windows and libDoNothing.so in Unix)
  2. Extension version
  3. Qt Creator Extension Required Version
  4. Vendor Name
  5. Copyright
  6. License text
  7. Description
  8. Vendor URL
  9. List of extensions on which this extension depends.

2.3 Implementation of the extension

To successfully compile an empty project, two basic implementation files of our extension are needed. What are they like? The main requirement is that the main extension class should inherit from the base class IPlugin and override some of its methods.
donothingplugin.h
 #ifndef DONOTHING_H #define DONOTHING_H #include "donothing_global.h" #include <extensionsystem/iplugin.h> namespace DoNothing { namespace Internal { class DoNothingPlugin : public ExtensionSystem::IPlugin { Q_OBJECT public: DoNothingPlugin(); ~DoNothingPlugin(); bool initialize(const QStringList &arguments, QString *errorString); void extensionsInitialized(); ShutdownFlag aboutToShutdown(); private slots: void triggerAction(); }; } // namespace Internal } // namespace DoNothing #endif // DONOTHING_H 

Consider the file implementation of this code in more detail:
 #include "donothingplugin.h" #include "donothingconstants.h" #include <coreplugin/icore.h> #include <coreplugin/icontext.h> #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/command.h> #include <coreplugin/actionmanager/actioncontainer.h> #include <coreplugin/coreconstants.h> #include <QtGui/QAction> #include <QtGui/QMessageBox> #include <QtGui/QMainWindow> #include <QtGui/QMenu> #include <QtCore/QtPlugin> using namespace DoNothing::Internal; DoNothingPlugin::DoNothingPlugin() { // Create your members } DoNothingPlugin::~DoNothingPlugin() { // Unregister objects from the plugin manager's object pool // Delete members } 

The constructor and destructor are used only to initialize base variables that are not widgets and / or actions (Action).
 bool DoNothingPlugin::initialize(const QStringList &arguments, QString *errorString) { // Register objects in the plugin manager's object pool // Load settings // Add actions to menus // connect to other plugins' signals // "In the initialize method, a plugin can be sure that the plugins it // depends on have initialized their members." Q_UNUSED(arguments) Q_UNUSED(errorString) Core::ActionManager *am = Core::ICore::instance()->actionManager(); QAction *action = new QAction(tr("DoNothing action"), this); Core::Command *cmd = am->registerAction(action, Constants::ACTION_ID, Core::Context(Core::Constants::C_GLOBAL)); cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Alt+Meta+A"))); connect(action, SIGNAL(triggered()), this, SLOT(triggerAction())); Core::ActionContainer *menu = am->createMenu(Constants::MENU_ID); menu->menu()->setTitle(tr("DoNothing")); menu->addAction(cmd); am->actionContainer(Core::Constants::M_TOOLS)->addMenu(menu); return true; } 

The initialize () function is called when Qt Creator decides it's time to initialize the extension. This function is intended to initialize the initial state and register all actions and objects related to the extension in Qt Creator itself.

The function will be called only after all extensions depend on the extension already loaded into memory. In the default code, as can be seen from the example above, the addition of a new menu item has already been described, but we will stop there a bit later.

All we need to know now is that if the initialization is successfully completed, this function should return true, if unsuccessful, return false and write an error message to the errorString variable in human language.
 void DoNothingPlugin::extensionsInitialized() { // Retrieve objects from the plugin manager's object pool // "In the extensionsInitialized method, a plugin can be sure that all // plugins that depend on it are completely initialized." } 

The extensionsInitialized () method is called after the end of initialization. Serves mainly as an assistant to those extensions that depend on the current one.
 ExtensionSystem::IPlugin::ShutdownFlag DoNothingPlugin::aboutToShutdown() { // Save settings // Disconnect from signals that are not needed during shutdown // Hide UI (if you add UI that is not in the main window directly) return SynchronousShutdown; } 

The aboutToShutdown () method is called before the extension is unloaded from memory.
 void DoNothingPlugin::triggerAction() { QMessageBox::information(Core::ICore::instance()->mainWindow(), tr("Action triggered"), tr("This is an action from DoNothing.")); } Q_EXPORT_PLUGIN2(DoNothing, DoNothingPlugin) 

2.4 Build and test extensions

To build our extension, just press the key combination Ctrl + R, after which the extension will be assembled and installed in the Qt Ceator extension directory, but not launched, since Qt Creator does not know how to launch this library. You can fix this situation on the application launch settings page (Projects -> Start -> Startup Configuration -> Program):
image
The result will be Qt Creator launched, in the list of extensions of which you can see a new line:
image
And in the directory of all extensions a new directory with a couple of files appeared:
 kafeg@kafeg-desktop:~/devel/Qt/qt-creator-2.4.0-src/build/lib/qtcreator/plugins/DoNothingCompany$ ls DoNothing.pluginspec libDoNothing.so 

This is how we can add the most elementary Qt Creator extension. Go ahead.

3 Adding new menus and menu items


In this part, we will learn how to add new menu menus to existing menus, and also understand how to create our own menus. But first, look at the contents of the Qt Creator panel menu:
image
The panel contains the following default elements:

All other menu items, for example, Debug, Build and Analysis are implemented in separate extensions and are not part of the default menu set.

Qt developers know that the menus of the menus themselves are implemented by a combination of the QMenu and QAction classes, and their display in the form of a panel is handled by the QMenuBar class.

3.1 Core :: ActionManager

The basic part of Qt Creator is essentially just an empty window that can load extensions. All the functionality provided by Qt Creator is implemented through its extensions. The main extension of Qt Creator is referred to as "core". Without this extension, Qt Creator is nothing at all.

One of the main components of the core extension is ActionManager. ActionManager is an object responsible for registering all menus, menu items, and keyboard shortcuts. Actually, if we want to add a new menu item, we must use the ActionManager object. Just below, we'll figure out how ...
To access the ActionManager object, our code must contain the following:
 #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/icore.h> ... Core::ActionManager* am = Core::ICore::instance()->actionManager(); 

3.2 Core :: ActionContainer

ActionContianer provides menus and menu bars in Qt Creator. Instances of this class are never created directly, they are accessed through the methods ActionManager :: createMenu (), ActionManager :: createMenuBar () and others.

There are instances of ActionContainer associated with all the default menus. To get an instance of ActionContainer, you need to use code like this:
 #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 ); 

Below is a table of the default IDs that allow you to get ActionContainer instances. IDs are variables of type const char * static in Core visibility.
MenuID
FileCore :: Constants :: M_FILE
File -> NewCore :: Constants :: M_FILE_NEW
File -> OpenCore :: Constants :: M_FILE_OPEN
File -> Recent FilesCore :: Constants :: M_FILE_RECENTFILES
EditCore :: Constants :: M_EDIT
Edit -> AdvancedCore :: Constants :: M_EDIT_ADVANCED
ToolsCore :: Constants :: M_TOOLS
WindowCore :: Constants :: M_WINDOW
Window panesCore :: Constants :: M_WINDOW_PANES
HelpCore :: Constants :: M_HELP

For example, if we want to get a pointer to the “Help” menu, then we should use the following code:
 #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 ); 

3.3 Adding Menus and Menu Items

Let's see how we can add new menu items. To do this, let's return to our existing initialize () function and take a closer look at its implementation.
 bool DoNothingPlugin::initialize(const QStringList &arguments, QString *errorString) { Q_UNUSED(arguments) Q_UNUSED(errorString) Core::ActionManager *am = Core::ICore::instance()->actionManager(); QAction *action = new QAction(tr("DoNothing action"), this); Core::Command *cmd = am->registerAction(action, Constants::ACTION_ID, Core::Context(Core::Constants::C_GLOBAL)); cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Alt+Meta+A"))); connect(action, SIGNAL(triggered()), this, SLOT(triggerAction())); Core::ActionContainer *menu = am->createMenu(Constants::MENU_ID); menu->menu()->setTitle(tr("DoNothing")); menu->addAction(cmd); am->actionContainer(Core::Constants::M_TOOLS)->addMenu(menu); return true; } 

The code shown here is now becoming much more understandable. So, we get a pointer to ActionManager, then create a new menu item and assign it a shortcut. After that we create our own menu and add the item we just created to it. It is also worth noting that now ACTION_ID and MENU_ID are useful to us.

Next we get a pointer to the ActionContainer menu of tools (M_TOOLS) and add our menu to it.
In addition, our keyboard shortcut was entered in the registry of all keyboard shortcuts of the application and is available to change in its settings:
image
It should also be noted that you can add your menus anywhere. For example, if we would need to add our menu to the main menu bar, then instead of the constant M_TOOLS we would use the constant MENU_BAR. And in order to position our menu inside another menu, we would have to pass an additional parameter to the function addMenu ()
 am->actionContainer(Core::Constants::MENU_BAR)->addMenu(am->actionContainer(Core::Constants::M_HELP), menu); 

3.3 Responding to menu item events

Since menu items are instances of the QAction class, we can use the signals sent by these objects, such as triggered (bool) or toggled (bool), and respond to them by creating slots. An example again can be taken from our code above:
 ... connect(action, SIGNAL(triggered()), this, SLOT(triggerAction())); ... void DoNothingPlugin::triggerAction() { QMessageBox::information(Core::ICore::instance()->mainWindow(), tr("Action triggered"), tr("This is an action from DoNothing.")); } 

image

4 Qt Creator Architecture


As a rule, each large enough system has its own architecture, having understood that, we will be able to move on much faster. Qt Creator is simple and in this part we will try to understand its basic architecture, after which we will continue to deal with writing extensions.

4.1 Qt Creator Core

The Qt Creator core is just an extension manager. All functionality is provided through them.
image


The basic functionality of Qt Creator is implemented in the Core extension (Core :: ICore). We have already touched on this expansion in the previous part. In the following, we will refer to this base extension as Core. Extension Manager (ExtensionSystem :: PluginManager) provides simple features for the interaction of extensions, through hooks, which some extensions can provide to others.

4.2 What is an extension?

At the system level, an extension is a shared library (DLL on Windows, SO on Linux, DYLIB on Mac). From the developer’s point of view, an extension is a module that:
  1. Inherits and implements the ExtensionSystem :: IPlugin interface. Further in the text we will call a similar class “Plugin Class”.
  2. Exports the Plugin Class using the Q_EXPORT_PLUGIN macro.
  3. Provides a file (Plugin Name) .pluginspec containing metadata about the extension.
  4. Provides one or more objects that other extensions may be interested in using.
  5. Checks the availability of one or more objects provided by other extensions.

We have already met the first three points, but nowhere have we stopped at the last two.

4.2.1 How to get a list of available objects?

Available objects are stored in the object pool inside the PluginManager. The allObjects () method from the PluginManager returns the entire pool of objects as a list of pointers to a QObject. Below is the code with which we could print a list of available objects for printing:
 #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); } listWidget->show(); 

Thus, after executing this part of the code, we will see a long list of available objects:
image

From the names in the list it can be concluded that the objects are provided by various extensions. I think now we can give a more precise definition of the term "object provided":

A supplied (exported) object is an instance of a QObject class (or its descendants) provided by one extension and available to other extensions through a pool of objects.

4.2.2 How to export your object to the list of available?

For this purpose there are three ways:

The methods IPlugin :: addObject () and IPlugin :: addAutoReleasedObject () essentially call the PluginManager :: addObject () method.
IPlugin methods are created just for convenience. Extensions are recommended to use them to add objects. The differences between the addAutoReleasedObject () and addObject () methods are that the objects added through the first method will be automatically destroyed and removed from the pool of available objects (in the reverse order) when the extension is destroyed. In addition, you can call the IPlugin :: removeObject (QObject *) method at any time to manually remove an object from the pool of available ones.

4.2.3 Which objects to export?

Extensions can provide (export) any objects, but usually exported objects are structured according to a functional feature. Other Qt Creator functionality is provided via exported interfaces. Here are some of them:

C ++ developers are accustomed to the fact that interfaces are usually called classes, all methods of which are purely virtual and public. In Qt Creator, interfaces are descendants of a QObject that have one or more purely virtual methods.

If the extension has objects that implement an interface, then such an object should be exported. For example, if an extension exports an implementation of the INavigationWidgetFactory interface, Core will automatically pick up the widget created in this implementation to display it on the navigation bar. As an example, consider adding a simple QTableWidget to the navigation bar through the implementation of the Core :: INavigationWidgetFactory interface.
 #include <coreplugin/inavigationwidgetfactory.h> #include <QTableWidget> class NavWidgetFactory : public Core::INavigationWidgetFactory { public: NavWidgetFactory() { } ~NavWidgetFactory() { } Core::NavigationView createWidget() { Core::NavigationView view; view.widget = new QTableWidget(50, 3); return view; } QString displayName() const { return "Spreadsheet"; } int priority() const { return 0; } QString id() const { return "Spreadsheet"; } }; bool DoNothingPlugin::initialize(const QStringList &arguments, QString *errorString) { ... // 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; } 

And here is the result:
image

4.2.4 Notifications about newly exported objects.

When the PluginManager :: addObject () method is called, the PluginManager sends an objectAdded signal (QObject *). This signal can be used to track which objects were newly added during Qt Creator operation.

It is obvious that an extension can process this signal only after connecting it to any of its slots, and therefore, it will receive notifications only about objects added after its full initialization has been completed.

Usually, the slot connected to this signal monitors one or several interfaces. For example, this might be the code that follows the appearance of a new object that implements the INavigationWidgetFactory interface.
 void Plugin::slotObjectAdded(QObject * obj) { INavigationWidgetFactory *factory = Aggregation::query<INavigationWidgetFactory>(obj); if(factory) { // use it here... } } 

4.2.4 Object Search

, . :

, . ? PluginManager::getObjects(). , , INavigationWidgetFactory, :
 ExtensionSystem::PluginManager* pm = ExtensionSystem::PluginManager::instance(); QList<Core::INavigationWidgetFactory*> objects = pm->getObjects<Core::INavigationWidgetFactory>(); 

4.3

QObject . , Qt Creator
 #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); 

Now the bundle object contains pointers to two objects. To access the interfaces, we can use the following code:
 Interface1* iface1Ptr = Aggregation::query<Interface1>( &bundle ); Interface2* iface2Ptr = Aggregation::query<Interface2>( &bundle ); 

You can include multiple interfaces in one bundle:
 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 ); 

And also delete both the added interfaces and the bundle itself:
 Aggregation::Aggregate* bundle = new Aggregation::Aggregate; bundle->add(new Interface1); bundle->add(new Interface2); Interface1* iface1Ptr = Aggregation::query<Interface1>(bundle); delete iface1Ptr; // deletes the bundle and all objects in it // same as delete bundle 

Now it may not be clear why there is a need to mention Aggregation :: Aggregate objects here, but in the following parts it will also be useful to us to simplify the implementation and structuring of the described examples.

5 Adding a new editor (Editor)


( Core Plugin), Qt Creator — . , , : , C++ . UI (Qt Designer), JS/QML, QRC, (PRO/PRI), EXE/DLL/SO.

, HTML. , HTML- .

5.1

, :

Now we will consider each of these interfaces.

5.1.1 Interface Core :: IFile.

This interface is an abstract layer between working with files and the user interface. It provides purely virtual methods for loading / saving files, specifying the file name, also helps the application determine the mime type of the file and get the values ​​of certain flags (for example, “modified” and “read-only”). This interface is declared in the src / plugins / coreplugin / ifile.h file :

The question arises - why do I need an IFile if a QFile already exists? Here is the answer:

5.1.2 Core::IEditor.

. src/plugins/coreplugin/editormanager/ieditor.h

Core::IEditor :

, .
image

5.1.3 Core::IEditorFactory

Core::IEditor mime-. src/plugins/coreplugin/editormanager/ieditorfactory.h.

IEditorFactory::mimeType() mime- . IEditorFactory::createEditor() .

5.1.4 Core::MimeDatabase

Core::MimeDatabase Qt Creator mime-. mime- . 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())); 

:
 File Type for sample.html = text/plain File Type for Main.cpp = text/x-c++src File Type for TextEdit.pro = text/plain 

Core::MimeDatabase , , - mime- . mime- :
  1. ( -> ).
  2. Qt Creator Core::MimeDatabase mime-type .
  3. Qt Creator Core::IEditorFactory mime-.
  4. Qt Creator Core::IEditorFactory Core::IEditor.
  5. , Core::IEditor::widget() .
  6. Core::IEditor::open() , .

5.1.5 mime-

, mime-, Core::MimeDatabase. , , XML-. , text/html mime- *.html. XML 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> 

mime- Core::MimeDatabase::addMimeTypes():
 Core::ICore* core = Core::ICore::instance(); Core::MimeDatabase* mdb = core->mimeDatabase(); QString errMsg; bool success = mdb->addMimeTypes("text-html-mimetype.xml", errMsg); 

— Qt Creator *.html mime- text/html.

5.2 Qt Creator HTML.

Well, having stopped a little on the theory, we can start creating an editor that supports viewing / editing HTML files. To implement our plans, we need to create several classes:
Classbase classDescription
HtmlEditorWidgetQTabWidgetThis will be a widget with two tabs, the first one will show the final result, and the second will show the source of the document.
HtmlfileCore :: IFileImplementing the IFile interface for HtmlEditorWidget.
HtmleditorCore :: IEditorImplements IEditor to control HtmlEditorWidget and its interaction with HtmlFile.
HtmlEditorFactoryCore :: IEditorFactoryImplements IEditorFactory to create IEditor instances based on the text / html mime type.
HtmlEditorPluginCore :: IPluginImplements IPlugin to associate all of the above with Qt Creator.

, Qt Creator, HTMLEditor .

5.2.1 HTML Editor Widget

, Qt Creator HTML-. - , , — .
 #ifndef HTMLEDITORWIDGET_H #define HTMLEDITORWIDGET_H #include <QTabWidget> #include <QWebView> #include <QPlainTextEdit> struct HtmlEditorWidgetData { QWebView *webView; QPlainTextEdit *textEdit; bool modified; QString path; }; 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; }; #endif // HTMLEDITORWIDGET_H 

QWebView QPlainTextEdit, :
  1. , QWeBView
  2. ,
  3. QWebView.

 #include "htmleditorwidget.h" 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)), this, SIGNAL(titleChanged(QString))); d->modified = false; } 

:
 HtmlEditorWidget::~HtmlEditorWidget() { delete d; } 

The setContent () method sets the contents of webView and textEdit. And the content () method returns the content accordingly. The title () method returns a string that will be displayed in the list of open files. Well, the last two methods handle the connections with signals created in the constructor.
 void HtmlEditorWidget::setContent(const QByteArray& ba, const QString& path) { if(path.isEmpty()) d->webView->setHtml(ba); else d->webView->setHtml("file:///" + path); d->textEdit->setPlainText(ba); d->modified = false; d->path = path; } QByteArray HtmlEditorWidget::content() const { QString htmlText = d->textEdit->toPlainText(); return htmlText.toAscii(); } QString HtmlEditorWidget::title() const { return d->webView->title(); } void HtmlEditorWidget::slotCurrentTabChanged(int tab) { if(tab == 0 && d->modified) setContent( content(), d->path ); } void HtmlEditorWidget::slotContentModified() { d->modified = true; emit contentModified(); } 

5.2.2 Core :: IFile Implementation

We implement this interface in the HtmlFile class. This class will implement several virtual methods from Core :: IFile and will be able to set the modified flag to reflect the status of the document being edited.
 #ifndef HTMLFILE_H #define HTMLFILE_H #include <coreplugin/ifile.h> #include "htmleditorconstants.h" struct HtmlFileData; class HtmlEditor; class HtmlEditorWidget; class HtmlFile : public Core::IFile { Q_OBJECT public: HtmlFile(HtmlEditor* editor, HtmlEditorWidget* editorWidget); ~HtmlFile(); void setModified(bool val=true); bool isModified() const; QString mimeType() const; bool save(QString *errorString, const QString &fileName, bool autoSave); bool reload(QString *errorString, ReloadFlag flag, ChangeType type); void rename(const QString &newName); bool open(const QString &fileName); void setFilename(const QString& filename); QString fileName() const; QString defaultPath() const; QString suggestedFileName() const; QString fileFilter() const; QString fileExtension() const; bool isReadOnly() const; bool isSaveAsAllowed() const; void modified(ReloadBehavior* behavior); protected slots: void modified() { setModified(true); } private: HtmlFileData* d; }; struct HtmlFileData { HtmlFileData() : mimeType(HTMLEditor::Constants::C_HTMLEDITOR_MIMETYPE), editorWidget(0), editor(0), modified(false) { } const QString mimeType; HtmlEditorWidget* editorWidget; HtmlEditor* editor; QString fileName; bool modified; }; #endif // HTMLFILE_H 

In the constructor, we simply set up the associations we need. In the destructor we delete the private object.
 #include "htmlfile.h" #include <QFile> #include <QFileInfo> #include "htmleditor.h" #include "htmleditorwidget.h" HtmlFile::HtmlFile(HtmlEditor* editor, HtmlEditorWidget* editorWidget) : Core::IFile(editor) { d = new HtmlFileData; d->editor = editor; d->editorWidget = editorWidget; } HtmlFile::~HtmlFile() { delete d; } 

The setModified () method sets the modified flag and sends 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; } 

From here we return the mime-type of this class.
 QString HtmlFile::mimeType() const { return d->mimeType; } 

The save () method is called when you select the menu item File -> Save or by using the keyboard shortcut Ctrl + S. It saves the contents of the HtmlEditorWidget (from textEdit) in the current associated file. then the modified flag is set to false.
 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 you select File -> Open. or Ctrl + O. Loads the file and passes it to the setContent () function of our editing widget.
 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; } bool HtmlFile::reload(QString *errorString, ReloadFlag flag, ChangeType type) { return open(d->fileName); } void HtmlFile::rename(const QString &newName) { QFile file(d->fileName); file.rename(newName); setFilename(newName); } void HtmlFile::setFilename(const QString& filename) { d->fileName = filename; } QString HtmlFile::fileName() const { return d->fileName; } QString HtmlFile::defaultPath() const { return QString(); } QString HtmlFile::suggestedFileName() const { return QString(); } QString HtmlFile::fileFilter() const { return QString(); } QString HtmlFile::fileExtension() const { return QString(); } bool HtmlFile::isReadOnly() const { return false; } bool HtmlFile::isSaveAsAllowed() const { return true; } void HtmlFile::modified(ReloadBehavior* behavior) { Q_UNUSED(behavior); } 

5.2.3 Core :: IEditor Implementation

We implement IEditor to allow Qt Creator to use our html widget and associate it with HtmlFile.
 #ifndef HTMLEDITOR_H #define HTMLEDITOR_H #include <coreplugin/editormanager/ieditor.h> #include <QToolBar> struct HtmlEditorData; class HtmlFile; class HtmlEditorWidget; class HtmlEditor : public Core::IEditor { Q_OBJECT public: 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(); Core::Context context() const; QString id() const; protected slots: void slotTitleChanged(const QString& title) { setDisplayName(title); } private: HtmlEditorData* d; }; 

The HtmlEditorData object stores pointers to HtmlEditorWidget and HtmlFile objects, the displayName variable is used to store the name of the editor displayed to the user.
 struct HtmlEditorData { HtmlEditorData() : editorWidget(0), file(0) { } HtmlEditorWidget* editorWidget; QString displayName; HtmlFile* file; Core::Context context; }; #endif // HTMLEDITOR_H 

The constructor initializes itself, creates an instance of HtmlFile, and associates between all three objects. destructor by tradition only removes the private object.
 #include "htmleditor.h" #include "htmlfile.h" #include "htmleditorwidget.h" 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 = *(new Core::Context(HTMLEditor::Constants::C_HTMLEDITOR)); //<< uidm->uniqueIdentifier(HTMLEditor::Constants::C_HTMLEDITOR); connect(d->editorWidget, SIGNAL(contentModified()), d->file, SLOT(modified())); connect(d->editorWidget, SIGNAL(titleChanged(QString)), this, SLOT(slotTitleChanged(QString))); connect(d->editorWidget, SIGNAL(contentModified()), this, SIGNAL(changed())); } HtmlEditor::~HtmlEditor() { delete d; } 

Three simple functions for mapping.
 QWidget* HtmlEditor::widget() { return d->editorWidget; } Core::Context HtmlEditor::context() const { return d->context; } Core::IFile* HtmlEditor::file() { return d->file; } 

The createNew () method resets the contents of HtmlEditorWidget and HtmlFile.
 bool HtmlEditor::createNew(const QString& contents) { Q_UNUSED(contents); d->editorWidget->setContent(QByteArray()); d->file->setFilename(QString()); return true; } 

The open () method passes the name of the file to open to HtmlFile.
 bool HtmlEditor::open(const QString &fileName) { return d->file->open(fileName); } 

Returns the type of editor.
 const char* HtmlEditor::kind() const { return HTMLEditor::Constants::C_HTMLEDITOR; } 

displayName is used to display the file name in the ComboBox.
 QString HtmlEditor::displayName() const { return d->displayName; } void HtmlEditor::setDisplayName(const QString& title) { if(d->displayName == title) return; d->displayName = title; emit changed(); } 

The remaining methods are not so important.
 bool HtmlEditor::duplicateSupported() const { return false; } Core::IEditor* HtmlEditor::duplicate(QWidget* parent) { Q_UNUSED(parent); return 0; } QByteArray HtmlEditor::saveState() const { return QByteArray(); } bool HtmlEditor::restoreState(const QByteArray& state) { Q_UNUSED(state); return false; } QToolBar* HtmlEditor::toolBar() { return 0; } bool HtmlEditor::isTemporary() const { return false; } QString HtmlEditor::id() const { return QString(); } 

5.2.4 Implementing Core :: IEditorFactory

The HtmlEditorFactory class will implement the Core :: IEditorFactory interface.
 #ifndef HTMLEDITORFACTORY_H #define HTMLEDITORFACTORY_H #include <coreplugin/editormanager/ieditorfactory.h> #include <QStringList> #include "htmleditorplugin.h" struct HtmlEditorFactoryData; class HtmlEditorFactory : public Core::IEditorFactory { Q_OBJECT public: HtmlEditorFactory(HTMLEditor::Internal::HTMLEditorPlugin* owner); ~HtmlEditorFactory(); QStringList mimeTypes() const; QString kind() const; Core::IEditor* createEditor(QWidget* parent); Core::IFile* open(const QString &fileName); private: HtmlEditorFactoryData* d; }; #endif // HTMLEDITORFACTORY_H 

The HtmlEditorFactoryData structure contains the private data of the HtmlEditorFactory class. The constructor of this structure initializes it with the mime type contained in the HTMLEditor :: Constants :: C_HTMLEDITOR_MIMETYPE. It also initializes the editor type with the value HTMLEditor :: Constants :: C_HTMLEDITOR. All constants are declared in the htmleditorconstants.h file.
 #include "htmleditorfactory.h" #include "htmleditorconstants.h" #include <coreplugin/editormanager/editormanager.h> #include "htmleditorwidget.h" #include <QStringList> #include <coreplugin/editormanager/ieditor.h> #include "htmleditor.h" struct HtmlEditorFactoryData { HtmlEditorFactoryData() : kind(HTMLEditor::Constants::C_HTMLEDITOR) { mimeTypes << QString(HTMLEditor::Constants::C_HTMLEDITOR_MIMETYPE); } QString kind; QStringList mimeTypes; }; 

Next, a drop of simple familiar methods.
 HtmlEditorFactory::HtmlEditorFactory(HTMLEditor::Internal::HTMLEditorPlugin* owner) :Core::IEditorFactory(owner) { d = new HtmlEditorFactoryData; } HtmlEditorFactory::~HtmlEditorFactory() { delete d; } QStringList HtmlEditorFactory::mimeTypes() const { return d->mimeTypes; } QString HtmlEditorFactory::kind() const { return d->kind; } 

The open () method transfers the file name to the editors' manager, which in turn either creates and opens a new editor, or opens one of the existing ones.
 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; } 

This method returns an instance of the HtmlEditor class.
 Core::IEditor* HtmlEditorFactory::createEditor(QWidget* parent) { HtmlEditorWidget* editorWidget = new HtmlEditorWidget(parent); return new HtmlEditor(editorWidget); } 

5.2.5 Main extension class

We implement the HtmlEditorPlugin class using the knowledge that we gained in the second part. True, we’ll change the initialize () method a bit.
 bool HTMLEditorPlugin::initialize(const QStringList &arguments, QString *errorString) { Q_UNUSED(arguments) Q_UNUSED(errorString) Core::ICore* core = Core::ICore::instance(); Core::MimeDatabase* mdb = core->mimeDatabase(); QString *errMsg = new QString(); if(!mdb->addMimeTypes(":/text-html-mimetype.xml", errMsg)) return false; addAutoReleasedObject(new HtmlEditorFactory(this)); return true; } 

6 Adding Sidebar


The navigation bar in Qt Creator is an area that may contain components such as:

In addition, we can simultaneously use not one, but several navigation bars.
image

Remember, we have already tried to add a nameplate in one of the previous sections to the navigation bar? Now we will do almost the same thing, but we will complicate the task a little.

6.1 Interface Core :: INavigationWidgetFactory

So, as far as we remember, to add a new navigation bar, we need to create our own implementation of the Core :: INavigationWidgetFactory interface, located in the plugins / corelib / inavigationwidgetfactory.h file.

6.2 Preparing the widget for the panel

Qt Creator. FTPBrowser. , , . , FtpDirModel.

:
image
FTP- FTP Path Go, FTP QTreeView . FTPBrowserForm. , . .
 #include <coreplugin/inavigationwidgetfactory.h> class FtpViewNavigationWidgetFactory : public Core::INavigationWidgetFactory { public: FtpViewNavigationWidgetFactory() { } ~FtpViewNavigationWidgetFactory() { } Core::NavigationView createWidget(); QString displayName() const { return "FTP View"; } int priority() const { return 0; } QString id() const { return "Spreadsheet"; } }; 

createWidget() . displayName() .
 Core::NavigationView FtpViewNavigationWidgetFactory::createWidget() { Core::NavigationView view; view.widget = new FTPBrowserForm; return view; } 

Here's what we got:
image

6.3

/ Qt Creator. , INavigationWidgetFactory :
 void saveSettings(int position, QWidget *widget); void restoreSettings(int position, QWidget *widget); 

, . :
 void FtpViewNavigationWidgetFactory::saveSettings(int position, QWidget *widget) { FTPBrowserForm* ftpExp = qobject_cast<FTPBrowserForm*>(widget); if(!ftpExp) return; QSettings *settings = Core::ICore::instance()->settings(); settings->setValue("FtpView.URL", ftpExp->url().toString()); } void FtpViewNavigationWidgetFactory::restoreSettings(int position, QWidget *widget) { FTPBrowserForm* ftpExp = qobject_cast<FTPBrowserForm*>(widget); if(!ftpExp) return; QSettings *settings = Core::ICore::instance()->settings(); QString urlStr = settings->value("FtpView.URL").toString(); ftpExp->setUrl( QUrl(urlStr) ); } 

, — .

… … … =)

Source: https://habr.com/ru/post/135289/


All Articles