Next Previous Table of Contents
The KAppWizard, or also called the KDE Application Wizard, is intended to let you start working on new projects with KDevelop. Therefore, all your projects are first created by the wizard; then you can start building them and extend the already provided source skeleton. KAppWizard also allows to choose between several project types according to your project's goals:
In this chapter we'll see how the KAppWizard can be invoked and what has to be done to generate a KDE application project. This will also be the initial step of our coverage, where we will create the initial version of a sample project. For all other project types the steps are usually the same, just you may not have certain options available.
To start with your first KDE application, open KDevelop. Then select "New..." from the "Project"-menu. The KAppWizard starts, and you see a tree on the first page, containing the project types. When a type is selected, you see a preview how it will look like after the initial build process. Choose the KDE subtree, Normal type. Then press the "Next" button on the bottom of the first wizard page. This will switch to the next page, where you have to set the general project options.
For our sample application, we choose the project name KScribble
; therefore insert this in the field "Projectname". Then select
the directory you want to have your project build in; the default is your home directory. You can enter the path manually or you can as
well press the button on the right to select the directory by a dialog.
Next, you have to enter the Version number. For the first version, set this to 0.1
. It is usual to number new applications that are
in development for the first release lower than 1, and as the initial version will only contain the standard framework, we'll name this
the 0.1 version.
Finally, add your name to the "Author" field and your email address. You can leave all other options to their default settings.
To give you some information about all other options, you can press the right mouse button over the options, and you will get a quick-help window that describes the option's purpose.
These are:
Now we'll switch to the next page by pressing the "Next" button again to set the template for the header files of your project.
The header template page allows you to automatically include a preface for your header files, containing the filename, the construction date, the year of the copyright, also your name and your email address. You don't have to change those uppercase parts yourself, as KAppWizard does this automatically and stores the template for this project, so it can be used later again for creating new files.
The second part of the default header template contains a license information. By default, your project is set under the GNU General Public License, which is also included in the package. This license is used to protect your source code against any person that just copies your sources for his own purpose. The General Public License offers you this license for free and thereby protects your rights as the author, and is common for distributing free software. To get more information about the license, you should read the COPYING file in the base directory of your new project later which is a copy of the GPL and ships with your application already.
Anyway, you may want to choose another license or another header template you're already using for your projects. Therefore you can either edit the given default template directly. To do this, you're given the template in an editing window. To clear the default page, select "New", to use another template, select "Load...", which lets you choose the template file.
When you're done, go to the next page by entering "Next". This is the template page for your source files and is generally the same as the header template page. The only difference is that this template is used for your implementation files.
Now that you've set all options for KScribble, select "Next" and press the "Generate" button on the bottom of the wizard window. If the button is not available, you haven't set all options correctly. To correct any errors, step back in the Wizard with "Back".
Then you'll see what KAppWizard does- he copies all templates to your project directory and creates the new project. After KAppWizard is finished, the "Cancel" button changes to an "Exit" button to leave the wizard.
After this last step, you're finished with creating a new project. KDevelop then loads it and the tree-views let you browse through the project's files and classes.
In the next section, we'll discuss how to build and run your first version of KScribble and how the source code is organized.
After our project is generated, we'll first make a trip through the source code to get a general understanding how the application frame works. This won't only help to get started but we'll know where to change what in later steps.
When opening the LFV (Logical File Viewer) page on the tree-view, you see some folders that already sort the project files relevant to the developer. The first two folders are "Header" and "Sources". The Header-folder therefore logically contains all header files of the project, the Sources-folder all sourcecodes. All other folders are of no interest right now, so we'll turn back here later to see what they contain.
The two folders then contain the following files:
Headers:
Sources:
Before diving into the sources, we'll let KDevelop build and run our new application. To do this, select "Make" from the "Build"-menu
or hit the according button on the toolbar. The output window opens from the bottom of KDevelop and lets you see what make
does by
the messages it gives us:
1 Making all in docs
2 make[1]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
3 Making all in en
4 make[2]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs/en'
5 make[2]: Nothing to be done for `all'.
6 make[2]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs/en'
7 make[2]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
8 make[2]: Nothing to be done for `all-am'.
9 make[2]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
10 make[1]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
11 make[1]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble'
12 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribbleview.cpp
13 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribbledoc.cpp
14 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribble.cpp
15 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
main.cpp
16 /usr/bin/moc ./kscribble.h -o kscribble.moc.cpp
17 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribble.moc.cpp
18 /usr/bin/moc ./kscribbledoc.h -o kscribbledoc.moc.cpp
19 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribbledoc.moc.cpp
20 /usr/bin/moc ./kscribbleview.h -o kscribbleview.moc.cpp
21 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribbleview.moc.cpp
22 /bin/sh ../libtool --silent --mode=link g++ -O0 -g -Wall -o kscribble -L/opt/kde/lib -L/usr/X11R6/lib -rpath /opt/kde/lib
-rpath /usr/X11R6/lib kscribbleview.o kscribbledoc.o kscribble.o main.o kscribble.moc.o kscribbledoc.moc.o kscribbleview.moc.o
-lkfile -lkfm -lkdeui -lkdecore -lqt -lXext -lX11
23 make[1]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble'
As you see, we've put line numbers in front of each line, which won't appear in your output; it just makes it easier to describe what
happened during the build now. First of all, make
works recursively. That means, it starts from the directory it is invoked in and
then goes into the subdirectories first, returns and processes the next directory. Finally, the directory it was started is processed
and make
finishes. Therefore, make
started in the main project directory containing the sources first. In line 1 and 2, you
see how the make
process goes into the docs
directory, then into the en
subdirectory. As there isn't anything to do, it
leaves these directories until it returns to the source-directory kscribble
in line 11. Then, the real work starts: make
invokes the compiler, here g++
to compile the source-file kscribbleview.cpp
. The macro -DHAVE_CONFIG_H
says that the file
config.h
should be used. This is a file containing macros for the specific platform and application and is located in the main
project directory. The following -I
commands add the include path where g++
can find the includes it needs. These are the
current directory, the main project directory (by -I..
) and the include path for the KDE, Qt and X11 library header files. The
directories for these include files were determined by the configure
script and set in the Makefiles, therefore, the compiler
knows where these are located. Finally, -O0
sets the optimization to zero (no optimization), -g
enables debugging, -Wall
sets the compiler warnings to all
and -c
tells the compiler to produce an object file, so only compile the file.
This is done for the other source files of our project as well in lines 13-15. Obviously, our sources are compiled, but instead of
linking the object files of the sources to the final binary, we see some other commands. In line 16, you see that the program "moc" is
called to process the header file kscribble.h
, with its output in kscribble.moc.cpp
. Then, in line 17, this source file is
compiled as well. The same happens with the other project header files until line 21. Now, as the Qt toolkit contains the signal/slot
mechanism, but still stays a C++ implementation, you're using certain keywords that are not originally C++ language, such as the
signals:
and slots:
declaration in your classes. This gives you the ability to easily allow object communication for all
class objects that inherit the class QObject
, so you can avoid the usual callback pointer functions. Therefore, the application
needs the sources that implement this functionality, and that is why moc
is called. Moc
is the Meta Object Compiler of the Qt
toolkit and builds the implementation for signals and slots mechanisms by parsing the header file and producing a source output that
has to be compiled in the binary. As KDevelop projects use automoc
to determine, which header file needs to be processed, you
don't have to take care for any call on moc
and the C++ compiler on the moc output files. Just remember the rules that make a
class use the signals and slots- inheritance from QObject
or any class that inherits QObject
itself, inclusion of the
Q_OBJECT
macro (without semicolon !) at the beginning of the class declaration and the declarations for signals and slots.
Finally, your binary is built by the compiler. The output binary is called kscribble
, the linker includes the path for the KDE and
X11 libraries and links the sources against the libraries kfile, kfm, kdeui, kdecore, qt, Xext
and X11
. Then you're done and
make exits.
To gain a concept of how a KDE application works, we'll first have a very close look at the source skeleton already provided by the Application Wizard. As we already saw, we're having a set of source and header files that build the initial code for the application and make it ready-to-run. Therefore, the easiest way to explain the code is to follow the implementation line by line as it is processed during executing the program until it enters the main event loop and is ready to accept user input. Then, we'll have a look at the functionality that enables user interaction and how certain things work. This is probably the best way to explain the framework and, as it is similar to almost all KDE applications, will enable you to read source codes from other projects as well; additionally, you will know where to change what part of the code to make your applications behave the way they are designed for.
main()
FunctionAs the application begins its execution with entering the main()
function, this will be the start for our code examination. The
main()
function of KScribble is implemented in the file main.cpp
and can also be found using the Class Browser by
selecting the "Globals" folder, sub-folder "Functions":
1 #include "kscribble.h"
2
3 int main(int argc, char* argv[]) {
4 KApplication app(argc,argv,"KScribble");
5
6 if (app.isRestored())
7 {
8 RESTORE(KScribbleApp);
9 }
10 else
11 {
12 KScribbleApp* kscribble = new KScribbleApp;
13 kscribble->show();
14 if(argc > 1){
15 kscribble->openFile(argv[1]);
16 }
17 }
18 return app.exec();
19 }
Now, what happens first is the usual creation of a KApplication
object, which gets our application name KScribble as a third
parameter. When creating a new KApplication
, a new KConfig
instance is created as well which is connected to a configuration
file in $HOME/.kde/share/config/appname + rc which stores all information we want to use when starting application windows. The name we
passed the constructor of app
will be used as the window title later.
Despite of the example code for turning the first Qt application into a KDE one, the following code is somewhat different. After the
KApplication
object is present, we're testing if the application is started by the session management of kwm
or manually by
the user. This can be found out when calling isRestored()
on the app
object, which returns true
for session management
and false
for a normal start.
As session management is a main feature of KDE applications and widely used by the framework but a lot more to explain, we'll follow
the else{}
section first; then we'll come back and explain the session functionality in a later step.
The else{}
section now creates an instance of the class KScribbleApp
in line 12. This object is called to show itself in line
13 as usual; line 14 determines if a command-line argument has been passed and, as this is usually the name of a file, calls the
kscribble
object to open it with openFile()
.
Note that we didn't call the method setTopWidget(kscribble)
for our application- this is already done by the class that KScribbleApp
inherits. Now we'll have a look at our KScribbleApp
object- what is it and what does it provide already ? The only thing we know
until now is that it has to be a Widget to represent the user interface in the main window. Let's turn to the class implementation of
KScribbleApp
, which can be found in the file kscribble.cpp
or by a click on the class icon in the Class Browser. As the
instance is created by the constructor.
First of all, we see that it inherits the class KTMainWindow
, which is a part of the kdeui
library. This class itself
inherits QWidget
, so, as usual, we have a normal widget as the top-level window. KTMainWindow
contains a lot of functionality
that the class KScribbleApp
makes use of. It provides a menubar, toolbar, statusbar and session management support. The only thing
we have to do when sub-classing KTMainWindow
is to create all the objects we need and create another widget that is managed by our
KTMainWindow
instance as the main view in the center of the window; usually this is the place where the user works like a
text-editing view.
Let's have a look at the code for the constructor and see how the instance is created:
1 KScribbleApp::KScribbleApp()
2 {
3 config=kapp->getConfig();
4
5
6 ///////////////////////////////////////////////////////////////////
7 // call inits to invoke all other construction parts
8 initMenuBar();
9 initToolBar();
10 initStatusBar();
11 initKeyAccel();
12 initDocument();
13 initView();
14
15 readOptions();
16
17 ///////////////////////////////////////////////////////////////////
18 // disable menu and toolbar items at startup
19 disableCommand(ID_FILE_SAVE);
20 disableCommand(ID_FILE_SAVE_AS);
21 disableCommand(ID_FILE_PRINT);
22
23 disableCommand(ID_EDIT_CUT);
24 disableCommand(ID_EDIT_COPY);
25 disableCommand(ID_EDIT_PASTE);
26 }
We see that our config instance of KConfig
now points to the applications configuration, so we can operate
with the configuration file entries later.
Then, all parts of the application that are needed are created by their according member functions that are specific to our main window:
Finally, we disable some commands that the user can do, because they should not be available in the current application state. As we now have a general overview how the application window is created, we will look into the details of how the user elements are constructed by following the above methods.
As shown above, the menubar of KScribble is created by the method initMenuBar()
. There, we create a set of
QPopupMenu
s that pop up if the user selected a menuentry. Then, we insert them into the menubar and connect to the entries.
First, we create our recent_file_menu
, which will contain the names of the last 5 opened files. We have to do this first, because
this menu is inserted into the file_menu
. Then we add the connection directly- we just retrieve the signal that is emitted by the
menuentry with its entry number and call the slotFileOpenRecent( int )
, which then calls the right file from the recent file list
to be opened.
Then we create our "File"-menu. This will be the menu that will be visible in the menubar. The standard actions are then inserted into the popup-menu one by one- first the commands for creating a new file, open a file, close a file etc., finally "E&xit" to close the application. All menu entries have to be created in the order as they appear later, so we have to keep an eye on which we want to have at what place. As an example, we look at the following entries:
file_menu->insertItem(Icon("fileopen.xpm"), i18n("&Open..."), ID_FILE_OPEN );
file_menu->insertItem(i18n("Open &recent"), recent_files_menu, ID_FILE_OPEN_RECENT );
The first one inserts the "Open..." entry. As we want to have it with an icon, we use the insertItem()
method with the icon's
name. To understand the icon loading process, we need to know what or where Icon()
is declared- in fact, it is a macro provided by
the class KApplication
:
#define Icon(x) kapp->getIconLoader()->loadIcon(x)
Additionally, it uses the following macro internally to get access to the application object:
#define kapp KApplication::getKApplication()
This means that the KApplication
object already contains an Icon loader instance- we only have to get access to it; then it will
load the according icon. As our icons are all from the KDE libraries, we don't have to take care for anything else- they are installed
on the system automatically, therefore we also don't have to include them into our application package to use them.
After the icon parameter (which is optional), we insert the menuentry name by i18n("&Open...")
. There, we have to watch two
things: first, the entry is inserted with the i18n()
method. Like the Icon()
entry, it is a macro defined in kapp.h
as
well and calls the KLocale
object of KApplication
to translate the entry to the currently used language:
#define i18n(X) KApplication::getKApplication()->getLocale()->translate(X)
Hereby, it should be mentioned that one could think "I don't want to use macros"- you can do that in most cases. But here it is
immanent to use i18n()
because for internationalization the according language files have to be build. As this build process
depends on the i18n
string, you have to use the macro.
As you might have already guessed, the ampersand within menu entries is later interpreted as a line under the following letter in the
menuentry. This allows fast access to the menu command via the keyboard when the user presses the Alt
-key in conjuction with the
underlined letter.
Finally, we're giving the menuentry an ID, which is an integer value by which we can find the entry later. To keep
an overview over the used values, these are defined by macros and are collected in the file resource.h
within your project. For
consistency, these macros are all uppercase and begin with ID_, then the menu name followed by the entry. This makes it very easy to
remember the sense of each entry anywhere within the code, so you don't have to turn to the menubar implementation again to look up the
entries.
The second example entry shows another variant of the insertItem()
method. Here, we add the recent_files_menu popup menu as a
menuitem. This means, that the entry shows itself with the given string "Open recent", followed by a right arrow. On selection, the
recent file popup menu appears and the user can choose the last file.
Last but not least there are a lot of other ways to insert menu items- the framework keeps this as simple as possible. More information
can be obtained in the Qt documentation about the QMenuData
class.
Now, after we created the popup menus file_menu, edit_menu
and view_menu
, we have to include a "Help"-menu as well. We could
do this like the others as well, but the KApplication
class offers a nice and quick method to cover this:
help_menu = kapp->getHelpMenu(true, i18n("KScribble\n" VERSION ));
This is all we have to do to get a help menu that contains an entry for the help contents with the F1 keyboard shortcut
, an about-box
for the application and an about-box for the KDE (which can be disabled by calling getHelpMenu(false,...);
). The contents for our
applications about-box is set with the i18n()
string again- VERSION takes the macro that is defined for the project version number
in the file config.h
, so we don't have to change this every time manually when we want to give out a new release. Feel free to add
any information about your application here, e.g. your name, email address, copyright and the like.
Now we only have to insert the pop-ups into the menubar. As KTMainWindow
already constructs a menubar for us, we just insert them
by calling menuBar()->insertItem();
.
What is left to do is to connect the menu-entries with the methods they will execute. Therefore, we connect each popup menu by its
signal activated( int )
to a method commandCallback( int )
, which contains a switch
statement that calls the according
methods for the menu entries. Additionally, we connect the pop-ups by their signal highlighted( int )
to provide statusbar help on
each entry. Whenever the user moves his mouse or keyboard focus to an entry, the statusbar then shows the according help message.
After we finished with the menubar, we can continue with the toolbar in the following section. Mind that an instance of a
KTMainWindow
can only have one menubar visible at a time; therefore if you want to construct several menu bars, you have to create
them separately with instances of KMenuBar
and set one of them by the according methods of KTMainWindow
as the current
menubar. See the class documentation of KMenuBar
for more detailed information about how to extend the features, also see
Configuring Menubars and Toolbars.
The creation of toolbars now is even simpler than that of menubars. As KTMainWindow
already provides toolbars, which are created
by the first insertion, you are free to create several ones. Just add the buttons for the functions you want to provide:
toolBar()->insertButton(Icon("filenew.xpm"), ID_FILE_NEW, true, i18n("New File") );
This adds a left-aligned button with the icon "filenew.xpm" with the according ID to the toolbar. The third parameter decides if the
button should be enabled or not; by default we set this to true
, because our disableCommand()
methods at the end of the
constructor do this for us automatically for both menu and toolbar entries. Finally, the last parameter is uses as a so-called
"Quick-Tip"- when the user moves the mouse pointer over the button so that it gets highlighted, a small window appears that contains a
short help message, whose contents can be set here.
Finally, all toolbar buttons are connected to our commandCallback()
method again by their signal clicked()
. On the signal
pressed()
, we let the user receive the according help message in the statusbar.
Additional Information:
As toolbars are created using the class KToolBar
, you should have a look at the according documentation. With
KToolBar
, a lot of things needed in a toolbar can be realized such as delayed pop-ups if your button wants to pop up a menu
when the button keeps being pressed or even widgets like combos. Also, by default, the toolbar fills the complete width of the
window, which makes it look nice for using a single bar. When using more than one, you should also think about setting the bar
size to end at the most right button, so other bars can be displayed in the same row below the menubar. We will discuss certain
techniques about designing and extending toolbars in section
Configuring Menubars and Toolbars.
The statusbar is, as well as the other bars, already provided by the KTMainWindow
instance, so we just have to insert our items as
we want to. By default, the framework contains only one entry that displays statusbar help. For a lot of applications this may not
last; then you would add the entries you need for displaying e.g. coordinates and the like.
Also, an application can only have one statusbar at a time like a menubar. If you want to construct several ones, you should create
them separately and set the current bar by the according method of KTMainWindow
. The statusbar also offers to insert widgets,
which can be used to produce nice habits for displaying progress-bars like KDevelop does. Refer to the class documentation of
KStatusBar
.
With reaching the method initKeyAccel()
, we already constructed the standard items of an application main window- the menubar,
toolbar and statusbar. Indeed, we didn't set any keyboard accelerator
s by which advanced users that only want
to work with the keyboard have a quick access to certain commands that are used most often during work with our program. To do this, we
could have inserted the accelerator
keys by the insertion of the menu-items for example, but KDE offers a good
solution to construct and maintain keyboard accelerator
s. A lot of users want to have them configurable on one hand
and on the other standard accelerator
s should be the same over all applications. Therefore, the KDE control center
offers configuring standard keyboard accelerator
s globally by using the KAccel
class. Additionally, the
KDE libraries contain a widget that lets users configure application specific keyboard shortcut
s easily. As the
application framework only uses menu-items that have standard actions such as "New" or "Exit", these are set by the method
initKeyAccel()
. Standard actions just have to be connected, for your application specific keyboard values, you have to insert them
first by specifying the keyboard accelerator
name and then connect them. As our accelerator
s are
all present in the menubar, we have to change the accelerator
s for the popup entries. Finally we call
readSettings()
, which reads the current settings from the root window of KDE containing the configuration of standard
accelerator
s, then the settings for accelerator
s specified in the application's config file. When
we're going further into our example project, we will also talk about how to configure our application specific
accelerator
s by a configuration dialog, see
Configuring Menubars and Toolbars for that part of the development process.
The next two member function calls, initDocument()
and initView()
, are finally building the part that the application windows
are supposed to provide to the user: an interface to work with data that the application is supposed to manipulate; and that is also
the reason why the application framework contains three classes, an *App
, *View
and *Doc
class. To understand, why this
structure is helpful, we'll look a bit aside the actual code and introduce some theory, then we'll switch to the program again to see
how the KDevelop frameworks support such a model.
Basically, all what has been explained about the framework is that we need an application instance that contains a main window. This window is responsible to provide the basic interface for the user- it contains the menubar, toolbars and statusbar and the event controlling for user interaction. Also, it contains an area, that is described as a "view". Now, the purpose of a view is generally, to display the data that the user can manipulate, e.g. a part of a text file. Although the text file is probably larger than the view is able to display on the screen, it offers the user to go to the part that he wants to see (therefore it is a view), and there the user can as well change the data of the file contents. To give the programmer a better way to separate parts of the application by code, the Document-View Model has been invented. Although not a standard, it provides a structure how an application should work:
Back to the example of working with a text file- there, this model would work the way that the Document would read the file contents and provides methods to change the data as well as to save the file again. The view then processes the events that the user produces by the keyboard and the mouse and uses the document object's methods to manipulate the document data.
Finally, the controller object is responsible for user interaction by providing the document and the view objects as well as the interfaces to send commands like opening and saving. Additionally, certain methods of the view object can be provided by commands that can be accessed via keyboard accelerator s or the mouse on menubars and toolbars.
This Document-View model has some advantages- it separates the program's code more object-oriented and by this offers more flexibility in general, e.g. the same document object could be displayed by two views at the same time; either by a new view in a new window or by tiling the current one that then contains two view object that build the actual window view region.
Now, if you're coming from MS-Windows systems you may have some experience with that- the MFC already provide a document model that is ready to use. For KDE and Qt applications, things are a bit different. Qt is a powerful toolkit as it provides the most needed classes, widgets etc. But there wasn't any intention to take care of the document-view model, and as KDE is inheriting Qt, there weren't any tendencies to introduce such a model either. This somehow has its reason in the fact that usually X-applications don't work with an MDI (Multiple Document Interface). Each main window is responsible for its data and that reduces the need of a document model to the fact that methods to work on documents are always implied into widgets. The only exception from this currently is the KOffice project that is intended to provide a complete office suite of applications like a word processor, a spreadsheet etc. Technically, this is realized by two changes two the normal usage of Qt and KDE:
But as KDevelop currently targets on using the current libraries of KDE 1.1.x and Qt 1.4x, we can't use this model by default- this will come in further releases of a KDE 2, which will (hopefully) contain two new major changes in relation to the current situation:
Therefore, the current way for application developers can be to either implement all needed document methods within their view or to try to reproduce a document model by themselves. KDevelop therefore contains such a reproduction by providing the needed classes and the basic methods that are generally used for a Document-View model with the application frameworks for Qt and KDE.
Back to the code, you now can imagine the purpose of the two methods we mentioned at the beginning of this section: the
initDocument()
and initView()
functions. The initDocument()
constructs the document object that represents the
application window data and initializes the basic attributes like setting the modification bit that indicates if the data currently
used has been changed by the user. Then, the initView()
method constructs the *View
widget, connects it to the document and
calls the setView()
method of KTMainWindow
to tell the *App
window to use the *View
widget as it's center view.
For the developer, it is important to know that during the development process he has to:
QWidget
in the *View
object to provide the
means to manipulate data,paintEvent()
of QWidget
in the *View
object to repaint() the view after changes,*View
object,*Doc
object to provide file loading and saving,*Doc
object that is representing the document data logically in the
memory.
Now, after we created all instances of the KTMainWindow
instance of our application to create the first window, we have to
initialize certain values that influence the look of the program. For this, we call readOptions()
, which gets all values and calls
the methods needed to set the according attributes. The KDE-Core library contains the class KConfig
that provides a good
possibility to store values in configuration files as well as to read them in again. Also, as each KApplication
instance
creates it's resource file already, we only have to access this file and create our values. As KConfig
provides us the file
object, we have to use the class KConfigBase
to read and write all entries. As writing is very easy to do with
writeEntry()
methods, reading depends on the attribute type which we want to initialize. Generally, an entry in the configuration
file contains a value name and a value. Values that belong together in some context can be collected in groups, therefore we have to
set the group name before we access the value afterwards; the group has to be set only once for reading a set of attributes that are in
the same group. Let's have a look at what we want to read in:
1 void KScribbleApp::readOptions()
2 {
3
4 config->setGroup("General Options");
5
6 // bar status settings
7 bool bViewToolbar = config->readBoolEntry("Show Toolbar", true);
8 view_menu->setItemChecked(ID_VIEW_TOOLBAR, bViewToolbar);
9 if(!bViewToolbar)
10 enableToolBar(KToolBar::Hide);
11
12 bool bViewStatusbar = config->readBoolEntry("Show Statusbar", true);
13 view_menu->setItemChecked(ID_VIEW_STATUSBAR, bViewStatusbar);
14 if(!bViewStatusbar)
15 enableStatusBar(KStatusBar::Hide);
16
17 // bar position settings
18 KMenuBar::menuPosition menu_bar_pos;
19 menu_bar_pos=(KMenuBar::menuPosition)config->readNumEntry("MenuBar Position", KMenuBar::Top);
20
21 KToolBar::BarPosition tool_bar_pos;
22 tool_bar_pos=(KToolBar::BarPosition)config->readNumEntry("ToolBar Position", KToolBar::Top);
23
24 menuBar()->setMenuBarPos(menu_bar_pos);
25 toolBar()->setBarPos(tool_bar_pos);
26
27 // initialize the recent file list
28 recent_files.setAutoDelete(TRUE);
29 config->readListEntry("Recent Files",recent_files);
30
31 uint i;
32 for ( i =0 ; i < recent_files.count(); i++){
33 recent_files_menu->insertItem(recent_files.at(i));
34 }
35
36 QSize size=config->readSizeEntry("Geometry");
37 if(!size.isEmpty())
38 resize(size);
39 }
As we have seen in one of the above code parts, the first action our constructor does was:
config=kapp->getConfig();
which sets the KConfig
pointer config
to the application configuration. Therefore, we don't have to care for the location of
the configuration file. Indeed, the file is, according to the KDE File System Standard (KDE FSS), located in
$HOME/.kde/share/config/
; we will have a closer look about the KDE FSS in a later step when we're setting installation
locations for project files. As the config file is placed in the user's home directory, each user has it's own appearance of his
application except for values that are located in a system wide configuration file that can optionally be created and installed by the
programmer in the KDE directory. But, although this could help in some cases, we should avoid any dependency of our application towards
the existing of file entries. Therefore, all read methods provided by KConfigBase
allow to add a default value to be used
when the entry doesn't exist. Another thing important to a programmer is that the configuration file is stored in plain text, and this
is for some reasons as well as you have to watch some criteria:
Now that we know the basics, we're going to analyze the code. As said, we only have to use our config pointer to access the values.
First, in line 4, we set the current group to "General Options". This indicates that the values used are somewhat general attributes
for the application. Then we read the values for the toolbar and statusbar- these have to be saved when the application closes to
restore their status again when the user restarts the program. As the bars can only be on or off, we use a boolean value, therefore,
our method is readBoolEntry()
. The process is identical for both bars, so we only have a look at the lines 7-10 to watch what's
happening for the toolbar. First, we read the value into the temporary variable bViewToolbar
at line 7. The value name in
the file is "Show Toolbar" and, if the value is not present (which would be the case the first time the application starts), the
default value is set to true
. Next, we set the checkmark for the menuentry for en-/disabling the toolbar by this value: we
call setItemChecked()
on the view menu, entry ID_VIEW_TOOLBAR
with our attribute. Finally, we set the toolbar to use
the value. By default, the toolbar is visible, therefore, we only have to do something if bViewToolbar
is false
. With
enableToolBar()
(line 10) we're setting the bar to hide itself if it is disabled.
Next, we have to read the bar positions. As the user might have changed the bar position by dragging a bar with the mouse to another
view area, these have to be saved as well and their status restored. Looking at the classes KToolBar
and
KMenuBar
, we see that the bar positions can be:
enum BarPosition {Top, Left, Bottom, Right, Floating, Flat}
As this value has been written in a numeric value, we have to read it with readNumEntry()
and convert it to a position value. With
setMenuBarPos()
and setBarPos()
we tell the bars where to show up.
Now you probably have noticed that our "File" menu contains a menu for recently used files. The filenames are stored in a list of
strings, which has to be saved on application closing and now has to be read in to restore the menu. First, we initialize the list with
the entries stored by using the readListEntry()
. Then, in a for-
loop, we create a menu entry for each list item.
Finally, we only have to take care for the geometry of our window. We read in the appearance by a QSize
variable containing an x
and y value for width and height of the window. As the window is initialized by KTMainWindow
, we don't have to take care for
a default value and only will use resize()
if the entry is not empty.
What is left to explain on application construction is that we initially have to disable available user commands that shouldn't be available if some instances don't match the needed criteria. These are file saving and operations that are using the clipboard. During the application's lifetime, we have to take care of these several times, but which is quite easy. The framework only gives us two methods to enable/disable menubar and toolbar items with one method call at the same time.
During the past section, we have only monitored what happens during the constructor call of our KScribbleApp
instance providing us
the main window. After returning to the main()
function, we have to call show()
to display the window. What is different from
any KApplication
or QApplication
here is that when we're using KTMainWindow
as the instance for our
main widget, we don't have to set it with setMainWidget()
. This is done by KTMainWindow
itself and we don't have to
take care of that.
The only thing left then is to interpret the command-line. We get the command-line option and ask, if int argc
is > 1, which
indicates that the user called our application with kscribble filename_to_open
. Our window is then asked to open the file by it's
name and calls openDocumentFile()
with the filename.
The last line of the main()
function does the known job: it executes the application instance and the program enters the event
loop.
Now, in section
The main() Function, we started to separate the execution process by
if( app.isRestored() )
and described the usual invocation process. The following now gives an introduction to session
management and how our application makes use of this.
As we said, the main()
function tests, if the application is invoked by the session manager. The session manager is responsible to
save the current status of all open application windows on the user's desktop and has to restore them when the user logs in the next
time, which means that the application is not started by the user but automatically invoked. The part of the code which is executed
was:
6 if (app.isRestored())
7 {
8 RESTORE(KScribbleApp);
9 }
In
The main() Function, we stated that we test the invocation by asking
app.isRestored()
. Then line 8 gets executed. It looks like a simple statement, but in fact this will result in a complex execution
process which we want to follow in this section.
RESTORE() itself is a macro provided by KTMainWindow
. It expands to the following code:
if (app.isRestored()){
int n = 1;
while (KTMainWindow::canBeRestored(n)){
(new KScribbleApp)->restore(n);
n++;
}
}
This will restore all application windows of the class KScribbleApp
by creating the instances and calling restore()
to the
new window. It is important to realize that if your application uses several different widgets that inherit KTMainWindow
, you have
to expand the macro and determine the type of the top widgets by using KTMainWindow::classNameOfToplevel(n)
instead of the class
KScribbleApp
.
The restore()
method then reads the part of the session file that contains the information about the window. As KTMainWindow
stores all of this for us, we don't have to care for anything else. Only information that belong to our specific instance of
KScribbleApp
has to be found then. Usually this would be a temporary file that we created to store the document or other
initialization that we might need. To get to this restoration information, we only have to overwrite two virtual methods of
KTMainWindow
, saveProperties()
and readProperties()
. The information we have to save on session end is if the currently
opened document is modified or not and the filename. If the file is modified, we will get a temporary filename to save it to. On
session beginning, this information now is used to restore the document contents:
void KScribbleApp::readProperties(KConfig*)
{
QString filename = config->readEntry("filename","");
bool modified = config->readBoolEntry("modified",false);
if( modified ){
bool b_canRecover;
QString tempname = kapp->checkRecoverFile(filename,b_canRecover);
if(b_canRecover){
doc->openDocument(tempname);
doc->setModified();
QFileInfo info(filename);
doc->pathName(info.absFilePath());
doc->title(info.fileName());
QFile::remove(tempname);
}
}
else if(!filename.isEmpty()){
doc->openDocument(filename);
}
setCaption(kapp->appName()+": "+doc->getTitle());
}
Here, the line kapp->checkRecoverFile()
seems a bit strange, as b_canRecover
is not initialized. This is done by the method
which sets it to true
, if there is a recover file. As we only saved a document in a recover file if it was modified, we set the
modified bit directly to indicate that the information hasn't been saved to the belonging file. Also we have to take care that the
recover file has another filename than the original file which was opened. Therefore, we have to reset the filename and path to the old
filename. Finally, we have the information we wanted to recover and we can delete the temporary file by the session manager.
Summary:
During this chapter, you got to know how the application starts either by normal user invocation or by the session manager. We went through the code to learn how the parts of the visual interface of the application are constructed as well as how to initialize attributes by configuration file entries. Now you can execute the framework application to test these functions and see how the program window reacts.
Besides the source code provided, KDevelop projects contain a lot of other additional parts that are of interest to the developer. These are:
Except the API-documentation, these elements of the project will be installed together with the application binary. As the project
framework has to be as open as possible, you have to adapt these parts towards your project goals. These are first to edit the icons
provided. This will give your application a unique identifier by which the user can determine your application visually in window
manager menus. The .kdelnk file then is a file that installs your application into kpanel
in the Applications
menu. This has
to be edited by setting the installation path which will be discussed later in this handbook. Finally, the documentation that you will
provide to the user is written in SGML. This makes it very easy to create several different output from the same source. By default,
KDevelop offers to create a set of HTML files from this source, for KDE-projects this will automatically use the ksgml2html
program to add a consistent KDE look and feel to the documentation. In a later section, we will see how the SGML source is edited and
what we have to watch for installation on the end-user.
Finally, the API (Application Programming Interface) documentation allows you and other developers to quickly get into the code and use
the classes without having to guess what purpose each class is for. We will learn how to extend the API documentation in a later step,
for now it lasts to know that the documentation is generated by the KDoc
program, which processes the header files and creates the
HTML output, therefore all documentation is placed in the headers.
Next Previous Table of Contents