Next Previous Table of Contents
As we have seen, we have already provided KScribble the ability to open and save pictures with the document class and enabled user
interaction by overwriting virtual methods in the view class and we gained the first functionaliy - we can draw pictures as well. But
when we created the QPen
instance in the document class, we set some pre-defined values for the pen; the color is black and the pen
width set to 3 pixels. As you usually want to change these values in a drawing application, we have to enhance the main GUI by
providing ways to set these, according to the currently active window and document connected to it. This chapter will therefore
introduce you to:
Further, we also add a method to delete the document contents at all with a menubar command.
As the title of this section says, we will add a menu for setting the pen values of the documents here. Menus that are inserted into
the menubar are instances of QPopupMenu
, and you can have a look at how the current menubar is created when you switch to the
KScribbleApp
class, method initMenubar()
. You will see that the menubar items are created in the order they appear on the
menubar - but this isn't necessary. There are two things important on how the menubar will look like:
Last but not least you have to create menus first with calling the constructor. The class declaration already contains the pointers to the popup menus, so we will have to add our "Pen" menu here first:
kscribbleapp.h class KScribbleApp { . . private: QPopupMenu* pPenMenu; }
Now we are going to create the menu itself. Change to the implementation of the method KScribbleApp::initMenuBar()
and add the
lines marked with an arrow:
void KScribbleApp::initMenuBar() { .. -> /////////////////////////////////////////////////////////////////// -> // menuBar entry pen-Menu -> pPenMenu = new QPopupMenu(); -> pPenMenu->insertItem(i18n("&Color"), ID_PEN_COLOR); -> pPenMenu->insertItem(i18n("&Brush"), ID_PEN_BRUSH); menuBar()->insertItem(i18n("&Edit"), pEditMenu); -> menuBar()->insertItem(i18n("&Pen"), pPenMenu); menuBar()->insertItem(i18n("&View"), pViewMenu); -> connect(pPenMenu, SIGNAL(activated(int)), SLOT(commandCallback(int))); -> connect(pPenMenu, SIGNAL(highlighted(int)), SLOT(statusCallback(int))); }
You see that we first create the menu with new QPopupMenu()
. Then we use the insertItem methods to add two menu entries, Color
and Brush. The visible commands are inserted with the method i18n()
, which ensures that you can internationalize your
appliction. So as a general rule, you would declare all visual text that will appear later by the method i18n()
. Qt-only
programs using Qt > 2.0 would use the according method tr()
instead of i18n()
, as Qt has it's own ways of
internationalizing applications. The second argument is a macro, the ID of the menubar item. This ID is a number that we have to set
using #define in the file resource.h, where you will see all other already used ID's declared. There are also other ways to insert
menus by directly connecting a slot to the inserted entry, but the application framework uses ID's to select which action has been
activated- and highlighted. Therefore each menu entry, independent of the popup menu it appears, has to be a unique number, and as we
can hardly remember numbers later, setting a #define for the ID is a nice solution. The popup menu is now inserted into the menubar
with insertItem()
as well, and with the pointer to the menu as second argument.
Note that we inserted the popup menu after the "Edit" menu and before the "View" menu, so it will appear between those menus later in the menubar. What is also important when creating menus is that they should be available to the user with shortcuts; ususally in menus you will see underlined characters that the user can jump to directly by pressing ALT and the according underlined letter of the menuitem. As a programmer, you have to set this character by a leading ampersand, so the "Pen" menu will later be accessible via the keyboard by pressing ALT+P. Within the menu, the user can press another button to go directly to the command he wants to, so in the menu all items should have this kind of shortcuts as well. Note that you should write item insertions together in groups that have the same visible access, so you can keep a better overview of the characters you already used so that there are no menu accelerators used twice. (this is also important for your translators: in other languages the used accelerator may not be available in the translated word, so they have to set some accelerators again.)
In the last two lines we're connecting the pen menu with two slots: one for when the menu signals that it is activated and the action should be executed, and one for when it is highlighted. That allows making a statusbar help message available for the user. You can have a look at the methods the menu is connected to, they contain switch statements where the sent menu ID is compared and the following action called.
What is left to do is to add the #define statements to the file resource.h:
resource.h /////////////////////////////////////////////////////////////////// // Pen-menu entries #define ID_PEN_COLOR 14010 #define ID_PEN_BRUSH 14020
You will see that the numbers are unique for these entries- you have to watch out not to set the same number for two entries- but if it happens by accident, there's still the compiler that informs you about redefining.
This is currently all you have to do to add a new menu for your menubar. The actions they will execute are: "Color" will call a color selection dialog, "Brush" will call a dialog (which we still have to create) to select the brush width.
But first we'll extend the toolbar as well by two icons for these actions in the next section.
Whenever you think that some new commands should be made available by toolbar buttons as well because they are often used and you want
to offer additional functionality, you can easily do that by adding buttons in the framework's initToolBar()
method of the
App
class. Here, we decide to add a button for both menu entries in the Pen-menu, but those need icons - which you can either
find in the KDE directory /toolbar or, when you don't find an icon that matches your action, have to create yourself. KIconEdit is very
suitable to paint icons, so we will first create them. Choose "New" from the KDevelop "File" menu and select "Icon" as the filetype.
The first icon will be named "pencolor.xpm". Now we have to select where we want to have the icon created in our project directory.
Press the directory selection button and change to your project directory containing the KScribble sources. Then create a new
directory "toolbar". Change to that directory and press "OK". The new icon will then be created in the new directory "toolbar" and will
be opened by KIconEdit within KDevelop automatically. Paint something that will signalize the user what the button is intended to do,
save the pixmap and then switch to the RFV / LFV in KDevelop. Select the icon by a right mouse button press and select "Properties"
from the popup menu. You will see that the icon is included in the distribution, but for your program to find the icon again later, you
have to set the installation destination as well. Check the "install" option and enter into the line now active below:
$(kde_datadir)/kscribble/toolbar/pencolor.xpm
This will install the pixmap in the KDE file system hierarchy's data directory, where each application has its subdirectory containing additional files needed by the application. Icons have to be installed into another subdirectory "toolbar", so the application's icon loader can find the pixmaps for your program.
After you're finished, repeat all these above steps with the second icon for selecting the pen width. Name this pixmap "penwidth.xpm".
Now we only have to insert the buttons into the toolbar; add the lines marked with the arrow into your code:
void KScribbleApp::initToolBar() { .. toolBar()->insertButton(BarIcon("editcopy"), ID_EDIT_COPY, true, i18n("Copy")); toolBar()->insertButton(BarIcon("editpaste"), ID_EDIT_PASTE, true, i18n("Paste")); toolBar()->insertSeparator(); -> toolBar()->insertButton(BarIcon("pencolor"), ID_PEN_COLOR, true, i18n("Color") ); -> toolBar()->insertButton(BarIcon("penwidth"), ID_PEN_BRUSH, true, i18n("Width") ); -> toolBar()->insertSeparator(); toolBar()->insertButton(BarIcon("help"), ID_HELP_CONTENTS, SIGNAL(clicked()), .. }
BarIcon()
, tells the method to load the icon for
the button. What seems unusual is that we don't have to care for the file extension. The preferred format for KDE 2 is *.PNG, but it
works with xpm's as well. (You could use ImageMagick for that as well which can do that- or use KScribble in a later step to
convert your icons to PNG !)
The second argument is again the ID. The commands are then automatically activated, as the toolBar()
is already connected to
the same methods as the menubar is for signal activated()
. The third argument stands for "available" when true, "deactivated"
when false; as we want to have these available, we set this to true. At last, we add a tooltip for the the buttons, which we also
embrace with i18n()
to allow internationalization.
Now you're done for now- the GUI is extended at least visually. You can compile and run KScribble again and see how it looks like- of course the new items in the menubar and toolbar can't execute any action - that is what we're going to add in the next section. You will also note that the toolbar icons we added are not displayed - which is because we didn't install KScribble and so they can't be found. All other used icons are already shipped with the KDE libraries, so these are already visible.
As we´ve already created the according menubar and toolbar commands, we now have to build the first dialog to set the pen width. For
this, select "New" from the KDevelop "File" menu and select "Qt/KDE Dialog". Then enter the dialog file name as kpenbrushdlg
,
the extension will be automatically added. Enter "OK" and the dialogeditor opens an empty widget that will be our dialog background.
When constructing a dialog, we have to think about what is really needed by the user; here, we need a label to display what will be
set; a spinbox with up and down buttons to set the pen width value and three buttons, one for resetting the pen width to the default
value, one to cancel the dialog and one for taking over the new value - the OK button. In this order we will add the items to the
dialog - which is important because the tab-focus follows the order by which the widgets are created. So if you´re starting with the OK
button, then the spinbox and then the cancel button, the input focus will change from the ok button to the spinbox and then to the
cancel button - which is not what the user expects. The tab focus should follow the widget´s items top-down from left to right, so we
have to construct the dialog in this order as well. To add items to the dialog, select the "Widgets" tab on the left pane. There you
have all available widgets present by icons to construct your dialog. Pressing a widget button will create the new item and place it at
the top-left corner of the widget. From there, you can place it with the mouse to the position you would like it to show up. Further,
when a widget item is selected, you can set the according values in the "Widget Properties" pane on the right.
The Label: press the "QLabel" button on the "Widgets" tab and place it at position x:50, y:20. Then select the "General" section in the widget properties pane. Change the text in properity "Text" from "Label" to "Pen Width:". Adjust the width of the label to a width that matches the label contents in x-direction; a width of 120 should last. You can do this either by using the mouse or set the value in the "Geometry" section of the properties.
The Spinbox: press the "QSpinBox" button on the "Widgets" tab and place it at the right of the label we created in the last step. Now set the variable name in section "C++Code" to "width_spbox". The minimum and maximum values are 1 and 100, which should last for setting the brush width.
The Buttons: finally, we need the mentioned three buttons. The leftmost button will be the default button. Create a
QPushbutton
and place it somewhere nicely on the bottom of the dialog, set the variable name to "default_btn" and the button text to
"Default". Proceed with the OK button with variable name "ok_btn" and the cancel button with variable name "cancel_btn" and set the
button text to "&OK" and "&Cancel".
If you´re fine with the layout of the dialog, choose "Generate complete sources" from the Build menu and set the classname to
"KPenBrushDlg", the inheritance to QDialog
. After pressing "OK", the sources for the dialog are created and added to the project. Now
you can return to the editor view in KDevelop and we can add the code needed to give the dialog some execution purpose.
After we have created the GUI of the dialog, we have to add some functionality to the buttons and provide ways to set and retrieve the selected value of the spinbox - because we want the dialog to display the current value when it gets called and to access the selected value when the user pressed the OK button to quit the dialog.
In the generated class for the dialog, KPenBrushDlg
, you can see one method besides the constructor and the destructor,
initDialog()
. This method implements the whole GUI construction, so we don´t have to care for that anymore and we can go
directly to add the usual connections for the push buttons first. Add the lines marked by arrows to the constructor of the
dialog:
KPenBrushDlg::KPenBrushDlg(int curr, QWidget *parent, const char *name) : QDialog(parent,name,true){ initDialog(); -> connect(default_btn, SIGNAL(clicked()), this, SLOT(slotDefault())); -> connect(ok_btn, SIGNAL(clicked()), this, SLOT(accept())); -> connect(cancel_btn, SIGNAL(clicked()), this, SLOT(reject())); }
This provides the functionality for the buttons on the bottom of the dialog when the user clicks the button. First, we set the default
button to execute a slot called slotDefault()
. This slot is still to be implemented below, where we will set the default value
of the spinbox directly.
The second connect()
call connects the ok button to call the slot accept()
provided by QDialog
, as well as the cancel
button gets connected to QDialog
´s slot reject()
. This will both close the dialog and will set the result value which we will
use later when we implement the method that calls the dialog to determine if we want to use the value set or to ignore any changes.
Now we have to add two methods to set and retrieve the spinbox value:
void setCurrent(int curr){ width_spbox->setValue(curr); } int width() { return width_spbox->value(); };
Add these methods to the class declaration with the modifier "public", as we want to set and retrieve the values when we call the
dialog to show up. The setCurrent()
method will be used to set the current value the pen has, the width()
method
returns us the selected with when the user presses OK and we want to know which value has been chosen.
Last but not least, we need to implement the slotDefault()
method:
//kpenbrushdlg.h: //method declaration: public slots: void slotDefault(); //kpenbrushdlg.cpp: //method implementation: void KPenBrushDlg::slotDefault() { width_spbox->setValue(3); }
This will set the default value to 3 pixels for the pen.
Now we´re ready with our first dialog and we can turn to over to the other application classes to adapt some things and add the method calls to invoke the dialog.
As you may guess, calling the dialogs means that we will not only implement calling our width selection dialog but also add the method
for selecting the pen color, but one after another. First, create a method slotPenBrush()
in the class KScribbleApp
:
void KScribbleApp::slotPenBrush() { slotStatusMsg(i18n("Setting brush width...")); // get one window with document for a current pen width QWidgetList windows = pWorkspace->windowList(); KScribbleView* m = (KScribbleView*)windows.at(0); KScribbleDoc* pDoc = m->getDocument(); int curr_width=pDoc->penWidth(); // create the dialog, get the new width and set the pen width for all documents KPenBrushDlg* dlg= new KPenBrushDlg(this); dlg->setCurrent(curr_width); if(dlg->exec()){ int width=dlg->width(); for ( int i = 0; i < int(windows.count()); ++i ) { m = (KScribbleView*)windows.at(i); if ( m ) { pDoc = m->getDocument(); pDoc->setPenWidth(width); } } } slotStatusMsg(i18n("Ready.")); }
Now we can call the dialog by creating the dlg instance of KPenBrushDlg. Then we set the current pen width by calling
dlg->setCurrent()
, which method we added to the dialog.
By calling dlg->exec()
we invoke the dialog. The if()
statement ensures that the following code is only executed when
the result code of the dialog has the accept flag set - which means, the code is executed if the user pressed the OK button on the
dialog.
Assuming the user changed the value and pressed OK, we have to set all documents to use the new pen width. For that we use the
for()
loop and set every document´s pen width to the width variable we retrieved before with dlg->width()
.
We don´t have implemented the method setPenWidth()
in the document class, so we´ll do this right now:
kscribbledoc.h: public: void setPenWidth( int w ){ pen.setWidth( w ); }
What is missing to execute any action is to add the methods that shall be called when the menu items are activated or the toolbar
buttons pressed. For this, we have to add the ID´s to the slot commandCallback()
, which selects and executes the according
methods we want to call if a menu or toolbar item was chosen:
void KScribbleApp::commandCallback(int id_) { switch (id_) { case ID_PEN_BRUSH: slotPenBrush(); break; case ID_PEN_COLOR: slotPenColor(); break; .... } }
This addition also adds the slotPenColor()
method to the execution list to set the pen color, which we will implement now:
void KScribbleApp::slotPenColor() { slotStatusMsg(i18n("Selecting pen color...")); QColor myColor; int result = KColorDialog::getColor( myColor, this ); if ( result == KColorDialog::Accepted ) { QWidgetList windows = pWorkspace->windowList(); KScribbleDoc* pDoc; KScribbleView* m; for ( int i = 0; i < int(windows.count()); ++i ) { m = (KScribbleView*)windows.at(i); if ( m ) { pDoc = m->getDocument(); pDoc->setPenColor(myColor); } } } slotStatusMsg(i18n("Ready.")); }
KScribbleDoc
to set the pen color. This one has to be implemented as
well:
kscribbledoc.h: /** sets the pen color */ void setPenColor( const QColor &c ){ pen.setColor( c ); }
Watch out for adding the declaration of the two new methods slotPenBrush()
and slotPenColor()
to the class
KScribbleApp
, so our class knows about these methods.
Now you´re ready ! Let´s summarize what we´ve done in this chapter:
By this structure, you are provided the general way how to extend your application with more functionality and manipulating settings that influence the behavoir of the document and view interaction.
Next Previous Table of Contents