"Simple Finished"

Preamble

If you want just to run the example, you are advised to:

Introduction

It is usual to present tutorials with part of the project and only then the whole. It seems to me that it is not the best way. I feel that it is better to first state the objective and show what the finished project will look like and only then take it in small steps, showing at each step what has been achieved.

The aim of this tutorial is to show how a simple, yet reasonably complete, plain text editor can be created using the PyQt. We shall call that simple editor, rather appropriately, "Simple". As we are first presenting the finished project, some readers will want to see how it was done and try it out themselves. They are urged to look at each Stage 0 first, then Stage 1 and so on. Please try to code each stage yourselves, as you will gain most out of doing, rather than reading.

Other readers my find that the example is too simple for them and they are looking for some specific, advanced technique. By making discovering this in early reading of this tutorial they will be saved unnecessary looking through this presentation that is too simple and pace that is too slow. The motto for this tutorial is "from a newbie to other newbies" and we apologise up front if it does not apply to you.

"Simple" Finished

Finished is a relative term - there are features which could usefully be added to this GUI program, but they may well be contrary to the aim of keeping the example as simple as possible.

The main item in that category is saving the program state at the time of closing it and remembering the state when next opening the program. The technique to do that is well described in chapter 6 of the book "Rapid GUI Programming with Python and Qt" by Mark Summerfield. It is a fine book well worth a place on every PyQt programmers desk. The omission of this feature is partly prompted by similar omissions from some "standard" programs, notably "Dolphin" file manager. Personally when I use "Dolphin", I wish that it did "remember" where we were at in the previous session... But it it is good enough for systems programmers to forget about it, it may be good enough for a tutorial example that aims at simplicity!

One other feature requires qualification: the "help" files are installed in the "qrc" resources. That is convenient, but also doubtful in view of its lack of economy of RAM. It seems that the resource file is automatically loaded in memory at the start of the program. That my be fine today when the memory is measured in Giga Bytes. Fine, provided that the program does not require large memory for, say, equations solving, a feature of programs for Structural Analysis of Engineering Structure, my area of interest. So personally, I would prefer to save resources of RAM and leave the help system in html format, particularly since help system, IMHO, is never too extensive.

Image of "Simple"

editor0.1.00.png

