As we have seen, we have already provided KScribble the ability to open and save pictures with the document class and enabled userinteraction by overwriting virtual methods in the view class and we gained the first functionaliy - we can draw pictures as well. Butwhen we created the QPen
instance in the document class, we set some pre-defined values for the pen; the color is black and the penwidth set to 3 pixels. As you usually want to change these values in a drawing application, we have to enhance the main GUI byproviding ways to set these, according to the currently active window and document connected to it. This chapter will thereforeintroduce you to:
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 intothe menubar are instances of QPopupMenu
, and you can have a look at how the current menubar is created when you switch to theKScribbleApp
class, method initMenubar()
. You will see that the menubar items are created in the order they appear on themenubar - but this isn't necessary. There are two things important on how the menubar will look like:
kscribbleapp.hclass KScribbleApp{.. private: QPopupMenu* pPenMenu;}
KScribbleApp::initMenuBar()
and add thelines 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)));}
new QPopupMenu()
. Then we use the insertItem methods to add two menu entries, Colorand Brush. The visible commands are inserted with the method i18n()
, which ensures that you can internationalize yourappliction. So as a general rule, you would declare all visual text that will appear later by the method i18n()
. Qt-onlyprograms using Qt > 2.0 would use the according method tr()
instead of i18n()
, as Qt has it's own ways ofinternationalizing applications. The second argument is a macro, the ID of the menubar item. This ID is a number that we have to setusing #define in the file resource.h, where you will see all other already used ID's declared. There are also other ways to insertmenus by directly connecting a slot to the inserted entry, but the application framework uses ID's to select which action has beenactivated- and highlighted. Therefore each menu entry, independent of the popup menu it appears, has to be a unique number, and as wecan hardly remember numbers later, setting a #define for the ID is a nice solution. The popup menu is now inserted into the menubarwith 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 inthe menubar. What is also important when creating menus is that they should be available to the user with shortcuts; ususally in menusyou will see underlined characters that the user can jump to directly by pressing ALT and the according underlined letter of themenuitem. As a programmer, you have to set this character by a leading ampersand, so the "Pen" menu will later be accessible via thekeyboard by pressing ALT+P. Within the menu, the user can press another button to go directly to the command he wants to, so in themenu all items should have this kind of shortcuts as well. Note that you should write item insertions together in groups that have thesame visible access, so you can keep a better overview of the characters you already used so that there are no menu accelerators usedtwice. (this is also important for your translators: in other languages the used accelerator may not be available in the translatedword, 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 actionshould be executed, and one for when it is highlighted. That allows making a statusbar help message available for the user. You canhave a look at the methods the menu is connected to, they contain switch statements where the sent menu ID is compared and thefollowing 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
Whenever you think that some new commands should be made available by toolbar buttons as well because they are often used and you wantto offer additional functionality, you can easily do that by adding buttons in the framework's initToolBar()
method of theApp
class. Here, we decide to add a button for both menu entries in the Pen-menu, but those need icons - which you can eitherfind in the KDE directory /toolbar or, when you don't find an icon that matches your action, have to create yourself. KIconEdit is verysuitable 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 newdirectory "toolbar". Change to that directory and press "OK". The new icon will then be created in the new directory "toolbar" and willbe 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, youhave 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 containingadditional files needed by the application. Icons have to be installed into another subdirectory "toolbar", so the application's iconloader 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 forthe 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 itworks with xpm's as well. (You could use ImageMagick for that as well which can do that- or use KScribble in a later step toconvert your icons to PNG !)The second argument is again the ID. The commands are then automatically activated, as the toolBar()
is already connected tothe 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 alsoembrace 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- ofcourse 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'tbe 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. Forthis, 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 beset; a spinbox with up and down buttons to set the pen width value and three buttons, one for resetting the pen width to the defaultvalue, 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 thedialog - which is important because the tab-focus follows the order by which the widgets are created. So if you´re starting with the OKbutton, then the spinbox and then the cancel button, the input focus will change from the ok button to the spinbox and then to thecancel button - which is not what the user expects. The tab focus should follow the widget´s items top-down from left to right, so wehave to construct the dialog in this order as well. To add items to the dialog, select the "Widgets" tab on the left pane. There youhave all available widgets present by icons to construct your dialog. Pressing a widget button will create the new item and place it atthe 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 labelto 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 setthe 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 laststep. Now set the variable name in section "C++Code" to "width_spbox". The minimum and maximum values are 1 and 100, which should lastfor setting the brush width.The Buttons: finally, we need the mentioned three buttons. The leftmost button will be the default button. Create aQPushbutton
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 thebutton 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. Nowyou 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 theselected value of the spinbox - because we want the dialog to display the current value when it gets called and to access the selectedvalue 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 godirectly to add the usual connections for the push buttons first. Add the lines marked by arrows to the constructor of thedialog:
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()));}
slotDefault()
. This slot is still to be implemented below, where we will set the default valueof the spinbox directly.The second connect()
call connects the ok button to call the slot accept()
provided by QDialog
, as well as the cancelbutton gets connected to QDialog
´s slot reject()
. This will both close the dialog and will set the result value which we willuse 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(); };
setCurrent()
method will be used to set the current value the pen has, the width()
methodreturns 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);}
As you may guess, calling the dialogs means that we will not only implement calling our width selection dialog but also add the methodfor 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."));}
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 whenthe result code of the dialog has the accept flag set - which means, the code is executed if the user pressed the OK button on thedialog.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 thefor()
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 ); }
commandCallback()
, which selects and executes the accordingmethods 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;.... }}
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 aswell:
kscribbledoc.h: /** sets the pen color */ void setPenColor( const QColor &c ){ pen.setColor( c ); }
slotPenBrush()
and slotPenColor()
to the classKScribbleApp
, so our class knows about these methods.Now you´re ready ! Let´s summarize what we´ve done in this chapter: