Ruby Logo Home

p9

Next Previous Table of Contents

Screenshot of tutorial nine

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


require 'Korundum'
    
class Browser < KDE::MainWindow
    k_dcop  'void setURL(QString)'
	
    slots   'fileSetDefaultPage()', 
            'changeLocation()',
            'bookLocation()',
            'gotoPreviousPage()',
            'openURLRequest(const KURL&, const KParts::URLArgs&)'
 
    def initialize( name )
        super(nil, name)
        @history = []

        KDE::StdAction.quit(self, SLOT('close()'), actionCollection());

        KDE::Action.new(i18n("&Set default page"), "gohome", KDE::Shortcut.new(0), self,
		    SLOT('fileSetDefaultPage()'), actionCollection(), "set_default_page")

        KDE::Action.new(i18n("Add to Bookmarks"), "reload", KDE::Shortcut.new(0), self,
		    SLOT('bookLocation()'), actionCollection(), "add_to_bookmarks")

        KDE::Action.new(i18n("Back to previous page"), "back", KDE::Shortcut.new(0), self,
		    SLOT('gotoPreviousPage()'), actionCollection(), "back")

        actionCollection().action("back").setEnabled(false)

        createGUI(Dir.getwd + "/p9ui.rc")

        vbox = Qt::VBox.new( self )
 
        @location = Qt::LineEdit.new( vbox )

        config = $kapp.config()
        config.setGroup("Settings")
        @location.text = config.readEntry( "defaultPage", "http://localhost")

        connect( @location , SIGNAL( 'returnPressed()' ),
                    self, SLOT( 'changeLocation()' ) )

        @browser = KDE::HTMLPart.new( vbox )
        @browser.openURL( KDE::URL.new(@location.text()) )

        connect( @browser.browserExtension(),
	            SIGNAL( 'openURLRequest( const KURL&, const KParts::URLArgs& )' ),
	            self, SLOT( 'openURLRequest(const KURL&, const KParts::URLArgs& )' ) )           	     
        setCentralWidget(vbox)
    end

    def changeLocation()
        @history.push( @browser.url().url() )
        actionCollection().action("back").setEnabled(true)
        @browser.openURL( KDE::URL.new(@location.text()) )
    end

    def setURL( url )
        @location.text = url
        changeLocation()
    end

    def openURLRequest(url, part)
        setURL( url.url() )
    end

    def gotoPreviousPage()
        @location.text = @history.pop() 
        if @history.empty?
            actionCollection().action("back").setEnabled(false)
        end
        @browser.openURL( KDE::URL.new(@location.text()) )
    end

    def bookLocation()
        dcopRef = KDE::DCOPRef.new("p8", "BookMarkList")
        if ! dcopRef.add(@location.text())
            Qt.qWarning("Error with DCOP\n")
        end
    end

    def fileSetDefaultPage()
        config = $kapp.config()
 
        config.group = "Settings"
        config.writeEntry( "defaultPage", @browser.url().url() )
    end
end

    aboutdata = KDE::AboutData.new("p9", "Tutorial - p9",
      "1.0", "Step 9 of a simple tutorial", KDE::AboutData::License_GPL,
      "(C) 2000, 2001 Antonio Larrosa Jimenez","",
      "http://devel-home.kde.org/~larrosa/tutorial.html")
    aboutdata.addAuthor("Antonio Larrosa Jimenez",
      "Original Developer/Maintainer","larrosa@kde.org",
      "http://devel-home.kde.org/~larrosa/index.html")
    aboutdata.addAuthor("Richard Dale",
      "Ruby port","Richard_Dale@tipitina.demon.co.uk",
      "")
    
    KDE::CmdLineArgs.init(ARGV, aboutdata)
	
    a = KDE::UniqueApplication.new()
	
    window = Browser.new( "Tutorial - p9" )
    window.resize( 300, 200 )
	
    a.mainWidget = window
    window.show
	
    a.exec 


p9.rb

    aboutdata = KDE::AboutData.new("p9", "Tutorial - p9",
      "1.0", "Step 9 of a simple tutorial", KDE::AboutData::License_GPL,
      "(C) 2000, 2001 Antonio Larrosa Jimenez","",
      "http://devel-home.kde.org/~larrosa/tutorial.html")

Just when you thought that we wouldn't change the top level anymore, here comes a change to generate the about dialog automatically.

The KDE::AboutData 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.

    aboutdata.addAuthor("Antonio Larrosa Jimenez",
      "Original Developer/Mantainer"),"larrosa@kde.org",
      "http://devel-home.kde.org/~larrosa/index.html")
    aboutdata.addAuthor("Richard Dale",
      "Ruby port","Richard_Dale@tipitina.demon.co.uk")
Now, we add information about two authors (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.

    KDE::CmdLineArgs.init(ARGV, aboutdata)
	
    a = KDE::UniqueApplication.new()

Now we initialize the command line arguments by passing them and the aboutdata object to the KDE::CmdLineArgs.init class method.

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

Ok, now we can have a look at the main class in p9.rb:

    TOOLBAR_ID_ADDBOOKMARK = 1
    TOOLBAR_ID_BACK = 2
    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 KDE::Action objects. This is a radical new and powerful feature that lets us simplify the creation and maintenance of user interfaces.

We will create a KDE::Action 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) .

        KDE::StdAction.quit(self, SLOT('close()'), actionCollection());

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

        KDE::Action.new(i18n("&Set default page"), "gohome", KDE::Shortcut.new(0), self,
		    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, 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()

        KDE::Action.new(i18n("Add to Bookmarks"), "reload", KDE::Shortcut.new(0), self,
		    SLOT('bookLocation()'), actionCollection(), "add_to_bookmarks")

        KDE::Action.new(i18n("Back to previous page"), "back", KDE::Shortcut.new(0), self,
		    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, ask for the action with name "back", and 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(Dir.getwd + "/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 SYSTEM "kpartgui.dtd">
<kpartgui name="p9" version="1">
<MenuBar>
  <Menu name="file"><text>&amp;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" version="1">

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

<MenuBar>
  <Menu name="file"><text>&amp;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 syntax for separator may be used to insert separators into the menus.

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

wc p7.rb p9.rb
    127     312    4368 p7.rb
    108     236    3416 p9.rb

In p9.rb we have 21 lines less than in p7.rb !!!

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

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 own KDE application. If that's the case, have a look at the resources in the Introduction for some pointers 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-2002 Antonio Larrosa © 2004 Richard Dale