A couple of days ago Marcelo Fernández wrote a simple image viewer in
less than 200 lines long, 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
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 -*- # - Example image viewer in # Marcos Dione <firstname.lastname@example.org> - http://grulicueva.homelinux.net/~mdione/glob/ # TODO: # * add licence! (GPLv2 or later) from .QtGui import QApplication, QMainWindow, QGraphicsView, QGraphicsScene from .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) 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
Pythoncode, 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
- 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
- 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
- The size reported by the
QMainWindowwas any vegetable (it said 640x480 when it actually was 960x600), so I used the
- 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
formatplugin 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.