Skip navigation.
KDE Developer's Journals

rich's blog

rich's picture

First Hack with ItemsViewsNG

People reading planet will have seen Thomas Zander's post about the new ItemViews framework the Trolls^H^H^HQt Software guys have been working on. It's very experimental right now, but I thought I'd have a quick look. I decided that a fun hack to write would be to take a standard listview that displays a list of URLs (boring!) and write a custom view that instead displays the rendered web page. I only spent a couple of hours on it, so the code is a hack (eg. you need to resize it to get the pages to display once they've loaded) but the results look ok. The whole code for this example comes in under 120 lines.


So, that's my hack in response to Thomas's challenge, who's next?

Here's an updated version that correctly updates the items when the page is loaded without requiring a resize.

And now version 3 that disables the scrollbars in the previews making things look a lot nicer.

rich's picture

Querying a Wiki Using Qt

I spent a little time yesterday working on a class that provides
a wrapper around the MediaWiki API. It makes quite a nice example
of how the QNetworkAccessManager API can be combined with
QXmlStreamReader to quickly access web services. The code, as you'll
see, is very simple.

First lets take a look at the constructor and the data members. We
specify the path to the api.php file of the wiki we want to query,
and setup the network access manager.

struct MediaWikiPrivate {
    QStringList results;
    QUrl apiUrl;
    QNetworkAccessManager *manager;
    int maxItems;
};

MediaWiki::MediaWiki( QObject *parent )
        : QObject( parent )
{
    d = new MediaWikiPrivate;
    d->apiUrl = QUrl("http://en.wikipedia.org/w/api.php");
    d->manager = new QNetworkAccessManager( this );
    d->maxItems = 10;
    connect( d->manager, SIGNAL(finished(QNetworkReply*)), SLOT(finished(QNetworkReply *)) );
}

People using the class can ask us to perform a search by calling
the search() method with the term they want to look for. Again,
the code is simple.

void MediaWiki::search( const QString &searchTerm )
{
    QUrl url = d->apiUrl;
    url.addQueryItem( QString("action"), QString("query") );
    url.addQueryItem( QString("format"), QString("xml") );
    url.addQueryItem( QString("list"), QString("search") );
    url.addQueryItem( QString("srsearch"), searchTerm );
    url.addQueryItem( QString("srlimit"), QString::number(d->maxItems) );

    qDebug() << "Constructed search URL" << url;

    d->manager->get( QNetworkRequest(url) );
}

We use the addQueryItem() method of QUrl to construct our search, by
specifying each of the options we need. Finally, we launch the request
by calling the get() method of QNetworkAccessManager.

Once the network request is completed, our finished() slot is called.
We check the status to see if the request succeeded then pass the
reply to the processSearchResult() method. Note that QNetworkReply
is a subclass of QIODevice which makes it very easy for us to work
with.

void MediaWiki::finished( QNetworkReply *reply )
{
    if ( reply->error() != QNetworkReply::NoError ) {
        qDebug() << "Request failed, " << reply->errorString();
        emit finished(false);
        return;
    }

    qDebug() << "Request succeeded";
    bool ok = processSearchResult( reply );
    emit finished( ok );
}

bool MediaWiki::processSearchResult( QIODevice *source )
{
    d->results.clear();

    QXmlStreamReader reader( source );
    while ( !reader.atEnd() ) {
        QXmlStreamReader::TokenType tokenType = reader.readNext();
        // qDebug() << "Token" << int(tokenType);
        if ( tokenType == QXmlStreamReader::StartElement ) {
            if ( reader.name() == QString("p") ) {
                QXmlStreamAttributes attrs = reader.attributes();
                //qDebug() << "Found page" << attrs.value( QString("title") );
                d->results << attrs.value( QString("title") ).toString();
            }
        }
        else if ( tokenType == QXmlStreamReader::Invalid )
            return false;
    }

    qDebug() << "Results" << d->results;
    return true;
}

To parse the XML returned by the wiki, we use QXmlStreamReader. We're only
interested in elements that look like this:

   <p ns="0" title="Qt (toolkit)" />

