A couple of days ago [Marcelo Fernández wrote a simple image viewer in
`PyGTK`](http://feedproxy.google.com/~r/ElBlogDeMarcelo/~3/kukV8oltwWI/). It's
less than 200 lines long[^1], and I thought that it would be nice to compare how
the same app would be written in `PyKDE4`. But then I though that it would not
be fair, as `KDE` is a whole desktop environment and `GTK` is 'only' a widget
library, so I did it in `PyQt4` instead.

To make this even more fair, I hadn't had a good look at the code itself, I only
run it to see what it looks like: a window with only the shown image in it, both
scrollbars, no menu or statusbar, and no external file, so I assume he builds
the ui 'by hand'. He mentions these features:

   * Pan the image with the mouse.
   * F1 to F5 handle the zoom from 'fit to window', 25%, 50%, 75% and 100%.
   * Zooming with the mouse wheel doesn't work.

Here's my take:

```python
#! /usr/bin/python
# -*- coding: utf-8 -*-

# OurManInToulon - Example image viewer in PyQt4
# Marcos Dione <mdione@grulic.org.ar> - http://grulicueva.homelinux.net/~mdione/glob/

# TODO:
#     * add licence! (GPLv2 or later)

from PyQt4.QtGui import QApplication, QMainWindow, QGraphicsView, QGraphicsScene
from PyQt4.QtGui import QPixmap, QGraphicsPixmapItem, QAction, QKeySequence
import sys

class OMITGraphicsView (QGraphicsView):
    def __init__ (self, pixmap, scene, parent, *args):
        QGraphicsView.__init__ (self, scene)
        self.zoomLevel= 1.0
        self.win= parent
        self.img= pixmap
        self.setupActions ()

    def setupActions (self):
        # a factory to the right!
        zoomfit= QAction (self)
        zoomfit.setShortcuts ([QKeySequence.fromString ('F1')])
        zoomfit.triggered.connect (self.zoomfit)
        self.addAction (zoomfit)

        zoom25= QAction (self)
        zoom25.setShortcuts ([QKeySequence.fromString ('F2')])
        zoom25.triggered.connect (self.zoom25)
        self.addAction (zoom25)

        zoom50= QAction (self)
        zoom50.setShortcuts ([QKeySequence.fromString ('F3')])
        zoom50.triggered.connect (self.zoom50)
        self.addAction (zoom50)

        zoom75= QAction (self)
        zoom75.setShortcuts ([QKeySequence.fromString ('F4')])
        zoom75.triggered.connect (self.zoom75)
        self.addAction (zoom75)

        zoom100= QAction (self)
        zoom100.setShortcuts ([QKeySequence.fromString ('F5')])
        zoom100.triggered.connect (self.zoom100)
        self.addAction (zoom100)

    def zoomfit (self, *ignore):
        winSize= self.size ()
        imgSize= self.img.size ()
        print winSize, imgSize
        hZoom= 1.0*winSize.width  ()/imgSize.width  ()
        vZoom= 1.0*winSize.height ()/imgSize.height ()
        zoomLevel= min (hZoom, vZoom)
        print zoomLevel
        self.zoomTo (zoomLevel)

    def zoom25 (self, *ignore):
        self.zoomTo (0.25)

    def zoom50 (self, *ignore):
        self.zoomTo (0.5)

    def zoom75 (self, *ignore):
        self.zoomTo (0.75)

    def zoom100 (self, *ignore):
        self.zoomTo (1.0)

    def zoomTo (self, zoomLevel):
        scale= zoomLevel/self.zoomLevel
        print "scaling", scale
        self.scale (scale, scale)
        self.zoomLevel= zoomLevel

if __name__=='__main__':
    # this code is enough for loading an image and show it!
    app= QApplication (sys.argv)
    win= QMainWindow ()

    pixmap= QPixmap (sys.argv[1])
    qgpi= QGraphicsPixmapItem (pixmap)
    scene= QGraphicsScene ()
    scene.addItem (qgpi)

    view= OMITGraphicsView (pixmap, scene, win)
    view.setDragMode (QGraphicsView.ScrollHandDrag)
    view.show()

    app.exec_ ()
    # up to here!

# end
```

Things to note:

   * The code for loading, showing the image and pan support is only 13 lines of
`Python` code, including 3 imports. The resulting app is also able to handle
vector graphics, but of course I didn't exploit that, I just added a
`QPixmap`/`QGraphicsPixmapItem` pair.
   * Zooming is implemented via `QGraphicsView.scale()`, which is accumulative
(scaling twice to 0.5 actually scales to 0.25 of the original size), so I have
to keep the zoom level all the time. There should be a `zoom()` interface!
   * The code for calculating the scale level is not very good: scaling between
75% and 50% or 25% produces scales of 0.666 and 0.333, which I think at the end
of the day will accumulate a lot of error.
   * For the same reason, `zoomToFit()` has to do some magic. I also got hit by
the integer division of `Python` (I was getting zoom factors of 0) so I had to
add `1.0*` to the claculations. It's good that this is fixed in `Python2.6/3.0`.
   * The size reported by the `QMainWindow` was any vegetable (it said 640x480
when it actually was 960x600), so I used the `QGraphicsView` instead. WTF?
   * For some strange reason `zoomToFit()` scales the image a little bigger than
it should, so a scrollbar appears in the direction of the constraining dimension.
   * Less that 100 lines! Even if `setupActions()` could surely be improved.
   * In Marcelo's favor I should mention that he writes docstrings for most of his
methods both in english and spanish (yes, of course I read his code after I
finished mine). I barely put a couple of comments, but doing the same should add
10 more lines, tops. Also, I don't want to convert this into a who-has-it-smaller
contest (the code, I mean :).
   * It took me approx 3 hours, with no previous knowledge of how to do it and
no internet connection, so no asking around. I just used the «Qt Reference
Documentation», going to the «Gropued Classes» page and to the «Graphics View
Classes» from there.
   * It doesn't zoom with the mouse wheel either.
   * The default colors of `ikiwiki`'s `format` plugin are at most sucky, but
better than nothing.

[^1]: Unluckly he didn't declared which license it has, so I'm not sure if I
      really can do this. I GPL'ed mine.

