commented PyQt code for main windows

link to book featured on this page
. I’ve been learning pyqt coding from
Rapid GUI Programming with Python and Qt:
The Definitive Guide to PyQt Programming

by Mark Summerfield
ISBN: 0132354187
. the book includes openware in several versions;
I’m using the examples for Python 2.6 .
. I’ve been adding comments to his source;
the example I commented below is from the file
chapter 6 (main windows)/imageChanger.pyw
(for a working program you’ll need the other files)

#!/usr/bin/env python
""" 2012.04: Ph.T: added comments
(my understanding of his book).
"""
# Copyright (c) 2008 Qtrac Ltd. All rights reserved.
# This program or module is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, either version 2 of the License, or
# version 3 of the License, or (at your option) any later version. It is
# provided for educational purposes and is distributed in the hope that
# it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
# the GNU General Public License for more details.

# import python's standard modules:
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from future_builtins import *
import os
import platform
import sys
# import 3rd-party modules:
from PyQt4.QtCore import (PYQT_VERSION_STR, QFile, QFileInfo, QSettings,
        QString, QT_VERSION_STR, QTimer, QVariant, Qt, SIGNAL)
from PyQt4.QtGui import (QAction, QActionGroup, QApplication,
        QDockWidget, QFileDialog, QFrame, QIcon, QImage, QImageReader,
        QImageWriter, QInputDialog, QKeySequence, QLabel, QListWidget,
        QMainWindow, QMessageBox, QPainter, QPixmap, QPrintDialog,
        QPrinter, QSpinBox)
# import our own local modules:
import helpform
import newimagedlg #this uses ui_newimagedlg.py
import qrc_resources
"""
. if you search for the above qrc_resources import,
you'll find no obvious references to it;
but instead you'll find things like this:
QIcon(":/editmirror.png")
-- the ":/" tells the system to look in the resource file;
and, that's where it will find editmirror.png .
. after it does the imports,
it will know qrc_resources is the resource file .
. use of a resource file imbedded with the app
is the only way express locations in a way that
works on all platforms .
. making the resource file is a process:
first we make this resources.qrc file:
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="filenew.png">images/filenew.png</file>
...
<file>help/index.html</file>
</qresource>
</RCC>
. then in the top of the examples folder,
you'll find makepyqt.pyw .
. if you run that, then it makes the resource file,
qrc_resources.py, which contains this:
from PyQt4 import QtCore
qt_resource_data = "\x00\x00\x08\x15\..."
qt_resource_struct = "\x00\x00\x00\x00\x00..."
def qInitResources():
    QtCore.qRegisterResourceData(0x01, qt_resource_struct,
		qt_resource_name, qt_resource_data)
def qCleanupResources():
    QtCore.qUnregisterResourceData(0x01,
		qt_resource_struct, qt_resource_name, qt_resource_data)
qInitResources()
#. end of file .
. when you import that, it's calling qInitResources,
which in turn calls QtCore.qRegisterResourceData .
. this is convenient for many small resources,
but for large help files, it is quadrupling the size
by translating every byte into a 4byte-hex string .

"""
# applications should support a version string:
__version__ = "1.0.0"