Program Listing of "Simple"

   1 #!/usr/bin/env python
   2 # .../simple.py - developing of a simple text editor.
   3 '''A really simple editor program in PyQt4 - simple.py
   4 '''
   5 
   6 import sys
   7 import os
   8 import platform
   9 
  10 from PyQt4.QtGui import (QMainWindow, QApplication, QFileDialog,
  11                          QKeySequence, QAction, QIcon, QMessageBox)
  12 from PyQt4.QtCore import (SIGNAL, PYQT_VERSION_STR, QT_VERSION,
  13                           QT_VERSION_STR)
  14 
  15 from ui_simple import Ui_MainWindow
  16 import qrc_simple
  17 import helpform
  18 
  19 __version__ = "0.1.00"
  20 
  21 class MainWindow(QMainWindow, Ui_MainWindow):
  22     def __init__(self, parent=None):
  23         super(MainWindow, self).__init__(parent)
  24         self.setupUi(self)        
  25         self.action_New = self.editAction(self.action_New, self.fileNew,
  26                 QKeySequence.New, "filenew", 
  27                 'Clear the textEdit window for a new file.')
  28         self.action_Open = self.editAction(self.action_Open, self.fileOpen, 
  29                         QKeySequence.Open, "fileopen", "Open an existing file")
  30         self.action_Save = self.editAction(self.action_Save, self.fileSave, 
  31                                 QKeySequence.Save, "filesave", "Save file")
  32         self.actionSave_As = self.editAction(self.actionSave_As, self.fileSaveAs, 
  33                             "Ctrl+A", "filesaveas", "Save file with a new name")
  34         self.action_Quit = self.editAction(self.action_Quit, self.fileQuit, 
  35                             "Ctrl+Q", "filequit", "Close main window and application")
  36         self.fileName = None
  37         fileToolbar = self.addToolBar("File")
  38         self.addActions(fileToolbar, (self.action_New, self.action_Open,\
  39                                        self.action_Save, self.actionSave_As))
  40         self.resize(800, 600)
  41         self.dirty = False
  42         self.textEdit.textChanged.connect(self.setDirty)
  43 #=================================================================================
  44 # Supplementary stuff for Help/aTutorialNoteAbout and Help/Help menu items.
  45         self.actionA_bout = self.editAction(self.actionA_bout, self.about, 
  46                                             "Ctrl+B", "about", "Popup About dialog")
  47         self.action_Help = self.editAction(self.action_Help, self.help, 
  48                                            "Ctrl+H", "help", "Show Help pages")
  49         helpToolBar = self.addToolBar("Help")
  50         self.addActions(helpToolBar, (self.actionA_bout, self.action_Help))
  51 #=================================================================================
  52 # Add quit tool bar. It would be nice to have it at the right side of MainWindow...
  53         quitToolBar = self.addToolBar("Quit")
  54         self.addActions(quitToolBar, (self.action_Quit, ))
  55 #=================================================================================
  56 
  57     def fileQuit(self):
  58         pass
  59         
  60     def about(self):
  61         '''Popup a box with about message.'''
  62         QMessageBox.about(self, "About Simple Editor",
  63                 """<b>Simple</b> v %s
  64                 <p>Copyright &copy; 2010 A. Kabaila. 
  65                 All rights reserved in accordance with
  66                 GPL v2 or later.
  67                 <p>This application can be used for 
  68                 simple plain text editing.
  69                 <p>Python %s - Qt %s - PyQt %s on %s""" % (
  70                 __version__, platform.python_version(),
  71                 QT_VERSION_STR, PYQT_VERSION_STR, platform.system()))
  72 
  73     def help(self):
  74         '''Display index.html file.'''
  75         form = helpform.HelpForm("index.html", self)
  76         form.show()        
  77 
  78     def setDirty(self):
  79         '''On change of text in textEdit window, set the flag
  80         "dirty" to True'''
  81         if self.dirty:
  82             return True        
  83         self.dirty = True
  84         self.updateStatus('self.dirty set to True')
  85         
  86     def clearDirty(self):
  87         '''Clear the dirty.'''
  88         self.dirty = False
  89 
  90     def updateStatus(self, message):
  91         '''Keep status current.'''
  92         if self.fileName is not None:
  93             flbase = os.path.basename(self.fileName)
  94             self.setWindowTitle(unicode("Simple Editor - " + flbase + "[*]") )
  95             self.statusBar().showMessage(message, 5000)
  96             self.setWindowModified(self.dirty)
  97         
  98     def okToContinue(self):
  99         '''Boolean result invocation method.'''
 100         if self.dirty:
 101             reply = QMessageBox.question(self,
 102                     "Simple Editor - Unsaved Changes",
 103                     "Save unsaved changes?",
 104                     QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
 105             if reply == QMessageBox.Cancel:
 106                 return False
 107             elif reply == QMessageBox.Yes:
 108                 return self.fileSave()
 109         return True
 110         
 111     def addActions(self, target, actions):
 112         '''Add actions to tool bars or menus'''
 113         for action in actions:
 114             if action is None:
 115                 target.addSeparator()
 116             else:
 117                 target.addAction(action)
 118 
 119     def editAction(self, action, slot=None, shortcut=None, icon=None,
 120                      tip=None, checkable=False, signal="triggered()"):
 121         '''Add attributes to Action that have not been generated by
 122         the Qt Designer.'''
 123         if icon is not None:
 124             action.setIcon(QIcon(":/{0}.png".format(icon)))
 125         if shortcut is not None:
 126             action.setShortcut(shortcut)
 127         if tip is not None:
 128             action.setToolTip(tip)
 129             action.setStatusTip(tip)
 130         if slot is not None:
 131 #            self.connect(action, SIGNAL(signal), slot)            
 132             action.triggered.connect(slot)                        
 133         if checkable:
 134             action.setCheckable(True)
 135         return action
 136 
 137     def fileNew(self):
 138         '''Clear the editor window for a new file with name
 139         specified in fileSaveAs method.'''
 140         if not self.okToContinue():
 141             return
 142         self.textEdit.setText('')
 143         self.statusBar().showMessage('File menu: New selected', 5000)
 144 
 145     def fileOpen(self):
 146         '''Open file'''
 147         if not self.okToContinue():
 148             return
 149         fname = unicode(QFileDialog.getOpenFileName(self,
 150                         "Open File", '.', "Files (*.*)"))
 151         if not (fname == ""):
 152             self.textEdit.setText(open(fname).read())
 153             self.fileName = fname
 154         else:
 155             return
 156         self.clearDirty()
 157         self.updateStatus('File opened.')
 158         
 159     def fileSave(self):
 160         '''Save file with current name.'''
 161         if self.fileName is None:
 162             return self.fileSaveAs()
 163         else:
 164             if not self.dirty:
 165                 return
 166             fname = self.fileName
 167             fl = open(fname, 'w')
 168             tempText = self.textEdit.toPlainText()
 169             if tempText:                
 170                 fl.write(tempText)
 171                 fl.close()
 172                 self.clearDirty()
 173                 self.updateStatus('Saved file') 
 174                 return True
 175             else:
 176                 self.statusBar().showMessage('Failed to save ...', 5000)
 177                 return False
 178 
 179     def fileSaveAs(self):
 180         '''Save file with a different name and maybe different directory.'''
 181         path = self.fileName if self.fileName is not None else "."
 182         fname = unicode(QFileDialog.getSaveFileName(self,
 183                         "Simple Editor, SaveAs ", path, "Any File (*.*)"))
 184         if fname:
 185             if "." not in fname:
 186                 fname += ".txt"
 187             self.fileName = fname
 188             self.fileSave()        
 189             self.statusBar().showMessage('SaveAs file' + fname, 8000)
 190             self.clearDirty()
 191             
 192 if __name__ == '__main__':
 193     '''Execute this part of program if it is run as mainline.'''
 194     app = QApplication(sys.argv)
 195     frame = MainWindow()
 196     frame.show()
 197     app.exec_()

Let me reiterate the recommendation to download the full listing of program from a the tar ball, named "simple0.1.00.tar.gz", which is available from http://akabaila.pcug.org.au/data_sample/ directory. It would be greatly appreciated if you let me know if that worked for you: email akabaila[at]pcug[dot]org[dot]au

The tar ball is meant to have all the required files - the original simple.ui file, generated by the Qt Designer and saved by us; the simple.qrc file, an XML file with list of icons and other resources used by the program, as well as the resources themselves. Also, the automatically generated ui_simple.py and qrc_simple.py files which are python program files, importable to the project.

As programs go, the above listing is small - less than 200 lines. If you understand it all and could now program it all yourself, then just go and do it - congratulations, you are done with this tutorial! If you feel a little doubtful or curious how it all developed, then let us go to the beginning of it - Stage 0, followed by Stage 1 and so on. I enjoy your company, stay with it!

Return Home

PyQt/simplefinis (last edited 2014-06-08 13:43:42 by DavidBoddie)

Unable to edit the page? See the FrontPage for instructions.