Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Embedded Systems

Embedded Development With QT/Embedded

Matthias Kalle Dalheimer and Steffen Hansen

, March 01, 2002


Mar02: Embedded Development with Qt/Embedded

Kalle is president and CEO of Klarälvdalens Datakonsult AB. You can reach him at [email protected]. Steffen is a senior software engineer at Klarälvdalens Datakonsult AB. Like Kalle, he is a core team member of the KDE Project. You can reach him at [email protected].


Making Qt/Embedded Lean and Mean


When developing software for handheld computers such as the iPAQ, Palm, and Visor, you often face challenges that are at odds with each other. On one hand, users expect applications with resource-hungry GUIs that can be manipulated via stylus, virtual keyboard, and the like. On the other hand, you must contend with the space and processing constraints that are normal in the embedded world. In part due to issues such as these, Linux is increasingly becoming the preferred platform for embedded devices such as handheld computers. Not only is Linux resource friendly (and because these systems have little Flash and RAM, they need a resource- friendly solution) but it is also cost effective (there are no per-device royalties required when using Linux). In this article, we look at how you develop Linux-based applications for handheld devices using the Trolltech's C++ GUI Qt/Embedded toolkit (http://www.trolltech.com/), available for embedded devices under both the GPL and commercial licenses for UNIX/X11 and Linux.

What sets Qt/Embedded apart from other embedded toolkits is that it was not specifically developed for embedded devices. Instead, Qt/Embedded is a port of the Qt toolkit for UNIX/X11, Windows, and MacOS X. Consequently, you can leverage your experience in developing desktop applications when approaching embedded application development. You don't have to learn a new API, nor do you have to pick up new programming techniques. This isn't to say that you should simply port desktop applications to pocket computers. Clearly, handheld devices have some very different requirements in terms of screen size, drag-and-drop, and limited memory. Still, Qt and Qt/Embedded share the same basic API.

In theory, Qt/Embedded can run on any embedded device that runs Linux and that has a framebuffer. In practice, however, each new device requires some porting efforts. Among the devices that are supported out of the box are the Compaq iPAQ and HP Cassiopeia. With that in mind, we'll use the iPAQ as an example platform to illustrate Qt development for handhelds. Nevertheless, the techniques presented here apply to similar devices as well.

Getting Started