class MainWindow(QMainWindow):
	""". this illustrates creation of an app,
	menu creation with recently open file list,
	user file handling, and user pref' reuse .
	. in terms of the mvc pattern,
	this uses a QLabel for the view,
	and a QImage for the model .
	. notice what the log was logging:
	the commands the user use during the edit session .
	. parts of an app window include title, menubar,
	toolbar areas, dock window* areas,
	CentralWidget and status bar .
	*: dock windows are not confined to the main window's frame;
	but docking converts them into panes of the main window .
	"""
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("Image Changer")
        self.image = QImage() #. our app's document, an image .
        self.dirty = False #. app has unsaved changes?
        self.filename = None #. None indicates either no image
        # or a new image is not yet saved .
        self.mirroredvertically = False #. is image v.mirrored?
        self.mirroredhorizontally = False #. is image h.mirrored?

		#. create a dockable, identifying its parent widget:
        logDockWidget = QDockWidget("Log", self)
        #. the object name is used later when we ask the system to
        # save and restore the dockable's current location:
        logDockWidget.setObjectName("LogDockWidget")
        #. where can it be docked to? in the case of a log
        # best to have its pane be a column:
        logDockWidget.setAllowedAreas(Qt.LeftDockWidgetArea|
                                      Qt.RightDockWidgetArea)
        #. we skip the setFeatures function because we want
        # the defaults: movable, floatable, closable .
        #. our dockable log uses a list.widget as content:
        self.listWidget = QListWidget()
        logDockWidget.setWidget(self.listWidget)
        #. attach the dockable to the app:
        self.addDockWidget(Qt.RightDockWidgetArea, logDockWidget)

        self.printer = None
        #( the first time user asks for a printout,
        # we will assign a printer object and retain it for later use
        # so that it preserves the user's printer options ).

        """. the status bar:
        http://qt-project.org/doc/.../QStatusBar.html
        The QStatusBar class provides a horizontal bar
        suitable for presenting status information.
        Each status indicator falls into one of three categories:
        # Normal:
        . occupies part of the status bar
        and may be hidden by temporary messages.
        . displayed by creating a small widget
        (QLabel, QProgressBar or even QToolButton)
        and then adding it using the addWidget();
        retrieved using statusBar();
        replaced using setStatusBar()
        or use removeWidget() .
        # Permanent:
        . is never hidden. for example, a Caps Lock indicator .
        . use addPermanentWidget() .
        # Temporary:
        . use showMessage(), then use clearMessage(),
        or set a time limit when calling showMessage().
        currentMessage() retrieves the temporary message .
        By default QStatusBar provides a QSizeGrip
        in the lower-right corner.
        You can disable it using setSizeGripEnabled().
        . isSizeGripEnabled() indicates current visibility .

        . we turn the sizegrip off because it
        "(seems inappropriate when we plan to have
        an indicator label that shows the image's dimension)
        -- apparently when you reach for the grip,
        it shows you the window's current dimensions .
        """
        status = self.statusBar()
        status.setSizeGripEnabled(False)
        # status bar permanently shows size of image:
        self.sizeLabel = QLabel()
        self.sizeLabel.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        status.addPermanentWidget(self.sizeLabel)
        # show a temp status for 5000 miliseconds:
        status.showMessage("Ready", 5000)

        # define the standard app menus using the subroutine:
        # createAction( [text of menu], [function to call],
        #   [keyboard accelerator],
        #   icon(name of .png resource),
        #   tip(string for tooltip),
        #   checkable=False, -- instead of function, set a checkbox?
        #   signal="triggered()" -- if checkable signal= toggled(bool).
        # )
        #(QKeySequence.* is a platform-specific keyboard accelerator).
        fileNewAction = self.createAction("&New...", self.fileNew,
                    QKeySequence.New, "filenew", "Create an image file")
        fileOpenAction = self.createAction("&Open...", self.fileOpen,
                QKeySequence.Open, "fileopen",
                "Open an existing image file")
        fileSaveAction = self.createAction("&Save", self.fileSave,
                QKeySequence.Save, "filesave", "Save the image")
        fileSaveAsAction = self.createAction("Save &As...",
                self.fileSaveAs, icon="filesaveas",
                tip="Save the image using a new name")
        filePrintAction = self.createAction("&Print", self.filePrint,
                QKeySequence.Print, "fileprint", "Print the image")
        fileQuitAction = self.createAction("&Quit", self.close,
                "Ctrl+Q", "filequit", "Close the application")
		#( self.close refers to the inherited method,
		# which then raises the closeEvent signal, which we reimpl' .)

    	#. create our 1st menu"File:
        self.fileMenu = self.menuBar().addMenu("&File")
        #. file.menu is generated dynamically as part of
        # providing a Recently Used Files list;
        # hence, it isn't built using addActions here;
        # instead, the ivar"fileMenuActions holds what goes in menu .
        self.fileMenuActions = (fileNewAction, fileOpenAction,
                fileSaveAction, fileSaveAsAction, None, filePrintAction,
                fileQuitAction)
        #. file.menu is rebuilt with self.updateFileMenu:
        self.connect(self.fileMenu, SIGNAL("aboutToShow()"),
                     self.updateFileMenu)
        #. activate the keyboard accelerators:
        self.updateFileMenu()

        #. create File.Menu's toolbar:
        fileToolbar = self.addToolBar("File")
        fileToolbar.setObjectName("FileToolBar")
        self.addActions(fileToolbar, (fileNewAction, fileOpenAction,
                                      fileSaveAsAction))
        # define the content-specific menus:
        editInvertAction = self.createAction("&Invert",
                self.editInvert, "Ctrl+I", "editinvert",
                "Invert the image's colors", True, "toggled(bool)")
        editSwapRedAndBlueAction = self.createAction("Sw&ap Red and Blue",
                self.editSwapRedAndBlue, "Ctrl+A", "editswap",
                "Swap the image's red and blue color components", True,
                "toggled(bool)")
        editZoomAction = self.createAction("&Zoom...", self.editZoom,
                "Alt+Z", "editzoom", "Zoom the image")

        #. create a submenu for the mirroring operations:
        mirrorGroup = QActionGroup(self)
        #--self is the parent widget needed for gc .
        # (this submenu contains 3 mutually exclusive states:
        # mirroring is off, vertical, or horizontal)

        #. create menu item"mirror/UnMirror:
        editUnMirrorAction = self.createAction("&Unmirror",
                self.editUnMirror, "Ctrl+U", "editunmirror",
                "Unmirror the image", True, "toggled(bool)")
        mirrorGroup.addAction(editUnMirrorAction)
        #. unMirrored is the initial state, so have it appears checked:
        editUnMirrorAction.setChecked(True)

        #. create menu item"mirror/Mirror Horizontally:
        editMirrorHorizontalAction = self.createAction(
                "Mirror &Horizontally", self.editMirrorHorizontal,
                "Ctrl+H", "editmirrorhoriz",
                "Horizontally mirror the image", True, "toggled(bool)")
        mirrorGroup.addAction(editMirrorHorizontalAction)

        #. create menu item"mirror/Mirror Vertically:
        editMirrorVerticalAction = self.createAction(
                "Mirror &Vertically", self.editMirrorVertical,
                "Ctrl+V", "editmirrorvert",
                "Vertically mirror the image", True, "toggled(bool)")
        mirrorGroup.addAction(editMirrorVerticalAction)

        #. create our 2nd menu"Edit:
        editMenu = self.menuBar().addMenu("&Edit")
        self.addActions(editMenu, (editInvertAction,
                editSwapRedAndBlueAction, editZoomAction))
        #. create the Edit.menu's submenu"Mirror:
        mirrorMenu = editMenu.addMenu(
						QIcon(":/editmirror.png"), "&Mirror")
        self.addActions(mirrorMenu, (editUnMirrorAction,
                editMirrorHorizontalAction, editMirrorVerticalAction))

        #. create Edit.Menu's toolbar:
        editToolbar = self.addToolBar("Edit")
        editToolbar.setObjectName("EditToolBar")
        self.addActions(editToolbar, (editInvertAction,
                editSwapRedAndBlueAction, editUnMirrorAction,
                editMirrorVerticalAction, editMirrorHorizontalAction))
        # edit.toolbar's zoom factor selection widget:
        self.zoomSpinBox = QSpinBox()
        self.zoomSpinBox.setRange(1, 400)
        self.zoomSpinBox.setSuffix(" %")
        self.zoomSpinBox.setValue(100)
        self.zoomSpinBox.setToolTip("Zoom the image")
        self.zoomSpinBox.setStatusTip(self.zoomSpinBox.toolTip())
        self.zoomSpinBox.setFocusPolicy(Qt.NoFocus)
        self.connect(self.zoomSpinBox,
                     SIGNAL("valueChanged(int)"), self.showImage)
        editToolbar.addWidget(self.zoomSpinBox)

        #. Help.menu's contents:
        helpAboutAction = self.createAction("&About Image Changer",
                self.helpAbout)
        helpHelpAction = self.createAction("&Help", self.helpHelp,
                QKeySequence.HelpContents)
        #. create our 3rd menu"Help:
        helpMenu = self.menuBar().addMenu("&Help")
        self.addActions(helpMenu, (helpAboutAction, helpHelpAction))

        #. widget for viewing our document:
        self.imageLabel = QLabel() #(labels can dislpay images).
        self.imageLabel.setMinimumSize(200, 200)
        self.imageLabel.setAlignment(Qt.AlignCenter)
        #. assigned to our app's main document view:
        self.setCentralWidget(self.imageLabel)

        #. our viewer supports a context menu:
        self.imageLabel.setContextMenuPolicy(Qt.ActionsContextMenu)
        #. we add context menu items now:
        self.addActions(self.imageLabel, (editInvertAction,
                editSwapRedAndBlueAction, editUnMirrorAction,
                editMirrorVerticalAction, editMirrorHorizontalAction))
        """. self.addActions is limited in the case of self.imageLabel
        because passing a None crashes instead of building a separator;
        we could instead pass noneForQWidget, defined as:
        noneForQWidget = QAction(self);
        noneForQWidget.setSeparator(True) .
        . to dynamically rebuild a context menu,
        we reimplement its contextMenuEvent() handler .
        """

        """. when the app's document has be replaced,
        the document-specific states need to be reset .
        . in this example, setting editUnMirrorAction to True
        implies setting Mirror {vert, horz} to false .
        . this is used by { fileNew(), loadFile() }:
        """
        self.resetableActions = ((editInvertAction, False),
                                 (editSwapRedAndBlueAction, False),
                                 (editUnMirrorAction, True))

        settings = QSettings() #--. a lack of arg here means
        # get a pointer to existing settings
        # which we defined in our main():
        #   app.setOrganizationName("Americium Dream")
        # 	app.setOrganizationDomain("code.google.com/u/dr.addn")
        #   app.setApplicationName("Image Changer")
        #   app.setWindowIcon(QIcon(":/icon.png"))
        #. settings.value(...) returns a QVariant that needs conversion
        # to a {StringList, ByteArray}:
        self.recentFiles = settings.value("RecentFiles").toStringList()
		#. an app's Geometry includes window size and position:
        self.restoreGeometry(
                settings.value("MainWindow/Geometry").toByteArray())
        #. app's State include Geometries of named toolbars and dockables:
        self.restoreState(settings.value("MainWindow/State").toByteArray())

        """. main is making 4 essential calls:
        app = QApplication(sys.argv) -- this is calling __init__
        ( the method we're in right now );
        and then when __init__ is done,
        main() can go on to the other 3 essential calls
        (needed for quickly showing user some action):
        form = MainWindow(); form.show(); app.exec_() .
        . if you have much work to do in loadInitialFile
        then you don't want to do that in __init__
        because it will impede main()'s showing of the window;
        so by launching loadInitialFile from QTimer with time=0,
        it's scheduled as a job having the lowest priority,
        and then pyqt won't do it until other work is done .
        """
        QTimer.singleShot(0, self.loadInitialFile)
    	#--. end of __init__ .

    def loadInitialFile(self):
		""". this is called from  __init__ in this form:
        QTimer.singleShot(0, self.loadInitialFile).
		. QFile.exists(filename) is used instead of
		python's os.access(filename, os.F_OK)
		as part of consistently preferring qt's lib's to python's .
		"""
        settings = QSettings()
        fname = unicode(settings.value("LastFile").toString())
        if fname and QFile.exists(fname):
            self.loadFile(fname)

    def createAction(self, text, slot=None, shortcut=None, icon=None,
                     tip=None, checkable=False, signal="triggered()"):
		""". helps prepare menu items . the param's are:
		[text of menu], [function to call when item selected],
		[keyboard accelerator; eg, "Ctrl+X" "Alt+X" ],
		icon(name of .png resource),
		tip(string for tooltip),
		checkable=False, -- instead of function, set a checkbox?
		signal="triggered()" -- if checkable signal= toggled(bool).
		"""
        action = QAction(text, self)
        if icon is not None:
            action.setIcon(QIcon(":/{0}.png".format(icon)))
        if shortcut is not None:
            action.setShortcut(shortcut)
        if tip is not None:
            action.setToolTip(tip)
            action.setStatusTip(tip)
        if slot is not None:
            self.connect(action, SIGNAL(signal), slot)
        if checkable:
            action.setCheckable(True)
        return action

    def addActions(self, target, actions):
		""". helps add items to menu .
        eg, self.addActions(fileToolbar,
				(fileNewAction, fileOpenAction,fileSaveAsAction)
			).
		"""
        for action in actions:
            if action is None:
                target.addSeparator()
            else:
                target.addAction(action)

    def closeEvent(self, event):
		""". when the window frame's close.box is clicked
		nothing happens but posting a closeEvent;
		when our menu launches the close() method
		that we inherited from QMainWindow,
		all that does toward saving state for us
		is post a closeEvent;
		therefore, to save state before any closing attempts,
		we need to re-impl' this closeEvent handler .
		. the state includes the subwindow arrangements,
		the current file name, and the other recent file names .
		"""
        if self.okToContinue():
            settings = QSettings()
            #(. QSettings() just returned a pointer to
            # the app's data record which is autosaved to a file .)
            filename = (QVariant(QString(self.filename))
                        if self.filename is not None else QVariant())
            settings.setValue("LastFile", filename)
            recentFiles = (QVariant(self.recentFiles)
                           if self.recentFiles else QVariant())
            settings.setValue("RecentFiles", recentFiles)
            settings.setValue("MainWindow/Geometry", QVariant(
                              self.saveGeometry()))
            settings.setValue("MainWindow/State", QVariant(
                              self.saveState()))
        else:
            event.ignore()

    def okToContinue(self):
		""". this is called by any of the routines that would be
		losing changes made on the current document;
		(that would normally include only the closeEvent handler,
		but since this is a single-document editor,
		any calls to newFile() or openFile() will also be calling).
		. it returns false if either the request was canceled
		or a fileSave() was requested but failed
		(eg, when the destination drive was full).
		"""
        if self.dirty:
            reply = QMessageBox.question(self,
                    "Image Changer - Unsaved Changes",
                    "Save unsaved changes?",
                    QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
            if reply == QMessageBox.Cancel:
                return False
            elif reply == QMessageBox.Yes:
                return self.fileSave()
        return True

    def updateStatus(self, message):
		""". the given message is shown for 5.000 sec's;
		and the document's title is displayed:
		if this is a saved document (with a name)
		then show filename as the document title,
		else show a default title .
		"""
        self.statusBar().showMessage(message, 5000)
        self.listWidget.addItem(message)
        if self.filename is not None:
            self.setWindowTitle("Image Changer - {0}[*]".format(
								os.path.basename(self.filename)))
                                #(without folder path location)
                                #(aka QFileInfo(self.filename).fileName())
        elif not self.image.isNull():
			#. image exists but is unnamed:
            self.setWindowTitle("Image Changer - Unnamed[*]")
        else: #. app has no document yet:
            self.setWindowTitle("Image Changer[*]")
            #("Image Changer[*]" is a coded string:
            # it tells QMainWindow to use the platform's method
            # for indicating whether the current content
            # has unsaved changes . for most platforms
            # the convention is to asterisk the title name).
        self.setWindowModified(self.dirty)
        #(tells QMainWindow whether we have unsaved changes).

    def updateFileMenu(self):
        self.fileMenu.clear()
        #. draw all file menu items but the Quit:
        self.addActions(self.fileMenu, self.fileMenuActions[:-1])
        current = (QString(self.filename)
                   if self.filename is not None else None)
        recentFiles = []
        #. filter the recentFiles list for existing and not current:
        for fname in self.recentFiles:
            if fname != current and QFile.exists(fname):
                recentFiles.append(fname)
        #. add any recent existing files to file.menu:
        if recentFiles:
            self.fileMenu.addSeparator()
            for i, fname in enumerate(recentFiles):
                action = QAction(QIcon(":/icon.png"),
                        "&{0} {1}".format(i + 1,
                        QFileInfo(fname).fileName()), self)
                        # ^- gets base name without pathname .
                action.setData(QVariant(fname))
                self.connect(action, SIGNAL("triggered()"),
                             self.loadFile)
                self.fileMenu.addAction(action)
        self.fileMenu.addSeparator()
        #. the last item is the Quit:
        self.fileMenu.addAction(self.fileMenuActions[-1])

    def fileNew(self):
		""". Qt Designer helps you design a widget (the view)
		and writes it as a .ui file;
		then mkpyqt.py converts the .ui into
		the same pyqt code you would use to generate a widget;
		eg, ui_newimagedlg.py was mkpyqt.py-generated
		from newimagedlg.ui .
		. you can then multi-inherit that into your dialog like so:
		class NewImageDlg(QDialog, ui_newimagedlg.Ui_NewImageDlg).
		. that .ui inheritance provides one function:
		setupUi(self, NewImageDlg)
		which calls what Qt Designer made (a view) .
		. fileNew() calls a dialog, newimagedlg.py,
		that inherits ui_newimagedlg.py;
		NewImageDlg uses that view to let you customize an image
		with a pen`width, height, brush pattern, and ink color .
		"""
        if not self.okToContinue():
            return
        # else nothing to save, and ok to overwrite app's content:
        dialog = newimagedlg.NewImageDlg(self)
        if dialog.exec_():
			# we have a new image, so add old one to recentFiles:
            self.addRecentFile(self.filename)
            self.image = QImage() #-- a null image,
            # hence not affected by the following:
            for action, check in self.resetableActions:
                action.setChecked(check)
            #. now that checkables are reset,
            # assign user's image to app's content:
            self.image = dialog.image()
            #(self.image = QPixmap.toImage(pixmap)).
            self.filename = None
            self.dirty = True #. contains some of user's choices .
            self.showImage()
            #. update status bar:
            self.sizeLabel.setText("{0} x {1}".format(self.image.width(),
                                                      self.image.height()))
            self.updateStatus("Created new image")

    def fileOpen(self):
		""". this opens a file to overwrite current document;
		it opens the same folder as that holding current doc
		(if current is new, it opens the current working dir).
		. since this is an image-viewing app,
		it shows only files in QImageReader.supportedImageFormats .
		"""
        if not self.okToContinue():
            return
        dir = (os.path.dirname(self.filename)
               if self.filename is not None else ".")
        formats = ["*.{0}".format(unicode(fileExt).lower()) \
                    for fileExt in QImageReader.supportedImageFormats()]
        fname = unicode(QFileDialog.getOpenFileName(self,
                "Image Changer - Choose Image", dir,
                "Image files ({0})".format(" ".join(formats))))
				#. shows Image files (*.bmp, *.jpg, *.jpeg [...])
				# as one of the user's file filtering options;
				# to give the user multiple options,
				# separate them by newlines ('\n').
        if fname:
            self.loadFile(fname)

    def loadFile(self, fname=None):
		""". this is the workhorse for
		fileOpen, loadInitialFile, and the file.menu items
		that let you choose a recent file to open;
		in the case of recent files,
		the parameter fname is not assigned anything
		so this routine has to query the menu item,
		asking it for the name of the file it's named after .
		"""
        if fname is None:
			# then this was called by a recentfiles menu item .
            action = self.sender() #get a link to the menu item .
            if isinstance(action, QAction): #(paranoid sanity check)
				# the menu item's data is the filename we need:
                fname = unicode(action.data().toString())
                #. this is about to overwrite the current document,
                # so we need to ask user if they want to save
                # and since this was called by a menu item
                # we know the user has not been asked to save yet,
                # so we do that now:
                if not self.okToContinue():
                    return
            else:
                return # or write to error log .
		#. now fname should be defined,
		# and user has had a chance to save current document .
        if fname:
            self.filename = None
            image = QImage(fname)
            if image.isNull():
				#. status bar message shows load failure:
                message = "Failed to read {0}".format(fname)
            else:
				#. get ready to update recentFiles menu:
                self.addRecentFile(fname)
                #. reset the checkable menus:
                self.image = QImage()
                for action, check in self.resetableActions:
                    action.setChecked(check)
                #. install the new image as our current document:
                self.image = image
                self.filename = fname
                self.showImage()
                #. no changes made to this one yet:
                self.dirty = False
                #. status bar message shows image dimensions
                self.sizeLabel.setText("{0} x {1}".format(
                                       image.width(), image.height()))
                message = "Loaded {0}".format(os.path.basename(fname))
            #. status bar is updated with new message:
            self.updateStatus(message)

    def addRecentFile(self, fname):
		""". this is meant to be passed the current filename,
		but it could be None if the document is
		new and still unamed .
		. if named and not already in the recentfiles.QStringList,
		then add it to the front (with QStringList`prepend)
		and if we've reached max length of list
		then use QStringList`takeLast to remove the old item
		(at the end of the list).
		"""
        if fname is None:
            return
        if not self.recentFiles.contains(fname):
            self.recentFiles.prepend(QString(fname))
            while self.recentFiles.count() > 9:
                self.recentFiles.takeLast()

    def fileSave(self):
        if self.image.isNull():
			#. then we should save an empty file?
			# the author considers it trivially saved already .
            return True #(=file is trivially saved)
        if self.filename is None:
			#then we need to ask user for a filename:
            return self.fileSaveAs()
        else: #. we have a name and a non-empty image to save:
            if self.image.save(self.filename, None):
                self.updateStatus("Saved as {0}".format(self.filename))
                self.dirty = False
                return True #(=file is saved)
            else:
                self.updateStatus("Failed to save {0}".format(
                                  self.filename))
                return False #(=file is not saved)

    def fileSaveAs(self):
		""". fileSaveAs first needs to get the user's filename choice,
		using QFileDialog.getSaveFileName
		( we first need to prep its parameters that define
		filetype filtering and the current filename or directory ).
		. if the user didn't include the file extension,
		we have to fix that (apparently the saver workhorse
		uses the filetype to know which format to save as,
		and if the name didn't include a filetype,
		then we choose one for the user (.png).
		. finally we call fileSave to do the actual save .
		"""
        if self.image.isNull():
            return True #(this actually isn't correct behavior
            # because the user could have wanted to save an empty file).
        #. we have an image to save so start prepping parameters;
        # if there is no current filename (and an implied directory)
        # have the save-as dialog open the current directory:
        fname = self.filename if self.filename is not None else "."
        #. prep the filetype filter:
        formats = (["*.{0}".format(unicode(format).lower())
                for format in QImageWriter.supportedImageFormats()])
		#. call a workhorse to get the user's chosen filename:
        fname = unicode(QFileDialog.getSaveFileName(self,
                "Image Changer - Save Image", fname,
                "Image files ({0})".format(" ".join(formats))))
        if fname:
			# then user chose a filename but may left out the ext,
            if "." not in fname: # if so then add ext of .png .
                fname += ".png"
            #. now the we have a complete filename to save to,
            # so we can ...
            self.addRecentFile(fname) # put that on the list,
            self.filename = fname # rename the document,
            return self.fileSave() # and call the file-saving workhorse .
        #. if fname is None, then
        return False # signal the save failed .

    def filePrint(self):
		""". this defers creation of a printer object
		for when the printer is first requested,
		and then it keeps the printer object around
		to preserve the user's preference for the session .
		.
		"""
        if self.image.isNull(): #. image is empty?
            return # avoid printing emptiness .
        #. make sure a printer object exists:
        if self.printer is None:
            self.printer = QPrinter(QPrinter.HighResolution)
            self.printer.setPageSize(QPrinter.Letter)
        #. get user's printer pref's:
        form = QPrintDialog(self.printer, self)
        if form.exec_(): #. when user is done with dialog,
			# start the printer:
            painter = QPainter(self.printer)
            rect = painter.viewport()
            size = self.image.size()
            size.scale(rect.size(), Qt.KeepAspectRatio)
            painter.setViewport(rect.x(), rect.y(), size.width(),
                                size.height())
            painter.drawImage(0, 0, self.image)

    def editInvert(self, on):
        if self.image.isNull():
            return
        self.image.invertPixels()
        self.showImage()
        self.dirty = True
        self.updateStatus("Inverted" if on else "Uninverted")

    def editSwapRedAndBlue(self, on):
        if self.image.isNull():
            return
        self.image = self.image.rgbSwapped()
        self.showImage()
        self.dirty = True
        self.updateStatus(("Swapped Red and Blue"
                           if on else "Unswapped Red and Blue"))

    def editUnMirror(self, on):
        if self.image.isNull():
            return
        if self.mirroredhorizontally:
            self.editMirrorHorizontal(False)
        if self.mirroredvertically:
            self.editMirrorVertical(False)

    def editMirrorHorizontal(self, on):
        if self.image.isNull():
            return
        self.image = self.image.mirrored(True, False)
        self.showImage()
        self.mirroredhorizontally = not self.mirroredhorizontally
        self.dirty = True
        self.updateStatus(("Mirrored Horizontally"
                           if on else "Unmirrored Horizontally"))

    def editMirrorVertical(self, on):
        if self.image.isNull():
            return
        self.image = self.image.mirrored(False, True)
        self.showImage()
        self.mirroredvertically = not self.mirroredvertically
        self.dirty = True
        self.updateStatus(("Mirrored Vertically"
                           if on else "Unmirrored Vertically"))

    def editZoom(self):
        if self.image.isNull():
            return
        percent, ok = QInputDialog.getInteger(self,
                "Image Changer - Zoom", "Percent:",
                self.zoomSpinBox.value(), 1, 400)
        if ok:
            self.zoomSpinBox.setValue(percent)

    def showImage(self, percent=None):
		""". this slot is triggered when the document's view changes;
		in this app, the document is an image
		that changes whenever it's been replaced, transformed,
		or viewed at a new magnification .
		. our document is a QImage (a type optimized for editing)
		and it's copied to a QPixmap (optimized for fast drawing).
		. the zoom transform is applied only to the view,
		not the document .
		"""
        if self.image.isNull():
            return
        if percent is None:
            percent = self.zoomSpinBox.value()
        factor = percent / 100.0
        width = self.image.width() * factor
        height = self.image.height() * factor
        #. create a copy of image for the view:
        image = self.image.scaled(width, height, Qt.KeepAspectRatio)
        self.imageLabel.setPixmap(QPixmap.fromImage(image))

    def helpAbout(self):
        QMessageBox.about(self, "About Image Changer",
                """<strong>Image Changer</strong> v {0}

Copyright © 2008 Qtrac Ltd.
                All rights reserved.

This application can be used to perform
                simple image manipulations.

Python {1} - Qt {2} - PyQt {3} on {4}""".format(
                __version__, platform.python_version(),
                QT_VERSION_STR, PYQT_VERSION_STR,
                platform.system()))

    def helpHelp(self):
        form = helpform.HelpForm("index.html", self)
        form.show()

def main():
    app = QApplication(sys.argv)
    app.setOrganizationName("Qtrac Ltd.")
    app.setOrganizationDomain("qtrac.eu")
    app.setApplicationName("Image Changer")
    app.setWindowIcon(QIcon(":/icon.png"))
    form = MainWindow()
    form.show()
    app.exec_()

main()

2 comments on “commented PyQt code for main windows

  1. Thanks for the file. I would like to aks you for some help as I have problems with the code. It raises the following error:
    ImportError: No module named ui_newimagedlg
    Can you please tell me there could be the problem?

    Like

  2. the full error:
    class NewImageDlg(QDialog, newimagedlg.NewImageDlg):
    AttributeError: ‘module’ object has no attribute ‘NewImageDlg’

    Like

Leave a comment