KDE Tutorial - p9

p9

Next Previous Table of Contents

Now, we are going to simplify p7 while maximizing user customizability by using the new XMLGUI framework to build the user interface.


#include <kapp.h>
#include <kcmdlineargs.h>
#include <klocale.h>
#include <kaboutdata.h>
#include "p9.h"

int main( int argc, char **argv )
{
  KAboutData *aboutdata = new KAboutData("p9", I18N_NOOP("KDE Tutorial - p9"),
      "1.0", I18N_NOOP("Step 9 of a simple tutorial"), KAboutData::License_GPL,
      "(C) 2000, 2001 Antonio Larrosa Jimenez","",
      "http://perso.wanadoo.es/antlarr/tutorial.html");
  aboutdata->addAuthor("Antonio Larrosa Jimenez",
      I18N_NOOP("Original Developer/Mantainer"),"larrosa@kde.org",
      "http://perso.wanadoo.es/antlarr/index.html");

  KCmdLineArgs::init(argc, argv, aboutdata);

  KApplication a;

  MainWindow *mywindow=new MainWindow( "Tutorial - p9" );
  mywindow->resize( 300, 200 );

  a.setMainWidget( mywindow );
  mywindow->show();

  return a.exec();

}

main.cpp


#include <dcopobject.h>
 
class p9Iface : virtual public DCOPObject
{
   K_DCOP
 
k_dcop:
   virtual void setURL( const QString s )=0;
 
}; 

p9Iface.h


#include "p9Iface.h"
#include <kmainwindow.h>
#include <kurl.h>
#include <kparts/browserextension.h>
#include <qvaluestack.h>
 
class QLineEdit;
class KHTMLPart;

class MainWindow : public KMainWindow, virtual public p9Iface
{
  Q_OBJECT
public:
    MainWindow ( const char * titulo );

    virtual void setURL ( const QString url );

public slots:
    void fileSetDefaultPage();
    void changeLocation();
    void bookLocation();
    void gotoPreviousPage();
    void openURLRequest(const KURL &url, const KParts::URLArgs & );

private:
    QLineEdit *location;
    KHTMLPart *browser;
    QValueStack <QString> history;
};

p9.h


#include "p9.h"
#include <qvbox.h>
#include <qlineedit.h>
#include <dcopclient.h>
#include <kfiledialog.h>
#include <kapp.h>
#include <kaction.h>
#include <klocale.h>
#include <khtml_part.h>
#include <kdebug.h>
#include <kconfig.h>
#include <kstdaction.h>

MainWindow::MainWindow ( const char * name ) : KMainWindow ( 0L, name ),
DCOPObject ( "browser" )
{

  KStdAction::quit(this, SLOT(close()), actionCollection());

  (void)new KAction(i18n("&Set default page"), "gohome", 0, this,
		    SLOT(fileSetDefaultPage()), actionCollection(), "set_default_page");

  (void)new KAction(i18n("Add to Bookmarks"), "reload", 0, this,
		    SLOT(bookLocation()), actionCollection(), "add_to_bookmarks");

  (void)new KAction(i18n("Back to previous page"), "back", 0, this,
		    SLOT(gotoPreviousPage()), actionCollection(), "back");

  actionCollection()->action("back")->setEnabled(false);

  createGUI("p9ui.rc");

  QVBox * vbox = new QVBox ( this );

  location = new QLineEdit ( vbox );

  KConfig *config=kapp->config();
  config->setGroup("Settings");
  location->setText( config->readEntry( "defaultPage", "http://localhost") );

  connect( location , SIGNAL( returnPressed() ),
      this, SLOT( changeLocation() ) );

  browser=new KHTMLPart( vbox );
  browser->openURL( location->text() );

  connect( browser->browserExtension(),
      SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
      this, SLOT( openURLRequest(const KURL &, const KParts::URLArgs & ) ) );

  setCentralWidget( vbox );

  DCOPClient *client = kapp->dcopClient();
  client->attach();
  client->registerAs("p7");
}