Of course, your first step is to install and configure Linux on the iPAQ (which comes preloaded with Microsoft's Pocket Windows), then add the development suite (that is, the "tool chain"). Since the installation process is beyond the scope of this article, the procedure for it is provided online; see "Resource Center," page 5. Also, because memory is always at a premium on embedded devices, it is important to know that you don't have to carry around the entire Qt with all its parts — XML parsers, table components, and the networking module. Instead, you can trim down Qt so that it contains what you need for your application. For details, see the accompanying text box entitled "Making Qt/Embedded Lean and Mean."

Once Linux is installed on an iPAQ, all you'll see is a blinking cursor and console prompt. To make more happen, install the Qt Palmtop Environment (QPE, http://qpe.sourceforge.net), which includes a number of applications developed with Qt/Embedded. However, an application QPE does not include is one to track expenses. Consequently, we present here an application called "Expenses" that lets you enter/edit/delete expenses, assign them a currency, and group them into categories.

Signals and Slots

One of the more notable Qt features involves signals and slots. Every GUI toolkit has to address the problem of how to connect user interaction (clicking a button or selecting an item in a menu, for instance) to program functionality (for example, calling a method). The basic idea here is that a component (such as a GUI element) can emit signals when something happens. This "something" could be users clicking buttons, data coming in via the network, or whatever. On the other hand, slots are simply ordinary methods marked up in a special way. You connect signals and slots with the QObject::connect() method. For example, in the constructor of the class ExpensesMainWindow, which implements the application window of our application (ExpensesMainWindow.cpp, available electronically), you see:



connect( _newExpenseAction,<br>
SIGNAL( activated() ), this,<br>
SLOT(slotNewExpense() ) );</p>

which means that whenever the object pointed to by _newExpenseAction emits the signal activated(), the Qt core invokes the method slotNewExpense() at the currently constructed object. _newExpenseAction is of the type QAction*, which is a class that abstracts from how users invoke a certain functionality; this can currently be via menu items or toolbar buttons. The signal activated() is emitted by QAction whenever users invoke this functionality. For each Qt class, the reference documentation tells you which signals and which slots are available. The slot slotNewExpense() is something we implement in our ExpensesMainWindow class.

Again, slots are just ordinary C++ methods marked up to be slots. Signals, on the other hand, are implemented by Qt internally and never appear in your code except in declarations and in connect() calls.

Take a look at the class declaration of the class ExpensesMainWindow in ExpensesMainWindow.h (also available electronically):



private slots:<br>
  void slotQuit();<br>
  void slotNewExpense();<br>
  void slotDeleteExpense();</p>

The pseudokeyword slots is what identifies these methods (and a few others not shown here) as slots. Of course, you do not need a patched C++ compiler to compile this — the pseudokeyword slots is automatically defined away by the preprocessor. However, it serves as a sentinel for a Qt-provided program called the "Meta Object Compiler" (moc) that you need to run over your class declarations. Moc extracts the necessary information and writes a C++-source file, which you have to compile and link to your application as well. With the right build tools, you won't even notice this.

Now look at the constructor of the class ExpenseDialog (see ExpenseDialog.cpp, Listing One):



QPushButton* cancelPB =<br>
   new QPushButton( "&Cancel", this );<br>
connect( cancelPB, SIGNAL( clicked() ),<br>
   this, SLOT( accept() ) );</p>

Here, we connect the clicked() signal of the QPushButton class to the accept slot of the ExpenseDialog class. Actually, we do not define this slot, but instead inherit it from the base class, QDialog — another proof that slots are just ordinary methods that can be private, protected, public, virtual, inherited, and reimplemented. The accept slot of the class QDialog simply closes a modal dialog and returns a positive return code to the code that has opened the modal dialog (see the methods ExpensesMainWindow::slotNewExpense() and ExpensesMainWindow::slotEditExpense() in ExpensesMainWindow.cpp) for how the modal dialog is opened and evaluated.

The class ExpenseDialog is an example for two other common Qt programming techniques. If you look at the source code, you find that we create lots of "widgets" (Qt's term for user-interface components, equivalent to "components" in the Java AWT or "controls" in Windows lingo) on the heap, but that there is not even a destructor where we would delete them. Are we having a huge memory leak here? And in an application meant for embedded devices where we need to conserve memory wherever we can?

Of course not. Qt maintains a parent-child hierarchy of all widgets (and actually of every object of a class that directly or indirectly inherits QObject). Whenever a widget is created, it registers with its parent, and when the parent is deleted, it will automatically delete its child widgets as well. So as long as the ExpenseDialog object is deleted, all the widgets in the dialog will be deleted. And the ExpenseDialog object is created on the stack, so it is deleted when the variable goes out of scope. This automatic deletion technique is also the reason why you should never create any widgets that are not top-level widgets on the stack — they will be deleted by their parents when these are deleted as well, and trying to use the operator delete on memory allocated on the stack spells disaster. This does not apply to top-level widgets such as dialogs or the main application window because these have no parents and thus cannot be deleted automatically.

This automatic deletion leads to a programming style that is somewhat similar to using the Java AWT or Swing: Widgets are simply created and deletion is handled automatically. This is also why destructors are not so common in Qt GUI programming. Qt's solution is much more memory efficient than garbage collection as used by Java — the child widgets are only kept around as long as they are needed and not until the garbage collection thread runs the next time.

Finally, the constructor of ExpenseDialog exemplifies layout management, another common Qt programming technique. While layout management is old hat to Java developers, GUI developers who have not developed for anything but Windows might be unfamiliar with it. With layout management, you do not specify the absolute positions of the widgets, but rather the positions relative to each other. For example, in the constructor, we have a vertically oriented layout (toplevelVBL of the class QVBoxLayout) that manages two other layouts, one grid oriented (dataGL of the class QGridLayout) and one horizontally oriented (buttonsHBL of the class QHBoxLayout). These in turn manage the input fields with their labels and the two buttons at the bottom.

Layout management has many advantages: Dialogs do not have to be redesigned after having been translated to another language, they still look nice when users resize them manually; and finally it is even easier to use after you have gotten the hang of it.

Of course, we could have created this dialog with Qt Designer, a graphical tool shipped with Qt for designing dialogs and other parts of your application. However, it's always a good idea to do something by hand before letting a tool do the work, which is why we have written and presented the code manually.

So if you use QPE, you can make one small change to the application to make it integrate nicely with QPE: In main.cpp (Listing Two) replace QApplication with QPEApplication and link to libqpe.so.

QMake

Portable toolkits are fine and dandy, but when it comes to actually compiling code, portability typically comes to a halt. Consequently, QMake (included with Qt 3) takes a project description in a .pro file and creates a Makefile suitable for whichever platform you want. QMake supports many different UNIX versions with various compilers, as well as Windows, MacOS X, and Linux on embedded systems. Listing Three is a typical .pro file. If QMake is set up correctly, qmake -o Makefile expenses.pro creates a Makefile suitable for your platform. QMake thus abstracts from different makefile syntax, compiler names, compiler switches, ways to build shared libraries, and so on. It even automates calling Qt's moc completely. With the files expenses.pro, qmake, and Qt 3, you can build the application on any platform supported by Qt — not only Qt/Embedded. With a few smaller changes, it is even possible to build it with Qt 2.x — you just replace the QDateEdit widget that is not contained in Qt 2.x with something else (three spin boxes come to mind). Also, because qmake is not included with Qt 2.x, you will have to use its predecessor tmake, available at ftp://ftp.trolltech.com/pub/freebies/tmake/. Listing Three works with tmake the same as with qmake.

Conclusion

There are a number of restrictions to keep in mind when developing for handheld computers. First, since the iPAQ has a 240x320 pixel screen, we set the application window to this size and also restricted the dialog for entering new expenses. To make the columns of the central listview component fit at least somewhat in the available width, we had to abbreviate their headers, which could lead to problems if the abbreviations are not understood by the intended users. A toolbar usually does not provide anything that the menu does not provide itself, and given that a toolbar takes at least 20 pixels in a vertical direction (1/16th of the total available vertical space!), this decision was not hard to take.

Finally, we decided not to use the spreadsheet-like QTable class for displaying the entered expenses even though it would have had some advantages. However, QListView is more lightweight, both in run-time memory and in the code it takes in the Qt library, so we decided to go with that.

DDJ

Listing One

#include "ExpenseDialog.h"
#include "Expense.h"

#include <qcombobox.h>
#include <qdatetimeedit.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qlineedit.h>
#include <qpushbutton.h>
#include <qvalidator.h>

ExpenseDialog::ExpenseDialog( const QStringList& categories,
         const QStringList& currencies, QWidget* parent, const char*
name ) :
         QDialog( parent, name, true )
{
    // The top-level layout manager
    QVBoxLayout* toplevelVBL = new QVBoxLayout( this, 10 );

    // Create the widgets for data entry
    QGridLayout* dataGL = new QGridLayout( 5, 2, 10 );
    toplevelVBL->addLayout( dataGL );

    QLabel* dateLA = new QLabel( "Date:", this );
    dataGL->addWidget( dateLA, 0, 0 );
    _dateDE = new QDateEdit( this );
    _dateDE->setDate( QDate::currentDate() );

    dataGL->addWidget( _dateDE, 0, 1 );

    QLabel* descriptionLA = new QLabel( "Description:", this );
    dataGL->addWidget( descriptionLA, 1, 0 );
    _descriptionED = new QLineEdit( this );
    dataGL->addWidget( _descriptionED, 1, 1 );

    QLabel* categoryLA = new QLabel( "Category:", this );
    dataGL->addWidget( categoryLA, 2, 0 );
    _categoryCO = new QComboBox( this );
    _categoryCO->insertStringList( categories );
    dataGL->addWidget( _categoryCO, 2, 1 );

    QLabel* currencyLA = new QLabel( "Currency:", this );
    dataGL->addWidget( currencyLA, 3, 0 );
    _currencyCO = new QComboBox( this );
    _currencyCO->insertStringList( currencies );
    dataGL->addWidget( _currencyCO, 3, 1 );

    QLabel* amountLA = new QLabel( "Amount:", this );
    dataGL->addWidget( amountLA, 4, 0 );
    _amountED = new QLineEdit( this );
    _amountED->setValidator( new QDoubleValidator( _amountED ) );
    dataGL->addWidget( _amountED, 4, 1 );

    // Create the OK and Cancel buttons
    QHBoxLayout* buttonsHBL = new QHBoxLayout( 10 );
    toplevelVBL->addLayout( buttonsHBL );
    QPushButton* okPB = new QPushButton( "&OK", this );
    okPB->setDefault( true );
    connect( okPB, SIGNAL( clicked() ), this, SLOT( accept() ) );
    buttonsHBL->addWidget( okPB );
    QPushButton* cancelPB = new QPushButton( "&Cancel", this );
    connect( cancelPB, SIGNAL( clicked() ), this, SLOT( accept() ) );
    buttonsHBL->addWidget( cancelPB );

    // restrict even this dialog to 240x320
    setMaximumSize( 240, 320 );
}
void ExpenseDialog::setExpenseValues( Expense* expense )
{
    // preset the widgets with the values from the Expense object
    _dateDE->setDate( expense->date() );
    _descriptionED->setText( expense->description() );
    for( int i = 0; i < _categoryCO->count(); i++ )
        if( _categoryCO->text( i ) == expense->category() ) {
            _categoryCO->setCurrentItem( i );
            break;
        }
    for( int i = 0; i < _currencyCO->count(); i++ )
        if( _currencyCO->text( i ) == expense->currency() ) {
            _currencyCO->setCurrentItem( i );
            break;
        }
    _amountED->setText( QString::number( expense->amount(), 'f', 2 ) );
}
QDate ExpenseDialog::date() const
{
    return _dateDE->date();
}
QString ExpenseDialog::description() const
{
    return _descriptionED->text();
}
QString ExpenseDialog::category() const
{
    if( _categoryCO->count() > 0 )
        return _categoryCO->currentText();
    else
        return QString::null;
}
QString ExpenseDialog::currency() const
{
    if( _currencyCO->count() > 0 )
        return _currencyCO->currentText();
    else
        return QString::null;
}
double ExpenseDialog::amount() const
{
    double value = 0.0;
    bool ok;
    value = _amountED->text().toDouble( &ok );
    if( ok )
        return value;
    else
        return 0.0;
}

Back to Article

Listing Two

#include <qapplication.h>
#include "ExpensesMainWindow.h"

int main( int argc, char* argv[] )
{
    QApplication app( argc, argv );

    ExpensesMainWindow mainWindow;
    mainWindow.show();
    app.setMainWidget( &mainWindow );

    return app.exec();
}

Back to Article

Listing Three

TEMPLATE += app
TARGET += expenses

SOURCES += ExpensesMainWindow.cpp main.cpp Expense.cpp ExpenseDialog.cpp

HEADERS += ExpensesMainWindow.h Expense.h ExpenseDialog.h


Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.