A couple of days ago Marcelo Fernández wrote a simple image viewer in
PyGTK. 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:
#! /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
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 aQPixmap/QGraphicsPixmapItempair. - 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
QMainWindowwas any vegetable (it said 640x480 when it actually was 960x600), so I used theQGraphicsViewinstead. 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'sformatplugin 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.