So we locate those when we see a StartElement token, then extract the title
attribute. Once this process is complete, we have a list of all the pages
matching the query and emit our finished() signal.

To test the code I've written a small wrapper that lets you enter a term,
then searches online when you press return. The code queries wikipedia right
now, but simply by changing the setApiUrl() call you can tell it to search
techbase. I've left that code in place but commented out so people can try it.

In future, this code could be used as a KRunner plugin, as part of an IDE,
or more generally anywhere else where someone wants to query a wiki in their
application. The full example can be downloaded from
http://xmelegance.org/devel/mediawiki/wikiviewer.tar.bz2. Enjoy.

rich's picture

Advertising

Just watched a very clever advert for the new iphone.

  • It's a manual, it shows you how to download (and buy) new apps.
  • It's very short, just one feature
  • User ends up having fun - playing game, and then call from a guy
  • Interupting you in the middle of the game is presented as a feature!
  • Slogan about this changing everything (not sure how it's supposed to do so)

The advert seems well put together, but I wonder if we couldn't create something similar regarding KDE features?

rich's picture

Akademy 2008

As you've seen from all the stores on the dot and on planetkde this week has seen all the KDE
developers gathering in Mechelen in Belgium. The organisation this year has
been excellent - even the network (usually the achilles heel of KDE
conferences) has worked from day one.

As well as the usual socialising, talks and BoF sessions there has also been a
lot of coding. I've focussed on ksnapshot and plasma, KSnapshot gained a few
minor changes:

  • KSnapshot will now record the window title in the image if you've chosen
    to capture the window decorations. Hopefully this should be useful to indexers
    like strigi.
  • I've fixed the 'not saving settings' bug - it looks like kdelibs changed
    behaviour.
  • I've increased the JPEG quality setting a little so screen shots look nicer.
  • Tackat has persuaded me to look into writing a simple post processing app
    so that you can easily apply effects like screenie before you upload your pics
    to the dot.

The work on plasma was again to do with scripting, I first of all got the code
that was broken by the API changes fixed - this meant that the spinning
squares demo now works again. I then got the scripting support for the new
widget api working. You can see this in action in the screenshot below which
shows a calculator written in javascript using the plama widgets next to the
current C++ calculator.

This isn't quite ready to commit because the actual calculator logic is a bit
broken, but it shows that the widget support is nearly ready. In order to make
it easy to test this, I finished the implementation of package uninstalling in
plasma libs so that you can now use the plasmapkg tool to easily install and
remove scripted plasmoids.

I've done a little work on a few other things, but they're not quite ready
yet, so I'll save them for another post.

rich's picture

New Plasma Widgets Design

This is the draft design for a new widget API for plasma, it will be appearing on techbase later, but here's what we're thinking.

General Notes

The intention of this API is to provide a very simple way for users to create
plasma applets. The API can be used both for scripting and from C++. For the
simple javascript API, this API will be all that is provided allowing us to
run untrusted applets as they will not have access to any dangerous
facilities.

From C++ or more advanced scripting APIs (such as the 'full' Javascript
bindings) you can gain access to a pointer to the underlying Qt widget
contained within the QGraphicsProxy allowing you to access all of its
methods. For most simple uses however, this should be unnecessary as you can
do most of the customisation using the Qt stylesheet facilities.

All widgets have a stylesheet, which is a string property containing a Qt
stylesheet. This allows for simple but powerful configuration of the widgets -
for example you can configure the alignment of the text in the label using the
text-align property, or the font using the font property. Using this you can
do some extremely advanced interfaces without having to learn how Qt works.

A nice side effect of the way this API is defined is that it is possible to
implement the simple widget API inside a web browser using HTML, javascript
and HTML stylesheets to provide the implementation.
API Reference

Label

Constructors

  • Label(Widget)

Properties

  • Widget parent
  • String text
  • String image
  • String stylesheet
  • QWidget nativeWidget

Note that the image property is a string and specifies the name of the
image within the plasmoid package. The image can be either a bitmap (in any
supported format) or an SVG image.

PushButton

Constructors

  • PushButton(Widget)

Properties

  • Widget parent
  • String text
  • String image
  • String stylesheet
  • QWidget nativeWidget

Signals

  • clicked()

CheckBox

  • CheckBox(Widget)

Properties

  • Widget parent
  • String text
  • String image
  • String stylesheet
  • bool checked
  • QWidget nativeWidget

Signals

  • toggled(bool)

RadioButton

  • RadioButton(Widget)

Properties

  • Widget parent
  • String text
  • String image
  • String stylesheet
  • bool checked
  • QWidget nativeWidget

Signals

  • toggled(bool)

Note that a RadioButton must exist within a ButtonGroup, the group box ensures
that only one radio button is set at any one time.

Widgets To Do

  • WebContent
  • GroupBox
  • ButtonGroup
  • ComboBox
  • LineEdit
  • TextEdit
  • Meter
  • Graph
  • Throbber
  • Simplified versions of the QGraphicsLayout classes

Non-Widgets To Do

  • URLOpenner
rich's picture

Plasma Sprint Day One

After a day's work at the Plasma sprint, there's already quite a lot of news
to report. After a lot of trawling through log files, I was able to fix the
problem that was preventing the Plasma binding plugin from loading. In the end
it was something simple (as usual) namely that the method that allows the
plugin to load was not being compiled into the module since it was missing
from the generated .pri file. Once this was fixed, it was simply a matter of
changing the name of the extension we load from 'qt.plasma' to
'org.kde.plasma' to match a fix in the generator and we had a successfully
loading set of bindings.

Lots of thanks are due to Kent Hansen of Troll Tech
for this - not only did he write the binding generator and the type system for
the Qt bindings, he also fixed up the one for plasma. Oh, and if that wasn't
enough he's even figured out the bug that has been preventing the generated
code from having anti-aliasing enabled.

In order to test that these bindings worked properly, I wanted to create a
small demo that exercised the script engine code, the Qt bindings and the
plasma bindings in the same plasmoid. My initial attempt worked ok, but didn't
look very good (though it had the advantage of requiring only a tiny amount of
code). Instead, I'll show a slightly more complex example that looks prettier.

Obviously, you can't see the animation in the screenshot, but this shows four
squares spinning around a common axis at different speeds and changing
colour. Ok, it's not the best piece of graphic design you've ever seen but it
does illustrate that the crucial facilities are in place.

In addition to the coding, we also spent some time today going over the
results Seele obtained from her interviews with some of the developers. We
were looking at the user populations we are currently supporting well, but
more importantly at those that we could potentially encourage to adopt KDE and
Plasma to consider how we can improve the facilities we offer for them
too. Before anyone gets hot under the collar, this doesn't mean ripping out
functionality to 'dumb down' the interface, in fact a lot of the suggestions
were more about how we can improve the interface consistency.

Following this discussion we did some more coding and also broke down into
smaller groups so we could continue to plan for our inevitable world
domination.

rich's picture

QtScript Web Browser

As some of you may have seen, Kent has released the QtScript binding generator on Troll Tech labs. I've been playing with the code for a bit, and as with KJSEmbed one of the first tests was to make sure you could use it to write a simple web browser. Kent recently added support for QUiLoader to the bindings and as a result, I can use the QWebView designer plugin to make things simple. The result is a basic web browser in less than 10 lines of javascript:

var loader = new QUiLoader(null);
var file = new QFile("browser.ui");
file.open(QIODevice.OpenMode(QIODevice.ReadOnly, QIODevice.Text));

var web = loader.load(file,null);
web.show();

QCoreApplication.exec();

There's also a very simple designer ui file that just contains the web view and a layout. Here's the obligatory screenshot:

rich's picture

Qtscript Binding Generator

In 4.0 plasma had support for writing applets using Qt's built in javascript interpreter QtScript, but the facilities have been fairly limited. In KDE 3.x KJSEmbed gave us reasonably complete bindings to the Qt and KDE api's, allowing us to write applications such as a web browser in 10 lines of javascript. I'm glad to say that some plans that were discussed at the KDE conference in Glasgow are now a reality and Kent has released a qtscript binding generator based on the one used for QtJambi. The result is that we will very soon have good access to the Qt API from qtscript.

I've been playing with the output of the generator for a couple of weeks now and while it still has a lot of rough edges, it's definitely a solid foundation. Even better from my point of view as a KDE developer, the Jambi generator was designed from the start to allow you to use it to write bindings for Qt based code that is not part of the main Qt API. Using it to build bindings to the plasma APIs and in future the kdelibs API is definitely feasible.

rich's picture

XML Doesn't Beep

I learnt a something new about XML today, a part of the specification that deals one of the many edge cases that exist in every file format. To illustate this, lets take a look at a few examples. Why is this XML document well-formed :

<test>X</test>

this one also well-formed:

<test>& #9;</test>

But this document isn't:

<test>& #7;</test>

Note that I've added an extra space to these examples as the blogging software used by kdedevelopers.org seems to quote the characters required to make this appear directly.

To find out why it's broken, read on...

rich's picture

Programming Styles - Why Encapsulation is a Good Thing

I was reading a blog post on beautiful code about different styles of programming earlier this week. The author was comparing the 'ruby style' of direct access to member variables with the getter/setter pattern common in Java code. His basic question was is this simply a matter of your programming background?

Here's the Java version of his example:

public class GlazeObject implements Renderable {
    private ClassStore store = new DefaultClassStore();
    private Formatter formatter = new DefaultFormatter();
    
    public void setStore(ClassStore store) {
        this.store = store;
    }
     
    public void setFormatter(Formatter formatter) {
        this.formatter = formatter;
    }
	
    public void render()
        …
    }
}

