Click here to Skip to main content
15,867,488 members
Articles / Programming Languages / C++

Anatomy of a Simple KDE Designer Application

Rate me:
Please Sign up or sign in to vote.
4.85/5 (10 votes)
3 Jul 2008GPL38 min read 25.8K   14   1
A complete beginners guide to a KDE application.

Image 1

Introduction

In this article, we are going to look at the Simple Designer based KDE Application template that many people will use for their first venture into KDE programming with KDevelop. As with any other programming environment, it helps you to write your own programs if you understand the structure of the application that you are dealing with. So, with that in mind, this article will explain not only the files generated by the project wizard when you first create a project, but what files are generated when you compile a project and how they all fit together. We will, of course, be concentrating only on files that are directly relevant to programming your project, and not list the contents of configuration files etc.

Creating the Project

Open up KDevelop and go to Projects/New Project/C++/KDE and select Simple Designer Based KDE Application.

Image 2

You then set the application name and location, and click on the Next button. You will then get the Options dialog that allows you to set user preferences such as your name and email address. The Next dialog will be for the source control which we will not be using for this project. And finally, you'll be given the templates for the file headers to review. Just click through these until you get the Finish button.

When you click on the Finish button, the wizard will go away and KDevelop will open your project. The main window will contain the project main file: in this case, the AnatomyDemo.cpp file. On the right of your screen will be the project information which looks like:

Image 3

As you can see, the Automake Manager is just a different name for the project/solution window.

The Document View Architecture

The Simple Designer Based KDE Application has a document view architecture in that you have a KMainWindow derived class that contains a view class; in this case, a widget or widget derived classes.

There are two header files in the project; these are under the heading (header in noinst) in the Automake Manager. The first is the AnatomyDemo.h file, which reads:

class AnatomyDemo : public KMainWindow
{
    Q_OBJECT
public:
    /**
     * Default Constructor
     */
    AnatomyDemo();

    /**
     * Default Destructor
     */
    virtual ~AnatomyDemo();
};

All we have here is a constructor and destructor, but it is this class that controls the layout of the application and the application peripherals such as status bar, menu, and tool bars. This is more apparent when we look at the CPP file:

AnatomyDemo::AnatomyDemo() : KMainWindow( 0, "AnatomyDemo" )
{
    setCentralWidget( new AnatomyDemoWidget( this ) );
}

AnatomyDemo::~AnatomyDemo()
{
}

Although all we have is the constructor and destructor, you can clearly see that the only function call in the constructor is the call to setCentralWidget, with a class called AnatomyDemoWidget. The this parameter sets the parent.

In the AnatomyDemoWidget.h file, we see:

class AnatomyDemoWidget : public AnatomyDemoWidgetBase
{
    Q_OBJECT

public:
    AnatomyDemoWidget(QWidget* parent = 0, 
               const char* name = 0, WFlags fl = 0 );
    ~AnatomyDemoWidget();
    /*$PUBLIC_FUNCTIONS$*/

public slots:
    /*$PUBLIC_SLOTS$*/
    virtual void button_clicked();

protected:
    /*$PROTECTED_FUNCTIONS$*/

protected slots:
    /*$PROTECTED_SLOTS$*/

};

