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.
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:
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:
AnatomyDemo();
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 slots:
virtual void button_clicked();
protected:
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()
{}
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[] =
{
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
{
KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
mainWin = new AnatomyDemo();
app.setMainWidget( mainWin );
mainWin->show();
args->clear();
}
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:
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.
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:
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>
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 );
connect( button, SIGNAL( clicked() ), this, SLOT( button_clicked() ) );
}
AnatomyDemoWidgetBase::~AnatomyDemoWidgetBase()
{
}
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:
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:
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.