Következő Előző Tartalom

10. A GUI bővítése

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:

Further, we also add a method to delete the document contents at all with a menubar command.

10.1 Adding the "Pen" Menu

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:

Last but not least you have to create menus first with calling the constructor. The class declaration already contains the pointers tothe popup menus, so we will have to add our "Pen" menu here first:
kscribbleapp.hclass KScribbleApp{..  private:    QPopupMenu* pPenMenu;}

Now we are going to create the menu itself. Change to the implementation of the method 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)));}

You see that we first create the menu with 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

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 ithappens 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 colorselection 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.

10.2 Adding Toolbar Buttons

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.xpmThis 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()),..}

Here, we use the methods of KToolBar to insert buttons. The first argument, 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.

10.3 Creating the Pen Width Dialog

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.

10.4 Connections and Setting Up

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()));}

This provides the functionality for the buttons on the bottom of the dialog when the user clicks the button. First, we set the defaultbutton to execute a slot called 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(); };

Add these methods to the class declaration with the modifier "public", as we want to set and retrieve the values when we call thedialog to show up. The 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);}

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 methodcalls to invoke the dialog.

10.5 Calling the Dialogs

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."));}

Here, we first have to access the window list and retrieve a pointer to a document - which can be a document of any window, because alldocuments should have the same current pen width. Then we create an integer variable curr_width that stores the current pen width.Now we can call the dialog by creating the dlg instance of KPenBrushDlg. Then we set the current pen width by callingdlg->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 ); }

What is missing to execute any action is to add the methods that shall be called when the menu items are activated or the toolbarbuttons pressed. For this, we have to add the ID´s to the slot 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;....  }}

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."));}

When looking at the code, we see that we use another new method of 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 ); }

Watch out for adding the declaration of the two new methods 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: By this structure, you are provided the general way how to extend your application with more functionality and manipulating settingsthat influence the behavoir of the document and view interaction.
Következő Előző Tartalom