Our man in Toulon
A couple of days ago Marcelo Fernández wrote a simple image viewer in
PyGTK
. It's
less than 200 lines long1, 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:
#! /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 aQPixmap
/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 azoom()
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 ofPython
(I was getting zoom factors of 0) so I had to add1.0*
to the claculations. It's good that this is fixed inPython2.6/3.0
. - The size reported by the
QMainWindow
was any vegetable (it said 640x480 when it actually was 960x600), so I used theQGraphicsView
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
'sformat
plugin are at most sucky, but better than nothing.
-
Unluckly he didn't declared which license it has, so I'm not sure if I really can do this. I GPL'ed mine. ↩