As with the above, this class at the moment is nothing more than a constructor and destructor with one function (it is called a slot here, but we'll get to that, and from this class, perspective, it is a standard function), the button_clicked() function. This is the hello world part of the program which we can see if we look at the AnatomyDemoWidget.cpp file.

AnatomyDemoWidget::AnatomyDemoWidget(QWidget* parent, const char* name, WFlags fl)
        : AnatomyDemoWidgetBase(parent,name,fl)
{}

AnatomyDemoWidget::~AnatomyDemoWidget()
{}

/*$SPECIALIZATION$*/
void AnatomyDemoWidget::button_clicked()
{
    if ( label->text().isEmpty() )
    {
        label->setText( "Hello World!" );
    }
    else
    {
        label->clear();
    }
}

As well as the constructor and destructor, we can see the implementation of the button_clicked function. If there is no text in the label, it sets it to “Hello World”, and if it contains any text, it clears it.

Before seeing it in action though, there are a couple of other files we haven't looked at. The main.cpp file, which is,

static const char description[] =
    I18N_NOOP("A KDE KPart Application");

static const char version[] = "0.1";

static KCmdLineOptions options[] =
{
//    { "+[URL]", I18N_NOOP( "Document to open" ), 0 },
    KCmdLineLastOption
};

int main(int argc, char **argv)
{
    KAboutData about("anatomydemo", I18N_NOOP("AnatomyDemo"),
                     version, description,
                     KAboutData::License_GPL, "(C) 2008 pseudonym67", 
                     0, 0, "pseudonym67@hotmail.com");
    about.addAuthor( "pseudonym67", 0, "pseudonym67@hotmail.com" );
    KCmdLineArgs::init(argc, argv, &about);
    KCmdLineArgs::addCmdLineOptions( options );
    KApplication app;
    AnatomyDemo *mainWin = 0;

    if (app.isRestored())
    {
        RESTORE(AnatomyDemo);
    }
    else
    {
        // no session.. just start up normally
        KCmdLineArgs *args = KCmdLineArgs::parsedArgs();

        /// @todo do something with the command line args here

        mainWin = new AnatomyDemo();
        app.setMainWidget( mainWin );
        mainWin->show();

        args->clear();
    }

    // mainWin has WDestructiveClose flag by default, so it will delete itself.
    return app.exec();
}

As you would expect, main is the entry point into the program and deals with the About dialog and any command line options. It's main function is to instantiate the KApplication object and set the KMainWindow as the main widget for the application.

The final file is the anatomydemowidgetbase.ui file, which is an XML file that is used by the form/widget designer. If we open it, we get:

Image 4

which is your standard drag and drop form/widget designer interface. If you want to see the XML, put the KXML Editor on your system if it's not already, and use it to open the .ui file.

Image 5

Building the Application

In order to build the application, go to the Build menu and select the Build Project option. You will then see a dialog that tells you that there are no make files or configuration files. These are files required for the build, and are generated automatically by KDevelop. Unless you are experienced in Linux command line programming, let KDevelop handle them for you. Run them and wait.

Then, select Start in the Debug menu and you'll get:

Image 6

If you click on the "Click Me!" button, the Hello World text will show on the dialog. So, what just happened? You may have noticed that while we have a file called anatomydemowidgetbase.ui, we don't have a class implementation of the same name, but we do have:

class AnatomyDemoWidget : public AnatomyDemoWidgetBase
{
    Q_OBJECT

in our anatomydemowidget.h file. Also, what is a slot and how did the "Click Me!" button work? The answers to these questions are things that the Qt library does in the background, so in order to answer these questions, we need to know about some things called the Meta Object Compiler and the User Interface Compiler. First, we'll look at the User Interface Compiler,

User Interface Compiler

The User Interface Compiler is fairly straightforward in that it takes the .ui file, in this case, the anatomydemowidgetbase.ui file, and generates a C++ class that implements the form exactly as you have defined it in the GUI. So, in the debug/src directory, we will now have the files anatomydemowidgetbase.h and anatomydemowidgetbase.cpp. The header file reads:

#ifndef ANATOMYDEMOWIDGETBASE_H
#define ANATOMYDEMOWIDGETBASE_H

#include <qvariant.h>
#include <qwidget.h>

class QVBoxLayout;
class QHBoxLayout;
class QGridLayout;
class QSpacerItem;
class QPushButton;
class QLabel;

class AnatomyDemoWidgetBase : public QWidget
{
    Q_OBJECT

public:
    AnatomyDemoWidgetBase( QWidget* parent = 0, 
               const char* name = 0, WFlags fl = 0 );
    ~AnatomyDemoWidgetBase();

    QPushButton* button;
    QLabel* label;

public slots:
    virtual void button_clicked();

protected:
    QGridLayout* anatomydemowidgetbaseLayout;

protected slots:
    virtual void languageChange();

};

#endif // ANATOMYDEMOWIDGETBASE_H

And, this is the class that we inherit from the AnatomyDemoWidget class.

While the header file reads:

#include "anatomydemowidgetbase.h"

#include <qvariant.h>
#include <qpushbutton.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qtooltip.h>
#include <qwhatsthis.h>

/*
 *  Constructs a AnatomyDemoWidgetBase as a child of 'parent', with the
 *  name 'name' and widget flags set to 'f'.
 */
AnatomyDemoWidgetBase::AnatomyDemoWidgetBase( QWidget* parent, 
                       const char* name, WFlags fl )
    : QWidget( parent, name, fl )
{
    if ( !name )
    setName( "anatomydemowidgetbase" );
    anatomydemowidgetbaseLayout = new QGridLayout( this, 1, 1, 11, 6, 
       "anatomydemowidgetbaseLayout"); 

    button = new QPushButton( this, "button" );

    anatomydemowidgetbaseLayout->addWidget( button, 1, 0 );

    label = new QLabel( this, "label" );

    anatomydemowidgetbaseLayout->addWidget( label, 0, 0 );
    languageChange();
    resize( QSize(220, 133).expandedTo(minimumSizeHint()) );
    clearWState( WState_Polished );

    // signals and slots connections
    connect( button, SIGNAL( clicked() ), this, SLOT( button_clicked() ) );
}

/*
 *  Destroys the object and frees any allocated resources
 */
AnatomyDemoWidgetBase::~AnatomyDemoWidgetBase()
{
    // no need to delete child widgets, Qt does it all for us
}

/*
 *  Sets the strings of the subwidgets using the current
 *  language.
 */
void AnatomyDemoWidgetBase::languageChange()
{
    setCaption( QString::null );
    button->setText( tr2i18n( "Click Me!" ) );
    label->setText( QString::null );
}

void AnatomyDemoWidgetBase::button_clicked()
{
    qWarning( "AnatomyDemoWidgetBase::button_clicked(): Not implemented yet" );
}

#include "anatomydemowidgetbase.moc"

As you can see, this is pretty much what you would expect from a file that implements a form. The things of note are the connect function in the constructor and the file line at the end of the file; these are both explained below.

Meta Object Compiler

The Meta Object Compiler is the Qt type management system that provides a few benefits to the developer. Its main function is to read the C++ source files before they are compiled, and if the class declaration contains the Q_OBJECT macro, it will generate a file called "filename.moc", which in the case of the above would be the file anatomydemowidget.moc, which looks like this:

const char *AnatomyDemoWidget::className() const
{
    return "AnatomyDemoWidget";
}

QMetaObject *AnatomyDemoWidget::metaObj = 0;
static QMetaObjectCleanUp cleanUp_AnatomyDemoWidget( "AnatomyDemoWidget",
       &AnatomyDemoWidget::staticMetaObject );

#ifndef QT_NO_TRANSLATION
QString AnatomyDemoWidget::tr( const char *s, const char *c )
{
    if ( qApp )
    return qApp->translate( "AnatomyDemoWidget", s, c,
        QApplication::DefaultCodec );
    else
    return QString::fromLatin1( s );
}
#ifndef QT_NO_TRANSLATION_UTF8
QString AnatomyDemoWidget::trUtf8( const char *s, const char *c )
{
    if ( qApp )
    return qApp->translate( "AnatomyDemoWidget", s, c,
        QApplication::UnicodeUTF8 );
    else
    return QString::fromUtf8( s );
}
#endif // QT_NO_TRANSLATION_UTF8

#endif // QT_NO_TRANSLATION

QMetaObject* AnatomyDemoWidget::staticMetaObject()
{
    if ( metaObj )
    return metaObj;
    QMetaObject* parentObject = AnatomyDemoWidgetBase::staticMetaObject();
    static const QUMethod slot_0 = {"button_clicked", 0, 0 };
    static const QMetaData slot_tbl[] = {
    { "button_clicked()", &slot_0, QMetaData::Public }
    };
    metaObj = QMetaObject::new_metaobject(
    "AnatomyDemoWidget", parentObject,
    slot_tbl, 1,
    0, 0,
#ifndef QT_NO_PROPERTIES
    0, 0,
    0, 0,
#endif // QT_NO_PROPERTIES
    0, 0 );
    cleanUp_AnatomyDemoWidget.setMetaObject( metaObj );
    return metaObj;
}

void* AnatomyDemoWidget::qt_cast( const char* clname )
{
    if ( !qstrcmp( clname, "AnatomyDemoWidget" ) )
    return this;
    return AnatomyDemoWidgetBase::qt_cast( clname );
}

bool AnatomyDemoWidget::qt_invoke( int _id, QUObject* _o )
{
    switch ( _id - staticMetaObject()->slotOffset() ) {
    case 0: button_clicked(); break;
    default:
    return AnatomyDemoWidgetBase::qt_invoke( _id, _o );
    }
    return TRUE;
}

bool AnatomyDemoWidget::qt_emit( int _id, QUObject* _o )
{
    return AnatomyDemoWidgetBase::qt_emit(_id,_o);
}
#ifndef QT_NO_PROPERTIES

bool AnatomyDemoWidget::qt_property( int id, int f, QVariant* v)
{
    return AnatomyDemoWidgetBase::qt_property( id, f, v);
}

bool AnatomyDemoWidget::qt_static_property( QObject* , 
       int , int , QVariant* ){ return FALSE; }
#endif // QT_NO_PROPERTIES

Once Q_OBJECT is declared in your class header file, you will get a .moc file for you program and your class will inherit at least its base from QObject. This is why nearly all Qt and KDE CPP files end with the line:

#include "filename".moc

The main points of interest in the .moc are the tr and trutf8 functions which help implement the Qt multilingual support.

The qt_property function allows you to use QT_PROPERTY macros in your class. Any property used inside the QT_PROPERTY macro will be editable/settable in the form/widget editor properties section.

Using the QButton header as an example, we get:

Q_OBJECT
Q_ENUMS( ToggleType ToggleState )
Q_PROPERTY( QString text READ text WRITE setText )
Q_PROPERTY( QPixmap pixmap READ pixmap WRITE setPixmap )
Q_PROPERTY( QKeySequence accel READ accel WRITE setAccel )
Q_PROPERTY( bool toggleButton READ isToggleButton )
Q_PROPERTY( ToggleType toggleType READ toggleType )
Q_PROPERTY( bool down READ isDown WRITE setDown
DESIGNABLE false  )
Q_PROPERTY( bool on READ isOn )
Q_PROPERTY( ToggleState toggleState READ state )
Q_PROPERTY( bool autoResize READ autoResize WRITE setAutoResize
 DESIGNABLE false )
Q_PROPERTY( bool autoRepeat READ autoRepeat WRITE setAutoRepeat )
Q_PROPERTY( bool exclusiveToggle READ isExclusiveToggle )

where you can see that it is also possible to explicitly exclude certain items from the designer.

The .moc file also sets up your signals and slots.

Signals and Slots

Signals and slots are Qt's way of communicating between classes. Signals are a type safe method of sending a signal/message from one class to any class that is interested. No class will automatically receive a signal from another class. Each class must request a connection to a specific signal by using the connect function.

In order to achieve this, there are two new keywords that we use in our class headers. These are the signals and slots keywords. We have already seen the slots keyword in the header file above:

public slots:
    /*$PUBLIC_SLOTS$*/
    virtual void button_clicked();

This is simply a place setting that declares what at the class level is a standard function. The slot implementation is in the CPP file:

void AnatomyDemoWidget::button_clicked()
{
    if ( label->text().isEmpty() )
    {
        label->setText( "Hello World!" );
    }
    else
    {
        label->clear();
    }
}

If we look at the UI for a second, specifically if we click on the button and look at the properties, we see there is a section called Signal Handlers:

Image 7

This section will show the available signals for any object on the form, and here we can set up a response slot/function.

These are defined in QButton.h as:

signals:
    void        pressed();
    void        released();
    void        clicked();
    void        toggled( bool );
    void        stateChanged( int );

Then, if we go back to the constructor of the above function, we see the connect function call, which reads:

connect( button, SIGNAL( clicked() ), this, SLOT( button_clicked() ) );

The connect function has a few variations, but mostly follows this pattern, which is connect( ObjectSendingSignal, SIGNAL( SignalToBeSent ), ObjectToReceiveTheSignal, SLOT(FunctionCalledOnSignal ) ).

Summary

This has been a quick guide to the anatomy of a Simple KDE application, focusing on what is generated by a simple project template, and the files that are created when you build it. For this reason, there is no source code to download as all the code is generated automatically by KDevelop and the associated Qt tools. The aim of this article is to enable people new to Qt and KDevelop programming to get started with some understanding of what is happening so they can get on with their own projects and not be sitting there scratching their heads wondering just what is going on.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralExcellent Pin
ComaWhite8626-Oct-08 0:23
ComaWhite8626-Oct-08 0:23 
Excellent tutorial mate. I can't wait to see more for KDE4 development ^_^.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.