void MainWindow::changeLocation()
{
  history.push( browser->url().url() );
  actionCollection()->action("back")->setEnabled(true);
  browser->openURL( location->text() );
}

void MainWindow::setURL( const QString url )
{
  location->setText( url );
  changeLocation();
}

void MainWindow::openURLRequest( const KURL &url, const KParts::URLArgs & )
{
  setURL( url.url() );
}

void MainWindow::gotoPreviousPage()
{
  location->setText( history.pop() );
  if (history.isEmpty()) actionCollection()->action("back")->setEnabled(false);
  browser->openURL( location->text() );
}

void MainWindow::bookLocation()
{
  DCOPClient *client=kapp->dcopClient();
  QByteArray params;
  QDataStream stream(params, IO_WriteOnly);
  stream << location->text();
  if (!client->send("p8-*", "bookmarkList", "add(QString)", params))
    kdDebug << "Error with DCOP";
}

void MainWindow::fileSetDefaultPage()
{
  KConfig *config=kapp->config();

  config->setGroup("Settings");
  config->writeEntry( "defaultPage", browser->url().url() );
}     

p9.cpp

  KAboutData *aboutdata = new KAboutData("p9", I18N_NOOP("KDE Tutorial - p9"),
    "1.0", I18N_NOOP("Step 9 of a simple tutorial"), KAboutData::License_GPL,
    "(C) 2000, Antonio Larrosa Jimenez","",
    "http://perso.wanadoo.es/antlarr/tutorial.html");

When you thought that we wouldn't change main.cpp anymore, here comes a change to generate the about dialog automatically.

