Chapter Two :- The KDE Application
In the last chapter we looked at a practical demonstration of how a KDevelop generated application works. In this Chapter we are still looking at the same application but whereas previously, in the coming chapters we looked at the parts of the application that are designed entirely by the developer in this chapter we will be looking more at how KDevelop enables us as programmers to do these designs in the first place.
The Application
To start with let's look a the one file in our application that we didn't look at in the last chapter. That is the main.cpp file and it looks like this,
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("chapteronehelloworld", I18N_NOOP("ChapterOneHelloWorld"), version, description, KAboutData::License_GPL, "(C) 2005 pseudonym67", 0, 0, "[email protected]"); about.addAuthor( "pseudonym67", 0, "[email protected]" ); KCmdLineArgs::init(argc, argv, &about); KCmdLineArgs::addCmdLineOptions( options ); KApplication app; ChapterOneHelloWorld *mainWin = 0; if (app.isRestored()) { RESTORE(ChapterOneHelloWorld); } else { // no session.. just start up normally KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); /// @todo do something with the command line args here mainWin = new ChapterOneHelloWorld(); app.setMainWidget( mainWin ); mainWin->show(); args->clear(); } // mainWin has WDestructiveClose flag by default, so it will delete itself. return app.exec(); }
The first thing that we should notice is the I18N_NOOP macro which is a location helper for the KLocale class. It's task is to mark a string for translation but not to actually translate it. The idea being that when a translator, such as the QTranslator class, comes along then all the strings that need translating are already identified. For more on KDE translation see the KDE Internationalisation site.
The Command Line
The command line is one of those things in writing computer programs where to a large extent you can completely ignore it. The only problem is that when you do need to use it it hardly ever turns out to be trivial so it is always better to know how to use it than not.
The way the command line is used in KDevelop is that an array is set up of KCmdLineOptions with each part taking three strings, these being the name of the option such as “displayAbout”, The second is a string that describes what the command line option does and the third is the default option. So if we wanted to set up a command line option that showed the about box before the application started then it would read
{ “displayAbout”, I18N_NOOP( “Show the about box on Start” ), 0 }
Note that the description string is marked as ready for translation. This should be as it is a string that the user of our application should see. So let's see what this looks like in a code example. Set up the C++/KDE/Simple Designer based KDE Application called ChapterTwoCmdLine as described in chapter one and don't forget that as we have set up the CVS previously we only have to “Init Local Repository” when we get to the CVS screen. Open the main.cpp file by clicking on it in the Automake Manager and add the line above to the KCmdLineOptions array. When this is done build the application for now using Build/Build Project or press F8 ( default ) and then open Konqueror and go to the chaptertwocmdline directory, right click the directory and select Open With/Cervisia and you'll get,
You can clearly see from the image that the main.cpp file has been changed. If we right click on the main.cpp file at this point we can see what the differences are using the difference viewer,
The difference viewer shows the image of the file that is currently in CVS in the left pane and the current version on the hard disk, remember that when doing large project development the copy in CVS is unlikely to be on your hard disk. If you are happy with the version on the you can right click on the file and commit the changes made to the CVS Repository. At this point you will be given to option to log the change.
You can add the message explaining what you have done and check use CVS to see what other people have changed in the file. Once committed Cervisia will show the updated file as,
You can see from the above that Cervisia has updated the file and incremented the revision number. Of course in a real development we would have tested the file properly first so we'd better have a look and see what we've changed. If you open the development folder for the project in Konqueror and go to debug/src, right click on the folder and select Actions/Open Terminal Here. This will open a Konsole in the source folder where our project has been built.
If we look at the files in the folder with the Konsole using ls -l we can see the chaptertwocmdline, highlighted above in green along with the rest of the files. Also here we can see the chaptertwocmdlinewidgetbase class and header files. You may remember that in chapter one I said that for all intents and purposes that these classes exist in name only as far as we are concerned, well here they are. They are generated by the development environment at build time from the ui file and then our file that ends with the word widget inherits from them. We shall take a quick look at them shortly but for now we are concentrating on the output in the Konsole.
About half way down the screen pictured you can see the command “chaptertwocmdline -help” entered into the Konsole and the resulting print out. At the top there is a brief usage instruction and then the phrase “A KDE KPart Application” this is from the description mentioned earlier that is tagged with the I18N_NOOP macro that marks the string for translation. The reason being that at times the strings marked by I18N_NOOP are going to be seen by the users of the program and it's obviously a better user experience if they can understand what it says.
It should be noted that the KDE Developer FAQ at http://developer.kde.org/documentation/other/developer-faq.html#q2.11.2 states that once the KDE application is initialised you should use the macro i18n() to highlight translatable strings within the program code.
As you can see from the above print out the options are split into two parts from a standard “-help” command line argument with the general options coming first and then the options entered within the program follow. If you want a complete list of options use the command “chaptertwocmdline –help-all”.
I'll leave you to play around with the available options to see what they do and move on to looking at just what we are going to do with our option now that we have one. In an normal program a command line option is something that is used to influence how the program runs and not something that shows a dialog box as I am in this example.
First though here is the code that we are using.
if( args->isSet( "displayAbout" ) == true ) { KAboutDialog *aboutDlg = new KAboutDialog( KAboutDialog::AbtProduct, "Display Demo", KDialogBase::Ok, KDialogBase::Ok, 0, "Display About", true ); aboutDlg->setProduct( "Display About Demo", "1.0", "pseudonym67", "2005" ); aboutDlg->show(); }
The code itself tests to see if the displayAbout option is set, remember the default is set to false or 0 in the third part of the options string. Basically the three lines of code above create a KAboutDialog object and then set the details to be displayed before showing it. Most of the options for the KAboutDialog require a layout type in the first parameter that requires an image to be set but for now we are going to go for a straight text version by using KAboutDialog::AbtProduct
To see the options available for KAboutDialog simply double click on the class name so that it is highlighted then right click on it and select “Find Documentation: KAboutDialog” and you should get this in your right panel,
Clicking on the KAboutDialog will take you to the html help page for the class, although to see the options to be set for the KAboutDialog properly you will have to look at the html version of the C++ header file a link for which is provided in the html help.
We set the LayoutType according to the enum defined in KAboutDialog.h, They can be viewed in the class reference on KAboutDialog under the Public Types section, you can replace the AbtProduct with any of the above to see what effect they have on the about box.
The title is a simple string, the required buttons takes a KDialogBase ButtonCode enumeration as defined in the KDialogBase, and the button to be highlighted takes the same type. The parent window or 0 is next, followed by the name for the dialog and if we require the dialog to be modal or not. We set the product information the we want to display on the dialog and then call show.
If we run the program now though all we will get is the standard default hello world project as KDevelop still hasn't been told that we wish to pass a parameter to the program. There are two things that we need to do to get the program to run properly from the environment. We could go to the command line and type in what we need but that kind of defeats the point of having a development environment so we'll stick to doing things with KDevelop.
First click on Project/Project Options and then Run Options in the dialog and fill in the Program Arguments like so
Then select the Debugger option and fill in the Program Arguments like so,
Now if we run the program from either Debug/Start or Build/Execute Program the program will run with the displayAbout command line argument set.
When the program starts you should now get
displayed with the start of the program and because the dialog has been declared modal you will not be able to use the hello world part of the application until the OK button is clicked and the dialog is removed.
Code Snippets
There is one item on the right had side that we haven't used yet and that is the Code Snippets section. Now that we have the About Box set up the way that we want it we can save the code in the Code Snippets section so that in future projects we can use it as a guide for adding the About Box.
Click on the Code Snippets side bar to open it up and there will be just the default option. To add a new group such as a C++ group for holding C++ code right click in the window and select Add Group. This will give you an option to name the group and then KDevelop will create it.
Select the code you wish to add to the snippet in the code window and copy it with CTRL-C and then right click on the C++ group and select add item. Name the item About and then paste the snippet into available window.
This means that when we want to use the About Box again we have the set up code saved in the snippet box and don't have to go searching through code files trying to find it.
A Better Demonstration
Actually the simplest way to improve the demo is to use a standard KMessageBox such as
KMessageBox::information( 0, "Display About Demo\nVersion 1.0\nCopyright 2005\npseudonym67" );
which would give you
But we wont do that because it would be far too easy. So we'll start another project and do it the interesting way instead.
Create a new Simple Designer Based KDE Application called ChapterTwoAboutDemo as we did in Chapter One.
Coding Styles
There is a lot said about coding styles and most of it is nonsense. No matter what language you program or what environment you program in there will be people that insist that all classes start with a capital or not and all variables start with a capital or not and all enums should be labelled in capitals and have a variable named for easier access. i.e. enum ENUMTYPE{ zero, one, two }enumType; or EnumType; or even eEnumType; or possibly eenumType; and any other variation you can think of or as in the case with the KDE API not.
The thing is that everyone selects or adjusts to the style they are most used to so the way I always work it is this. If I am programming in a new environment then I use the class labels that are used in that environment and if function variables in the environment start lower case then my function variables start lower case. For anything that is not directly accessible I'll use whatever style I am most comfortable with not because I'm being obnoxious and not even because I'm trying to make code unmaintainable, in fact if anything even I sometimes think that my coding style is overly defensive and overly descriptive where some variable names get so long I get bored with typing them.
The reason I do this is simply because I am usually concentrating on what I want to the code to do at the time and as I result of this I usually just type the code in the manner I am most used to, which just happens to be the way I learned to write code in the early nineties. So try to stick within the rules where other people or classes are going to access your code but other than that it's just a religious war that has no bearing on if the code works or not.
Adding The Demo Code
Anyway mini rant over back to the demo. First of all add the following to the
First of all we edit the main.cpp file like so
static KCmdLineOptions options[] = { // { "+[URL]", I18N_NOOP( "Document to open" ), 0 }, { "displayAbout", I18N_NOOP( "Show the about box on Start" ), 0 }, KCmdLineLastOption }; int main(int argc, char **argv) { KAboutData about("chaptertwoaboutdemo", I18N_NOOP("ChapterTwoAboutDemo"), version, description, KAboutData::License_GPL, "(C) 2005 pseudonym67", 0, 0, "[email protected]"); about.addAuthor( "pseudonym67", 0, "[email protected]" ); KCmdLineArgs::init(argc, argv, &about); KCmdLineArgs::addCmdLineOptions( options ); KApplication app; ChapterTwoAboutDemo *mainWin = 0; if (app.isRestored()) { RESTORE(ChapterTwoAboutDemo); } else { // no session.. just start up normally KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); /// @todo do something with the command line args here /* One way to do it if( args->isSet( "displayAbout" ) == true ) { mainWin = new ChapterTwoAboutDemo( true ); } else mainWin = new ChapterTwoAboutDemo( false ); */ mainWin = new ChapterTwoAboutDemo(); mainWin->setShowAboutBox( args->isSet( "displayAbout" ) ); app.setMainWidget( mainWin ); mainWin->show(); args->clear(); }
I've added the option displayAbout to the command line options and then implemented a couple of the possible ways in which you could use it. Although I have to admit that there aren't many occasions on which I've wanted to start a program with a dialog box there have been one or two so the demo does manage to avoid the being completely useless category this time.
The KAboutData and the KCmdLineArgs code remains untouched here and there are two options given for how to implement the display of the aboutBox. The first is through a custom constructor and the second is done through the setting of a flag variable within the ChapterTwoAboutBox class.
The custom constructor is simply the line
ChapterTwoAboutDemo( bool showAbout );
In the header file while the flag variable is set using,
class ChapterTwoAboutDemo : public KMainWindow { Q_OBJECT private: bool bShowAboutBox; /// variable access public: bool showAboutBox() { return bShowAboutBox; } void setShowAboutBox( bool show ) { bShowAboutBox = show; }
The point here is just to set up a quick and easy flag to check if the command line parameter has been passed from within the code.
The custom constructor is then defined as
ChapterTwoAboutDemo::ChapterTwoAboutDemo( bool showAbout ) : KMainWindow( 0, "ChapterTwoAboutDemo" ) { setShowAboutBox( showAbout ); setCentralWidget( new ChapterTwoAboutDemoWidget( this ) ); }
in chaptertwoaboutdemo.cpp. The next thing to do is add About dialog itself to the ChapterTwoAboutDemo header file
private: bool bShowAboutBox; KAboutDialog *aboutDlg;
and then initialise it in the constructors.
ChapterTwoAboutDemo::ChapterTwoAboutDemo( bool showAbout ) : KMainWindow( 0, "ChapterTwoAboutDemo" ) { setShowAboutBox( showAbout ); setCentralWidget( new ChapterTwoAboutDemoWidget( this ) ); aboutDlg = new KAboutDialog( KAboutDialog::AbtProduct, "Display Demo", KDialogBase::Ok, KDialogBase::Ok, 0, "Display About", true ); }
with the above being the custom constructor that we have added taking the boolean showAbout variable.
All we need now is a mechanism for showing the dialog box. We could have added the dialog to the ChapterTwoAboutDemoWidget class but this would break the design of our application as the class derived from KMainWindow should handle code aspects that relate to the main application and the widget that deals with what goes on in the window area should handle only things that pertain to the window area. Ideally we would handle the box through a menu item that allows you to select to view the about box but we haven't got to that part yet, so what we do is override the show method from KMainWindow.
/// override show virtual void show();
If you look this up in the KMainWindow header file you will note that it says that KMainWindow itself overrides the show method from QMainWindow and that in KDE 4 this could be removed. It wont make a major difference to the code here though as if the override in KMainWindow is removed the code will just need to call QMainWindow::show() instead on KMainWindow show()
The whole overridden function looks like this,
void ChapterTwoAboutDemo::show() { KMainWindow::show(); /// show the about box if the arg was passed if( getShowAboutBox() == true ) { aboutDlg->setProduct( "Display About Demo", "1.0", "pseudonym67", "2005" ); aboutDlg->show(); } }
Debugging Code
If we try to run the code now we get the standard hello world application that so we'll debug the application to see what is going on. Firstly add a breakpoint to main.cpp
We add the breakpoint here so that we can see if the call to setShowAboutBox is being passed the correct argument. A breakpoint is set in KDevelop by left clicking on the left side of the screen where you can see the little grey circle, at the start of the red line in the image above. By default left clicking here will leave a bookmark which gives a paper clip image. If you choose to you can right click here and set breakpoint to be the default.
We'll also add a breakpoint to the ChapterTwoAboutDemo code,
which shows the point in the show function where we test if we want to display the about box or not. If we start the program through the menu Debug/Start then the code execution will stop in the main.cpp file
The line where we set our breakpoint is highlighted in green and at this point we can examine where the program is up to. There are a number of ways to examine the current program state and the usefulness of these really depends on the level of expertise of the person doing the debugging. The first thing to notice is the menu bar at the bottom of the screen
The last three menu items are the Frame Stack which will give a list of the function calls made within the program in order that they were made, so you can trace the way that the code has moved through the program and make sure that it hasn't done anything unexpected. The Disassemble window which gives the assembly code for the project and to be honest if you understand the assembly code you don't need to read this. And the GDB window. The GNU Project Debugger ( GDB ) attempts to tell you what is going on in the program at any point in time so if we look at the window we see.
Which
is the start of our program, showing the the placing of the
breakpoints, listing four of them here as I've been playing around
with things to see how they work and if the breakpoints are on or
off. Below that there is a print out of the program arguments and
below that there is the start of our main function that has an arg
count of one which is the call to the program itself. If we double
left click on the args variable on the line that the program is
stopped at then right click and select “watch args”, The GDB will
output a couple more lines
telling
us what it knows about the args variable. If we click on the
Variables menu one the left hand side we get,
We can see the args variable in the window on the left of the screen so we can monitor any variables within the program whenever we choose. Of course there is the slight technical point at the moment that it is not going to tell us anything because there is only one program argument and that is the name of the program itself. So stop debugging the program ( Debug/Stop ) and go to Project/Project Options/Debugger/Program arguments and add “-displayAbout” like last time. If you then hit Debug/Start when the program stops we will have some data to see.
Which shows us the name of one argument is called displayAbout. This means that we already know the outcome of the test for the breakpoint here which is what will the args variable be passing to setShowAboutBox function.
We can now move onto our other breakpoint and we do this by using the debugging toolbar.
The first button on the debug toolbar is the continue button that will carry on with the program until another breakpoint is reached or the program exits or the program crashes.
The second button on the debug toolbar allows us to step over the next line of code which means that it is executed as normal, so any function calls made on that line will be made as normal and the program execution will stop on the next line down.
The third button on the debug toolbar is the step into button that allows us to follow the code into a function called on the current line. The code execution will stop on the first line of code in the function that is being called.
The fourth button on the debug toolbar allows us to step out of the current function, executing the code in the function as normal and then stopping at the line of code below the line that called the current function.
If we hit the continue button in the debug toolbar the program will continue until we get to the breakpoint that we placed earlier to test the showAboutBox function.
If we now use the step into button we can check to see what answer the function will return.
Or of course we could just check the local information in the variable window by clicking on what is already there when we open it and looking at the “this” object
Now that we know that the function is going to return the answer that we want we can click on the continue button and the program will start up, displaying the about box first.
CVS The Easy Way
Up until now we have used CVS mostly through the Cervisia gui provided with Suse 10 but a much simpler and easier way exists to use CVS and that is directly through the KDevelop program. If we open up the File Tree on the left hand side menu bar we can see the files that have been modified since we created them.
If we select the chaptertwoaboutdemo.cpp file in the view and right click on it, at the bottom of the menu there is an option for CvsService. By moving down to the CvsService option we get a menu of CVS options with the top two options being the important one here. The second one down allows us to compare the current version of the file in our development directory with a version in CVS which we will usually want to be the most current version of the file stored there. The option at the top of the file is the Commit To Repository option which works exactly the same as if we were committing the file through Cervisia.
As with a commit through Cervisia you are prompted to add a message usually saying what the reason is for the changes to the file. This is always good practice and if you don't add a message at this point a dialog will appear reminding you that this is good practice.
When you click the OK button the CvsService menu bar along the bottom will display the results of the CVS operation.
The same operations can be performed directly on the files through the Automake Manager although the Automake Manager does not give any visual cues as to which files have been changed.
As with any version control system an important aspect of CvsService is the option to change the versions of the files that we are currently using so if you right click on the chaptertwoaboutdemo.cpp file in Automake Manager and select CvsService, then select Update/Revert to Another Release you'll get this dialog.
If you have been working through with the code instead of just reading it you can enter 1.1 for the generated version of the chaptertwoaboutdemo.cpp file. Clicking on the OK button will mean that the current project file will then be the original file. Note that to see this properly you need to close the file in the editor, if it is open and then reload it. You can update the file back to the currently latest version by selecting CvsService/Update again and typing in 1.2 into the “An arbitrary revision/tag/branch:” box.
One final note about CVS here and one that is approaching another religious topic is just when do you update the code in CVS. I've worked in some places where code has to be put in the repository every night whether it compiles or not but I can see sense in the point that you shouldn't get a version increase in your code until it does something different from the last version so you only update the repository when a change/update/feature is complete. This is one of those things that is largely going to be defined on a project basis which is something that you may or may not have a say in, so for the most part adopt the project practices but for your own projects use the CVS in the way that you are most comfortable with.
Documentation
Documentation has always been a pain when it comes to software, the main reasons for this being that in most business environments the emphasis is on working code and not on explaining how the code works when there's no guarantee that anyone will ever read it. Some people simply aren't interested and others simply aren't very good at it, they can write it in code but can't explain how they did it very well at all. Then there is the point of how do you do it. Formal methods of documentation take us straight into religious wars territory with people shouting the merits of x over y and others saying that neither of them allow them to express what it is they are really doing.
Then there's KDevelop and the built in Doxygen a combination that to be honest makes documenting source code so easy there really isn't any excuse not to do it. Doxygen takes the comments that we should always write in the code but mostly don't and generates a full set of documents that are disgustingly easy to navigate and understand.
To see what I mean, in Automake Manager left click on chaptertwoaboutdemo.h so that it shows up in the editor and then go to tools/Preview Doxygen Output and you'll get
Which is the main page of the documentation and shows just a header with the class version number as it appears in the header which we can get to if we click on the class list,
Here we see that the ChapterTwoAboutDemo class is the Main Window for the application which we defined in our header file as
/** * @short Application Main Window * @author pseudonym67 <pseudonym67@hotmail.com> * @version 0.1 */ class ChapterTwoAboutDemo : public KMainWindow {
If we click on the ChapterTwoAboutDemo text we get,
which shows us that while there is nothing wrong with the comments that have been placed in the code we could do a little better and make things more complete as the “variable access” comment is just floating, the overridden show function would be better documented as the constructors and destructor's are, and the custom constructor taking the boolean parameter is not documented at all.
If we edit the class definition so that it looks like this,
/** * @short Application Main Window * @author pseudonym67 <pseudonym67@hotmail.com> * @version 0.1 */ class ChapterTwoAboutDemo : public KMainWindow { Q_OBJECT private: bool bShowAboutBox; KAboutDialog *aboutDlg; public: /** * Get for show about box */ bool getShowAboutBox() { return bShowAboutBox; } /** * set for show about box */ void setShowAboutBox( bool show ) { bShowAboutBox = show; } /** * override show */ virtual void show(); public: /** * Default Constructor */ ChapterTwoAboutDemo(); /** * Custom Constructor with option to show the about dialog */ ChapterTwoAboutDemo( bool showAbout ); /** * Default Destructor */ virtual ~ChapterTwoAboutDemo(); };
The previewed file looks like this,
which is a lot better. As long as you remember to save the file first as Doxygen previews from the file saved on the disk which is not necessarily the same as the file currently open in the editor.
You can see from the examples that both “/**” and “///” can be used to specify a comment to be included in Doxygen which is just two of the methods that you can use. With the output depending on the programming language and how they are used. For our purposes we will be sticking with the “/**” from here on out. A full manual for Doxygen can be found at www.Doxygen.org
Generating The Documentation
It may or may not have escaped your notice but for some reason KDevelop only documents the Application Main Window by default. Since the bulk of the work of any program is going to be done by the main widget that is contained within the application we should probably document this as well. If you click on the chaptertwoaboutdemowidget.h file in Automake Manager to open it and edit it to look like,
/** * @short Application Main Widget * @author pseudonym67 <pseudonym67@hotmail.com> * @version 0.1 */ class ChapterTwoAboutDemoWidget : public ChapterTwoAboutDemoWidgetBase { Q_OBJECT public: /** * Default constructor */ ChapterTwoAboutDemoWidget(QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); /** * Default destructor */ ~ChapterTwoAboutDemoWidget(); public slots: /** * Fired when "Click Me" button is pressed */ virtual void button_clicked(); protected: protected slots:
};
If we preview the class with tools/Preview Doxygen Output we get,
Now we can build the documentation for our project go to Build/Build API Documentation. The KDevelop Messages window will be focused on an the Doxygen output will appear there. The window will give warnings of anything that isn't documented that Doxygen thinks should be. For this reason there will be a lot of warnings at the start referring to the chaptertwoaboutdemowidgetbase.h and mostly the chaptertwoaboutdemo.moc file. Both of these files are generated from the chaptertwoaboutdemowidgetbase.ui file by the compiler, we shall look at these generated files next but you can't do anything about warning messages generated here and it's not a good idea to try as the files will be regenerated from scratch the next time you do a build of the project.
By default there are three document types output by Doxygen. The one we are focusing on here is the html documentation which is located in the html folder off the project directory folder so in this case it's at chaptertwoaboutdemo/html. A Latex folder is also created with the build files for the latex documentation. To view this open a Konsole in the Latex directory and type make, alternatively if you want pdf documentation type “make pdf” here. The third directory created by Doxygen is the xml directory which the Doxygen manual says is still under development. You can view the files in an xml viewer but for now it is probably better to leave them alone.
The Doxygen output is exactly as we have seen in the previews when we look through the html folder with the exception that the Class List now looks like
Doxygen Options
You can set the options for Doxygen in Projects/Project Options/Doxygen.
If you scroll along until you get to the Dot tab and uncheck the “Hide undocumented relations” box Doxygen will include a class diagram for the hello world classes in your html page.
If you then check the “Use Dot” option on the same page you get more detailed diagramming.
Dot is part of Graphviz and information about it can be found at www.Graphviz.org By default it will also include a collaboration diagram for you class,
which isn't half bad for a file that still looks like this,
There are plenty of options we haven't looked at here and you're encouraged to play around with them to see what they do. For instance you could include a search engine or have it build your documentation into a Windows style .chm help file which you can show to Windows programmers and say “Yeah it took me ages to get the diagrams right”.
But we can't avoid it any more we're going to have to take a look at what is going on in the background with the ChapterTwoAboutDemoWidget class.
Generated Files
There are so far three files that we have come across that are generated from the ui file, which if you remember is the xml file generated from the form,
These files are the chaptertwoaboutdemowidgetbase.h, chaptertwoaboutdemowidgetbase.cpp and chaptertwoaboutdemowidgetbase.moc. The chaptertwoaboutdemowidgetbase.h and the chaptertowaboutdemowidgetbase.cpp are generated directly from the gui and resemble the sort of file that you would expect to see in any Windows programming environment as they directly control whatever it is that is on the form.
/**************************************************************************** ** Form interface generated from reading ui file '/home/pseudonym67/Dev/KDE/BeginningKDEPRogramming/chaptertwoaboutdemo/src/chaptertwoaboutdemowidgetbase.ui' ** ** Created: Mon Dec 5 19:22:09 2005 ** by: The User Interface Compiler ($Id: qt/main.cpp 3.3.4 edited Nov 24 2003 $) ** ** WARNING! All changes made in this file will be lost! ****************************************************************************/ #ifndef CHAPTERTWOABOUTDEMOWIDGETBASE_H #define CHAPTERTWOABOUTDEMOWIDGETBASE_H #include <qvariant.h> #include <qwidget.h> class QVBoxLayout; class QHBoxLayout; class QGridLayout; class QSpacerItem; class QPushButton; class QLabel; class ChapterTwoAboutDemoWidgetBase : public QWidget { Q_OBJECT public: ChapterTwoAboutDemoWidgetBase( QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); ~ChapterTwoAboutDemoWidgetBase(); QPushButton* button; QLabel* label; public slots: virtual void button_clicked(); protected: QGridLayout* chaptertwoaboutdemowidgetbaseLayout; protected slots: virtual void languageChange(); }; #endif // CHAPTERTWOABOUTDEMOWIDGETBASE_H
In the generated ChapterTwoAboutDemoWidgetBase class we can see pointers to the button and the label on the form and a slot declared for when the button is clicked. We can also see a pointer to QGridLayout has been included which controls the layout of the items on the form. We will look at the layouts of forms in the next chapter. and there is finally a languageChange function declared.
These are all created and implementated in the chaptertwoaboutdemowidgetbase.cpp file
#include <kdialog.h> #include <klocale.h> /**************************************************************************** ** Form implementation generated from reading ui file '/home/pseudonym67/Dev/KDE/BeginningKDEProgramming/chaptertwoaboutdemo/src/chaptertwoaboutdemowidgetbase.ui' ** ** Created: Mon Dec 5 19:22:12 2005 ** by: The User Interface Compiler ($Id: qt/main.cpp 3.3.4 edited Nov 24 2003 $) ** ** WARNING! All changes made in this file will be lost! ****************************************************************************/ #include "chaptertwoaboutdemowidgetbase.h" #include <qvariant.h> #include <qpushbutton.h> #include <qlabel.h> #include <qlayout.h> #include <qtooltip.h> #include <qwhatsthis.h> /* * Constructs a ChapterTwoAboutDemoWidgetBase as a child of 'parent', with the * name 'name' and widget flags set to 'f'. */ ChapterTwoAboutDemoWidgetBase::ChapterTwoAboutDemoWidgetBase( QWidget* parent, const char* name, WFlags fl ) : QWidget( parent, name, fl ) { if ( !name ) setName( "chaptertwoaboutdemowidgetbase" ); chaptertwoaboutdemowidgetbaseLayout = new QGridLayout( this, 1, 1, 11, 6, "chaptertwoaboutdemowidgetbaseLayout"); button = new QPushButton( this, "button" ); chaptertwoaboutdemowidgetbaseLayout->addWidget( button, 1, 0 ); label = new QLabel( this, "label" ); chaptertwoaboutdemowidgetbaseLayout->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 */ ChapterTwoAboutDemoWidgetBase::~ChapterTwoAboutDemoWidgetBase() { // no need to delete child widgets, Qt does it all for us } /* * Sets the strings of the subwidgets using the current * language. */ void ChapterTwoAboutDemoWidgetBase::languageChange() { setCaption( QString::null ); button->setText( tr2i18n( "Click Me!" ) ); label->setText( QString::null ); } void ChapterTwoAboutDemoWidgetBase::button_clicked() { qWarning( "ChapterTwoAboutDemoWidgetBase::button_clicked(): Not implemented yet" ); } #include "chaptertwoaboutdemowidgetbase.moc"
The constructor of chaptertwoaboutdemowidgetbase.cpp is where all the fun happens, it starts by checking to see if the “name” value is not 0. Which if you remember is called from the default constructor from our inheriting class,
ChapterTwoAboutDemoWidget::ChapterTwoAboutDemoWidget(QWidget* parent, const char* name, WFlags fl) : ChapterTwoAboutDemoWidgetBase(parent,name,fl) {}
which is instantiated as,
setCentralWidget( new ChapterTwoAboutDemoWidget( this ) );
and means that as the constructor is called with only one parameter then the other two parameters take their default values in the constructor declaration,
/** * Default constructor */ ChapterTwoAboutDemoWidget(QWidget* parent = 0, const char* name = 0, WFlags fl = 0 );
This means that the line
setName( "chaptertwoaboutdemowidgetbase" );
is executed and the name for the widget is chaptertwoaboutdemowidgetbase. The next line
chaptertwoaboutdemowidgetbaseLayout = new QGridLayout( this, 1, 1, 11, 6, "chaptertwoaboutdemowidgetbaseLayout");
we're going to skip for now as we'll be looking at grid layouts later and the following four lines should be simple enough,
button = new QPushButton( this, "button" ); chaptertwoaboutdemowidgetbaseLayout->addWidget( button, 1, 0 ); label = new QLabel( this, "label" ); chaptertwoaboutdemowidgetbaseLayout->addWidget( label, 0, 0 );
The two objects the QPushButton and the QLabel are created as children of the chaptertwoaboutdemowidgetbase class and then added to the grid layout passing the row and the column number so that the layout class can determine where they go, remember though that we wont be calling this code often if at all ourselves as this code is generated from what we do in the form designer.
The next section in the file sets up the signals and slots that have been defined in the form designer
// signals and slots connections connect( button, SIGNAL( clicked() ), this, SLOT( button_clicked() ) );
This is done by calling the QObject function connect that takes the object that is emitting the signal as it's first parameter, the signal that is being emitted as it's second parameter, the object that is receiving the signal as it's third parameter and the slot or function that is called when the signal is emitted. Which as far as we as users of the Qt Library are concerned when we click on the button our button clicked function is called.
Or at least mostly anyway what we have to remember from the libraries point of view is that we are talking about generated files here so the function that originally deals with the button clicked signal is defined at the end of the cpp file as
void ChapterTwoAboutDemoWidgetBase::button_clicked() { qWarning( "ChapterTwoAboutDemoWidgetBase::button_clicked(): Not implemented yet" ); }
But seeing as KDevelop gives us a class that inherits from ChapterTwoAboutDemoWidgetBase and automatically gives us an override on this function we should never see the warning unless we delete the slot function
public slots: /** * Fired when "Click Me" button is pressed */ virtual void button_clicked();
in the ChapterTwoAboutDemoWidget class.
The only things that we have missed so far are the languageChange function which sets the I18N specified text strings to the current language selection and the final line
#include "chaptertwoaboutdemowidgetbase.moc"
Which appends the chaptertwoaboutdemowidegetbase.moc file at the end,
/**************************************************************************** ** ChapterTwoAboutDemoWidgetBase meta object code from reading C++ file 'chaptertwoaboutdemowidgetbase.h' ** ** Created: Mon Dec 5 19:22:12 2005 ** by: The Qt MOC ($Id: qt/moc_yacc.cpp 3.3.4 edited Jan 21 18:14 $) ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ #undef QT_NO_COMPAT #include "chaptertwoaboutdemowidgetbase.h" #include <qmetaobject.h> #include <qapplication.h> #include <private/qucomextra_p.h> #if !defined(Q_MOC_OUTPUT_REVISION) || (Q_MOC_OUTPUT_REVISION != 26) #error "This file was generated using the moc from 3.3.4. It" #error "cannot be used with the include files from this version of Qt." #error "(The moc has changed too much.)" #endif const char *ChapterTwoAboutDemoWidgetBase::className() const { return "ChapterTwoAboutDemoWidgetBase"; } QMetaObject *ChapterTwoAboutDemoWidgetBase::metaObj = 0; static QMetaObjectCleanUp cleanUp_ChapterTwoAboutDemoWidgetBase( "ChapterTwoAboutDemoWidgetBase", &ChapterTwoAboutDemoWidgetBase::staticMetaObject ); #ifndef QT_NO_TRANSLATION QString ChapterTwoAboutDemoWidgetBase::tr( const char *s, const char *c ) { if ( qApp ) return qApp->translate( "ChapterTwoAboutDemoWidgetBase", s, c, QApplication::DefaultCodec ); else return QString::fromLatin1( s ); } #ifndef QT_NO_TRANSLATION_UTF8 QString ChapterTwoAboutDemoWidgetBase::trUtf8( const char *s, const char *c ) { if ( qApp ) return qApp->translate( "ChapterTwoAboutDemoWidgetBase", s, c, QApplication::UnicodeUTF8 ); else return QString::fromUtf8( s ); } #endif // QT_NO_TRANSLATION_UTF8 #endif // QT_NO_TRANSLATION QMetaObject* ChapterTwoAboutDemoWidgetBase::staticMetaObject() { if ( metaObj ) return metaObj; QMetaObject* parentObject = QWidget::staticMetaObject(); static const QUMethod slot_0 = {"button_clicked", 0, 0 }; static const QUMethod slot_1 = {"languageChange", 0, 0 }; static const QMetaData slot_tbl[] = { { "button_clicked()", &slot_0, QMetaData::Public }, { "languageChange()", &slot_1, QMetaData::Protected } }; metaObj = QMetaObject::new_metaobject( "ChapterTwoAboutDemoWidgetBase", parentObject, slot_tbl, 2, 0, 0, #ifndef QT_NO_PROPERTIES 0, 0, 0, 0, #endif // QT_NO_PROPERTIES 0, 0 ); cleanUp_ChapterTwoAboutDemoWidgetBase.setMetaObject( metaObj ); return metaObj; } void* ChapterTwoAboutDemoWidgetBase::qt_cast( const char* clname ) { if ( !qstrcmp( clname, "ChapterTwoAboutDemoWidgetBase" ) ) return this; return QWidget::qt_cast( clname ); } bool ChapterTwoAboutDemoWidgetBase::qt_invoke( int _id, QUObject* _o ) { switch ( _id - staticMetaObject()->slotOffset() ) { case 0: button_clicked(); break; case 1: languageChange(); break; default: return QWidget::qt_invoke( _id, _o ); } return TRUE; } bool ChapterTwoAboutDemoWidgetBase::qt_emit( int _id, QUObject* _o ) { return QWidget::qt_emit(_id,_o); } #ifndef QT_NO_PROPERTIES bool ChapterTwoAboutDemoWidgetBase::qt_property( int id, int f, QVariant* v) { return QWidget::qt_property( id, f, v); } bool ChapterTwoAboutDemoWidgetBase::qt_static_property( QObject* , int , int , QVariant* ){ return FALSE; } #endif // QT_NO_PROPERTIES
This is the Meta Object Compiled file and we specified that we wanted it by adding
Q_OBJECT
to our header file, or at least by not deleting it when KDevelop added to our class header. I'm not going to go into this file in detail as it's getting into how the entire Qt library works territory which is beyond the scope of not only this chapter but this book. Suffice to say that even a cursory glance at the file shows that this is the glue that makes the language and function calls when buttons etc are pressed work with it's definition of the emit and invoke functions.
Summary
In this chapter we have looked at the make up of KDE Application including the basic tools that we will be using on a day to day basis such as CVS, documentation and a basic look at the requirements of using the debugger. We ended with a look at the files that are generated in the background for us, giving us an idea of how the program works so we can hopefully approach the programming with a little understanding.