As you can see the Java version doesn't permit direct access to the members and instead uses accessor functions. For me, this is a good solution because I expect my code to evolve over time. Lets look at a couple of possible future versions of the same code:

public class GlazeObject implements Renderable {
    private ClassStore store = new DefaultClassStore();
    private Formatter formatter = new DefaultFormatter();
    private int expensive = 0x1234;

    public void setStore(ClassStore store) {
        this.store = store;
    }
     
    public void setFormatter(Formatter formatter) {
        this.formatter = formatter;
        this.expensive = doCalculation(formatter);
    }
	
    public void render()
       // Use precalculatede expensive value in fast path
        …
    }
}

In this future, we've discovered through profiling that one of the calculations we need in our render function is a performance bottle neck. To avoid the issue, we precalculate the value and simply use the cached version. Having accessor functions makes this trivial - we don't need to track down every use of the member variables and fix them as we have acheived proper encapsulation.

Of course, the future could be different. How about if we discover that the particular instances of the above object are often obscured so we don't need to render them. Well, now we might code something like this instead:

public class GlazeObject implements Renderable {
    private ClassStore store = new DefaultClassStore();
    private Formatter formatter = new DefaultFormatter();
    private bool dirty = true;
    private int expensive = 0x0000;

    public void setStore(ClassStore store) {
        this.store = store;
    }
     
    public void setFormatter(Formatter formatter) {
        this.formatter = formatter;
        this.dirty = true;
    }
	
    public void render()
       // Calculate expensive on first use then reuse it
        …
    }
}

Again, we're saved by our encapsulation. It's easy to make when the cached value needs updating because all accesses are via a single method. The downside here is that the first call to render() after the formatter is updated is slower than subsequent calls, but that is generally a good trade off.

Now all of the above examples are in Java, where we have a much more modern object format than is available to us in C++. In C++ we have to consider the dreaded binary compatibility. A C++ version might look something like this:

class GlazeObject : public Renderable
{
public:
  void setStore( ClassStore *store );
  void setFormatter( Formatter *formatter );
  void render();

private:
  class GlazeObjectPrivate *d;
};

class GlazeObjectPrivate
{
public:
  ClassStore *store;
  Formatter *formatter;
};

Here we're using the PIMPL pattern (a d pointer) to conceal the internal structure of the object from callers. This means we can change the implementation without needing to recompile things that are calling our object. Again as you see encapsulation gives us a big win. Programming is always a matter of trade offs, but frequently the good choice for the long term is to write a little bit more code and separate concerns.

Syndicate content