Development/Tutorials/KDevelop/Creating a class template: Difference between revisions

From KDE TechBase
(Mark for updating)
 
(14 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{Construction}}
{{Review|Port to KF5 and KDev5}}
 
{{Warning|The functionaly described here requires an unreleased version of KDevelop. If you are not using the latest development version, while you can write class templates, you will not be able to use them.}}


{{TutorialBrowser|
{{TutorialBrowser|
Line 56: Line 58:
Now, let's move on to the class declaration.
Now, let's move on to the class declaration.


=== Template content files ===
==== Template header ====
 
In most free software projects, source files start with a license header. This header varies with the chosen license, author, and the programming language's comment characters. When using our template, the user choose one of the suggested licenses or write his own. In either case, the full text of the license header is available to the template as the <tt>license</tt> variable.
 
Since C++ has block-style comments, we could write the license simply as
 
<syntaxhighlight lang="cpp">
/*
{{ license }}
*/
</syntaxhighlight>


We must decide what kind of project do we want to create. For the needs of the tutorial, we will keep it simple and restrict ourselves to four files: Header and implementation for a class, a {{path|main.cpp}} file, and a {{path|CMakeLists.txt}} file for building. It is also recommended to add a {{path|.kdev4}} project file, so that '''KDevelop''' will know what version control system to use.
However, it might be easier to read if the license had some kind of formatting. For this purpose, '''KDevPlatform''' includes a template filter library that makes it possible to write a nicely-formatted license header. We can use it like this:


==== Class header file ====
<syntaxhighlight lang="cpp">
{% load kdev_filters %}
/*
{{ license|lines_prepend:" * " }}
*/
</syntaxhighlight>


As explained in the [[http://techbase.kde.org/Projects/KDevelop4/Project_template_specification#Variables|template variables]] section, '''KDevelop''' will replace certain placeholders with suitable values. We will use some of this placeholders now.  
The <tt>lines_prened</tt> filter prepends its ''argument'' (in our case " * ") to every line of its input (in our case the license). This can produce licenses like the following


The placeholders are replaced in both file names and contents. In this tutorial, we will create a class with the same name as the application itself, and use the convention of lowercase file names. The header and implementation files will thus be named {{path|%{APPNAMELC}.h}} and {{path|%{APPNAMELC}.cpp}}, respectively.
{{Output|1=<nowiki>
/*
* This file is part of KDevelop
* Copyright 2012 Example Developer <developer@example.org>
*/
</nowiki>}}


The header file contents can also include placeholders. For this tutorial, let's create an empty class with a constructor and a destructor
==== Include guards ====


<syntaxhighlight lang="cpp-qt">
If our file is to be included somewhere, it is good practice to provide an include guard macro. '''KDevPlatform''' ships a convenience template for generating such a macro called <tt>include_guard_cpp.txt</tt>. We can use it like this
#ifndef %{APPNAMEUC}_H
#define %{APPNAMEUC}_H


class %{APPNAMEID}
<syntaxhighlight lang="smarty">
{
#ifndef {% include "include_guard_cpp.txt" %}
public:
#define {% include "include_guard_cpp.txt" %}
    %{APPNAMEID}();
    ~%{APPNAMEID}();
};


#endif // %{APPNAMEUC}_H
// Class declaration goes here


#endif // {% include "include_guard_cpp.txt" %}
</syntaxhighlight>
</syntaxhighlight>


==== Class implementation ====
{{Note|The included template will expand into an uppercase class name with an appended <tt>_H</tt>, which could also be achieved by typing <code><nowiki>{{ name|upper }}_H</nowiki></code>. However, the include guard template also supports namespaces.}}
{{Note|See the [https://docs.djangoproject.com/en/1.8/ref/templates/builtins/#include Django documentation] for details about the <tt>include</tt> command}}


The correspending implementation must include the header and implement the two declared methods.
==== Class declaration ====


<syntaxhighlight lang="cpp-qt">
Information about inheritance is stored in the <tt>baseClasses</tt> variable. It is a list that can be iterated using a <tt>for</tt> loop. Each list element has two properties, <tt>inheritanceMode</tt> and <tt>baseType</tt>, which can be accessed using the dot notation, like class members in C++ or Python.  
#include "%{APPNAMELC}.h"


%{APPNAMEID}::%{APPNAMEID}()
<syntaxhighlight lang="cpp">
class {{ name }}
{% if baseClasses %}
    : {% for b in baseClasses %}b.inheritanceMode b.baseType{% if not forloop.last %}, {% endif %}{% endfor %}
{% endif %}
{
{
</syntaxhighlight>


}
If our new class inherites from KFoo and KBar, and both inheritances are public, the output from such a template looks like


%{APPNAMEID}::~%{APPNAMEID}()
{{Output|1=<nowiki>
class Example
    : public KFoo, public KBar
{
{
</nowiki>}}
The template text could also be written in one line, so that the inheritance declarations (as possible the opening brace) would be on the same line as the class name. It is only written out this way in the tutorial for better readability. Both versions are valid C++.
==== Members ====
Classes can have two kinds of members: variables and functions. They are available to templates as <tt>members</tt> and <tt>functions</tt> variables, respectively. Both are lists and are intended to be iterated.
For simplicity, we will ignore access modes and various function modifiers (const, virtual, static, etc).


}
<syntaxhighlight lang="smarty">
{% for f in functions %}
  {% with f.arguments as arguments %}
    {{ f.returnType|default:"void" }} {{ f.name }}({% include "arguments_types_names.txt" %});
  {% endwith %}
{% endfor %}
</syntaxhighlight>
</syntaxhighlight>


Placeholders are always replaced in the same way, so the <tt>#include</tt> line will always match the header file.
Every function in C++ has a return type, if it returns nothing that type is <tt>void</tt>, which is what the <tt>default</tt> filter assures. Next, we have included another templates, called <tt>arguments_types_names.txt</tt>, which creates an argument list for language that require both argument types and names to be specified. This template requires a variables called <tt>arguments</tt>, which we created within the <tt>{% with %}</tt> statement.  


== Installing the template ==
<syntaxhighlight lang="smarty">
{% for m in members %}
  {{ m.type }} {{ m.name }};
{% endfor %}
</syntaxhighlight>


Now we have all the template contents prepared. We only need to compress the directory and make it available to '''KDevelop'''.  
Member variables are simple, they only require a type and a name.


There are three ways for doing that:
==== Whole header ====
* compressing and loading it manually
* loading it from within '''KDevelop'''
* using a CMake macro.


=== Manually ===
The entire header file looks like this


First create an archive out of the {{path|kdev_tutorial}} directory. Many archive formats are accepted, but for best compatibility use {{path|.zip}} in Windows and {{path|.tar.bz2}} everywhere else. In '''Dolphin''', this can be achieved by right clicking within the directory, choosing <menuchoice>Compress -> Compress To...</menuchoice>, and entering the filename as {{path|kdev_tutorial.tar.bz2}}.
<syntaxhighlight lang="smarty">
{% load kdev_filters %}
/*
{{ license|lines_prepend:" * " }}
*/


Now copy the new archive somewhere where '''KDevelop''' will find it. It looks for template archives in the {{path|${PREFIX}/share/apps/kdevappwizard/templates}}, where prefix is either the system directory where KDE is installed (for example {{path|/usr}}) or the the local KDE configuration (for example {{path|~/.kde}} or {{path|~/.kde4}}). Copying our template to either is fine, choose dependeng on whether you want the template available only to you or all users on your computer.
#ifndef {% include "include_guard_cpp.txt" %}
#define {% include "include_guard_cpp.txt" %}


After a run of <tt>kbuildsycoca4</tt>, '''KDevelop''' should pick up and offer you new template when creating a new project.  
class {{ name }}
{% if baseClasses %}
    : {% for b in baseClasses %}b.inheritanceMode b.baseType{% if not forloop.last %}, {% endif %}{% endfor %}
{% endif %}
{
public:
{% for f in functions %}
  {% with f.arguments as arguments %}
    {{ f.returnType|default:"void" }} {{ f.name }}({% include "arguments_types_names.txt" %});
  {% endwith %}
{% endfor %}


=== From '''KDevelop''' ===
{% for m in members %}
  {{ m.type }} {{ m.name }};
{% endfor %}
};


{{Note|This method requires an unreleased version of KDevelop. If you are not using the latest development version, use one of the other two options. }}
#endif // {% include "include_guard_cpp.txt" %}
</syntaxhighlight>


You can have '''KDevelop''' do all this. Select <menuchoice>Project -> New From Template</menuchoice>, then in the dialog click the <menuchoice>Load Template From File</menuchoice>. Navigate to the {{path|kdev_tutorial}} directory, and open the {{path|kdev_tutorial.kdevtemplate}} file. Your template directory will be compressed and install into the local directory (usually {{path|~/.kde/share/apps/kdevappwizard/templates}}).
=== The implementation file ===


=== With CMake ===
Class implementation has the same license as the header, and needs no include guard. Instead, it has to include the header file and contain bodies for member functions.


'''KDevPlatform''' includes a CMake macro that takes care of compressing and installing templates. This has the disadvantage of requiring a large library for installing a simple archive file, but may be useful especially for project that already depend on it. Alternatively, you may copy [https://projects.kde.org/projects/extragear/kdevelop/kdevplatform/repository/revisions/master/entry/cmake/modules/KDevPlatformMacros.cmake KDevPlatformMacros.cmake] and put it into your project.  
Including the header can be done using variables. For every output file in the template, '''KDevelop''' adds two variables: <tt>output_file_foo</tt> with the relative path to file Foo, and <tt>output_file_foo_absolute</tt> with the absolute path. For C++ includes, we will use the relative path.


The macro requires that each template is in a separate directory, so create a new {{path|kdev_tutorial_templates}} directory and move {{path|kdev_tutorial}} into it. Then add the following {{path|CMakeLists.txt}} file to the top-level directory
Member functions difinitions are very similar to their declarations, except that static and virtual modifiers are dropped and the class name is prepended to function names. Therefore we can copy the function declaration code and only make a small modification.  


<syntaxhighlight lang="cmake">
The whole implementation is below
project(kdevelop_template_tutorial)


find_package(KDE4 REQUIRED)
<syntaxhighlight lang="smarty">
find_package(KDevPlatform REQUIRED)
{% load kdev_filters %}
/*
{{ license|lines_prepend: * }}
*/


set(TEMPLATE_DIRS kdev_tutorial)
#include {{ output_file_header }}
kdevplatform_add_app_templates(${TEMPLATE_DIRS})
 
{% for f in functions %}
  {% with f.arguments as arguments %}
    {{ f.returnType|default:"void" }} {{ name }}::{{ f.name }}({% include "arguments_types_names.txt" %});
  {% endwith %}
  {
 
  }
{% endfor %}
</syntaxhighlight>
</syntaxhighlight>


Build and install the project with the usual CMake steps
The function body is left empty and has to be implemented by the user.
 
== Installing the template ==


{{Input|1=<nowiki>
Now we have all the template contents prepared. We only need to compress the directory and make it available to '''KDevelop'''.  
mkdir build
 
cd build
This can be done in a similar way as installing [[special:myLanguage/Development/Tutorials/KDevelop/Creating_a_project_template#Installing_the_template|project templates]]. The only differences are:
cmake ..
make
make install
</nowiki>}}


Note that this will install the template into {{path|<nowiki>${CMAKE_INSTALL_PREFIX}</nowiki>}}, for which you may need root privileges.
* templates should be saved into {{path|1=<nowiki>${PREFIX}/share/apps/kdevfiletemplates/templates</nowiki>}}
* they can be loaded from the <menuchoice>Code -> Create class from template</menuchoice> dialog
* the CMake macro is named <tt>kdevplatform_add_file_templates</tt> instead of <tt>kdevplatform_add_app_templates</tt>.

Latest revision as of 12:33, 31 May 2019

Warning
This page needs a review and probably holds information that needs to be fixed.

Parts to be reviewed:

Port to KF5 and KDev5
Warning
The functionaly described here requires an unreleased version of KDevelop. If you are not using the latest development version, while you can write class templates, you will not be able to use them.


Creating a project template
Tutorial Series   KDevelop Templates
Previous   Grantlee for theme artists, Django template language
What's Next  
Further Reading   Class template specification

Introduction

In this tutorial, we will create a template for a basic C++ class. It will demonstrate using the Grantlee template language, as well features specific to KDevelop class templates.

We chose C++ because even the simplest class definition often consists of two separate files, header and implementation. It is possible to include any number of files in a class template (there should be at least one, obviously).

Creating the directory structure

Note
There is a project template for a C++ class template available here. The resulting project is similar to what we will create here. You may use it to create the files but still follow this tutorial for explanation.


We will start with an empty directory. Name it something unique, like kdev_class_tutorial.

The description file

Every class template needs a description file. It is a regular desktop file with an extension of .desktop. Its base name must match the name of the generated archive, so let's call it kdev_class_tutorial.desktop. Paste in these contents:

[General]
Name=Tutorial
Comment=An example C++ class template, suitable for a tutorial
Category=C++
Files=Header,Implementation

[Header]
Name=Header
File=class.h
OutputFile={{ name }}.h

[Implementation]
Name=Implementation
File=class.cpp
OutputFile={{ name }}.cpp

You can see there are three sections in the description file: one for general properties, and one for each output file. In order for a file to be generated, it has to be listed in the Files entry, as well as have its own section with the same name.

In the output file sections, we already used template variables: {{ name }} will be replaced with the class name. The actual generated header file will not be {{ name }}.h, but rather Exmample.h, assuming we name our class Example.

The class header file

Now, let's move on to the class declaration.

Template header

In most free software projects, source files start with a license header. This header varies with the chosen license, author, and the programming language's comment characters. When using our template, the user choose one of the suggested licenses or write his own. In either case, the full text of the license header is available to the template as the license variable.

Since C++ has block-style comments, we could write the license simply as

/*
{{ license }}
*/

However, it might be easier to read if the license had some kind of formatting. For this purpose, KDevPlatform includes a template filter library that makes it possible to write a nicely-formatted license header. We can use it like this:

{% load kdev_filters %}
/*
{{ license|lines_prepend:" * " }}
 */

The lines_prened filter prepends its argument (in our case " * ") to every line of its input (in our case the license). This can produce licenses like the following

/*
 * This file is part of KDevelop
 * Copyright 2012 Example Developer <[email protected]>
 */

Include guards

If our file is to be included somewhere, it is good practice to provide an include guard macro. KDevPlatform ships a convenience template for generating such a macro called include_guard_cpp.txt. We can use it like this

#ifndef {% include "include_guard_cpp.txt" %}
#define {% include "include_guard_cpp.txt" %}

// Class declaration goes here

#endif // {% include "include_guard_cpp.txt" %}
Note
The included template will expand into an uppercase class name with an appended _H, which could also be achieved by typing {{ name|upper }}_H. However, the include guard template also supports namespaces.
Note
See the Django documentation for details about the include command


Class declaration

Information about inheritance is stored in the baseClasses variable. It is a list that can be iterated using a for loop. Each list element has two properties, inheritanceMode and baseType, which can be accessed using the dot notation, like class members in C++ or Python.

class {{ name }}
{% if baseClasses %}
    : {% for b in baseClasses %}b.inheritanceMode b.baseType{% if not forloop.last %}, {% endif %}{% endfor %}
{% endif %}
{

If our new class inherites from KFoo and KBar, and both inheritances are public, the output from such a template looks like

class Example
    : public KFoo, public KBar
{

The template text could also be written in one line, so that the inheritance declarations (as possible the opening brace) would be on the same line as the class name. It is only written out this way in the tutorial for better readability. Both versions are valid C++.

Members

Classes can have two kinds of members: variables and functions. They are available to templates as members and functions variables, respectively. Both are lists and are intended to be iterated.

For simplicity, we will ignore access modes and various function modifiers (const, virtual, static, etc).

{% for f in functions %}
  {% with f.arguments as arguments %}
    {{ f.returnType|default:"void" }} {{ f.name }}({% include "arguments_types_names.txt" %});
  {% endwith %}
{% endfor %}

Every function in C++ has a return type, if it returns nothing that type is void, which is what the default filter assures. Next, we have included another templates, called arguments_types_names.txt, which creates an argument list for language that require both argument types and names to be specified. This template requires a variables called arguments, which we created within the {% with %} statement.

{% for m in members %}
  {{ m.type }} {{ m.name }};
{% endfor %}

Member variables are simple, they only require a type and a name.

Whole header

The entire header file looks like this

{% load kdev_filters %}
/*
{{ license|lines_prepend:" * " }}
 */

#ifndef {% include "include_guard_cpp.txt" %}
#define {% include "include_guard_cpp.txt" %}

class {{ name }}
{% if baseClasses %}
    : {% for b in baseClasses %}b.inheritanceMode b.baseType{% if not forloop.last %}, {% endif %}{% endfor %}
{% endif %}
{
public:
{% for f in functions %}
  {% with f.arguments as arguments %}
    {{ f.returnType|default:"void" }} {{ f.name }}({% include "arguments_types_names.txt" %});
  {% endwith %}
{% endfor %}

{% for m in members %}
  {{ m.type }} {{ m.name }};
{% endfor %}
};

#endif // {% include "include_guard_cpp.txt" %}

The implementation file

Class implementation has the same license as the header, and needs no include guard. Instead, it has to include the header file and contain bodies for member functions.

Including the header can be done using variables. For every output file in the template, KDevelop adds two variables: output_file_foo with the relative path to file Foo, and output_file_foo_absolute with the absolute path. For C++ includes, we will use the relative path.

Member functions difinitions are very similar to their declarations, except that static and virtual modifiers are dropped and the class name is prepended to function names. Therefore we can copy the function declaration code and only make a small modification.

The whole implementation is below

{% load kdev_filters %}
/*
{{ license|lines_prepend: * }}
 */

#include {{ output_file_header }}

{% for f in functions %}
  {% with f.arguments as arguments %}
    {{ f.returnType|default:"void" }} {{ name }}::{{ f.name }}({% include "arguments_types_names.txt" %});
  {% endwith %}
  {

  }
{% endfor %}

The function body is left empty and has to be implemented by the user.

Installing the template

Now we have all the template contents prepared. We only need to compress the directory and make it available to KDevelop.

This can be done in a similar way as installing project templates. The only differences are:

  • templates should be saved into ${PREFIX}/share/apps/kdevfiletemplates/templates
  • they can be loaded from the Code -> Create class from template dialog
  • the CMake macro is named kdevplatform_add_file_templates instead of kdevplatform_add_app_templates.