The KAboutData class is used to store the data for the about dialog (wasn't it obvious? :-) ). First we pass the application internal name, then the "real" (displayable) name, the version, a short description, the license, the copyright notice, a free text (empty in this case), and the homepage for the application.

We note that instead of the usual i18n function, we're now using a I18N_NOOP macro, this is done this way because we cannot use i18n before creating a KApplication object, so we use I18N_NOOP, which "marks" the text for a later translation.

  aboutdata->addAuthor("Antonio Larrosa Jimenez",
    I18N_NOOP("Original Developer/Mantainer"),"larrosa@kde.org",
    "http://perso.wanadoo.es/antlarr/personal.html");
Now, we add information about one author (we can add as many authors information as needed). First, the name (no, don't mark it for translation ;-) ), then the task inside the application, the email address, and his/her personal home page.

  KCmdLineArgs::init(argc, argv, aboutdata);

  KApplication a;   

Now we initialize the command line arguments by passing them and the aboutdata object to the KCmdLineArgs::init static method.

Note that now we just can use the default parameters for the KApplication constructor, as it will take all the information from the aboutdata object.

Ok, now we can have a look at p9.cpp :

#define TOOLBAR_ID_ADDBOOKMARK 1
#define TOOLBAR_ID_BACK 2
#define TOOLBAR_ID_QUIT 3

We first note that these lines no longer exist. We are going to use another approach which is much more intuitive and so allows us to simplify the code.

We are going to use KAction objects. This is a radical new and powerful feature that let us simplify the creation and maintenance of user interfaces.

We will create a KAction object for each action the user can do. There are two types of actions, the standard ones (like opening a file, saving, undo, quit, etc.) and the custom ones (the ones specific for each application) .

  KStdAction::quit(this, SLOT(close()), actionCollection());

First, we create an standard "quit" action, that is connected to this object and the close() slot . The actionCollection() parameter is just the object that collects all the actions in the application.

  (void)new KAction(i18n("&Set default page"), "gohome", 0, this,
       SLOT(fileSetDefaultPage()), actionCollection(), "set_default_page")

Now we create a new action. The first parameter is the text which will be shown when this action is in a menu bar, the second parameter is the name of the icon that will be shown when this action is in a tool bar or menu bar, note that we just specify the name of the icon instead of the icon itself. This allows the libraries to do some nice things with it, like applying effects and choosing the right icon sizes for menubars and toolbars. the third parameter is the key accelerator that the user can press to activate the action (in this case, we don't have an accelerator). Then, we set the object and slot that will be called when this action is activated, and the actionCollection() object.

Finally, we set the name for the action (the name must identify the action, that is, must be unique).

Note that we don't have to store the action in any variable, as it will be managed by the object returned by actionCollection()

  (void)new KAction(i18n("Add to Bookmarks"), "reload", 0, this,
		    SLOT(bookLocation()), actionCollection(), "add_to_bookmarks");

  (void)new KAction(i18n("Back to previous page"), "back", 0, this,
		    SLOT(gotoPreviousPage()), actionCollection(), "back");

We create a couple of actions more, to add the current page to the bookmark, and to go back to the previous page.

  actionCollection()->action("back")->setEnabled(false);

With this call, we disable the "back" button. First we get the action collection, and ask for the action with name "back", then we call setEnabled(false) in this action to disable it.

The great benefit of this is that we don't have to manage menu items and toolbars separately, we just disable the action and the corresponding menu items and toolbar buttons get disabled automatically. No more millions definitions for each menu item ID and each toolbar ID, isn't it great ? ;-)

  createGUI("p9ui.rc");

By calling createGUI, we really plug the actions to the menu and tool bars.

We well now check the contents of p9ui.rc :

<!DOCTYPE kpartgui>
<kpartgui name="p9">
<MenuBar>
  <Menu name="file"><text>&File</text>
    <Action name="set_default_page"/>
  </Menu>
</MenuBar>
<ToolBar fullWidth="true" name="mainToolBar">
  <Action name="add_to_bookmarks"/>
  <Action name="back"/>
  <Separator/>
  <Action name="file_quit"/>
</ToolBar>
</kpartgui>   

This is an XML file which contains the GUI definition for p9.

<kpartgui name="p9">

This specifies that p9 is the application which we are creating the GUI for.

<MenuBar>
  <Menu name="file"><text>&File</text>
    <Action name="set_default_page"/>
  </Menu>
</MenuBar>

First we define the menu bar in the MenuBar section. For each menu, we have one Menu section with its name (in this case, we just have a file menu, as the help menu is added automatically), and in each Menu section, we put the actions we want to appear in that menu.

<ToolBar fullWidth="true" name="mainToolBar">
  <Action name="add_to_bookmarks"/>
  <Action name="back"/>
  <Separator/>
  <Action name="file_quit"/>
</ToolBar>

Now, we define the toolbar (in fact, the "mainToolBar" as we may have more than one toolbar. We just write the actions we want to appear in the toolBar with some (optional) separators.

Btw, the same sintax for separator may be used to insert separators in the menus.

Coming back to p9.cpp, we note that we're registrating it as "p7" to the dcopserver. We are doing this just so that we can use p8 with p9 as we did with p7.

As an interesting note, we will compare the number of lines in p7.cpp and p9.cpp :

  wc p7/p7.cpp p9/p9.cpp
     124     324    3454 p7/p7.cpp
     102     238    2687 p9/p9.cpp   

In p9.cpp we have 22 lines less than in p7.cpp !!!

While removing this 17.7 percent of the source code, we have added the possibility for the user to customize the application interface (he just have to modify the XML file and re-run the application, no need to recompile it !)

If you want to know more about the KXMLGUI framework, don't forget to read the excellent tutorial by Kurt Granroth at http://developer.kde.org/documentation/tutorials/xmlui/preface.html .

This is the final step of this tutorial. I hope you learned many things with it and decided to start your KDE application. If that's the case, have a look at the sugerences in the Introduction for the instructions on how to get your future programming doubts solved and for some ideas on how to start your application.

Thanks for reading this tutorial,

Antonio Larrosa

Next Previous Table of Contents


© 1999,2000,2001 Antonio Larrosa