[Main Page] [Edit #1] [Edit #2] [Edit #3] [Edit #4] [Edit #5]

An editor - the dots on the i


In this section we will make the editor a bit more friendly. We'll make the statusbar natter on about the name of the file under hand, have buttons enabled and disabled at will, have the editor ask us whether we really want to lose our work, and, yes, implement the network transparency I mentioned in the previous section.


#!/usr/bin/env python

import sys
import urllib
from kdeui import *
from kdecore import *
from kfile import *
from qt import *

#
# Constants
#

TRUE=1
FALSE=0

# Generic command codes

ID_OPEN=100
ID_NEW=101
ID_SAVE=102
ID_SAVEAS=103
ID_CLOSE=104
ID_NEWWINDOW=105
ID_CLOSEWINDOW=106
ID_COPY=107
ID_CUT=108
ID_PASTE=109
ID_OPTIONS=110
ID_EXIT=111
ID_HELPCONTENTS=112
ID_ABOUT=113
ID_HINTTEXT=300

# Status item codes

EDIT_VERSION_STRING="0.1"

#
# Edit object
#

class Edit(KTMainWindow):
  
  def __init__ (self):
    KTMainWindow.__init__(self)
    
    self.filename_ = None
    self.modified_ = FALSE
    
    self.initMenuBar()
    self.initToolBar()
    self.initStatusBar()
    
    self.view=QMultiLineEdit(self, "Main View")
    self.connect(self.view, SIGNAL("textChanged()"), self.textChanged)
    self.setView(self.view)
    self.show()
    
    self.enableCommand(ID_OPEN)
    self.enableCommand(ID_EXIT)
    self.disableCommand(ID_CLOSE)
    self.disableCommand(ID_SAVE)
    self.disableCommand(ID_SAVEAS)

  def commandCallback (self, id):
    if id == ID_NEW:
      self.view.clear()
      self.filename=""
    elif id == ID_OPEN:
      name=KFileDialog.getOpenFileURL()
      if name is not None:
        self.load(name)
    elif id == ID_SAVE:
      if self.filename_ is not None:
        self.saveAs(self.filename_)
      else:
        name=KFileDialog.getSaveFileName()
        if name is not None:
          self.saveAs(name)
    elif id == ID_SAVEAS:
      name=KFileDialog.getSaveFileName()
      if name is not None:
        self.saveAs(name)
    elif id == ID_ABOUT:
      QMessageBox.about(self
                       , "About EditApp"
                       , "This is a simple text editor"
                       )
    elif id == ID_EXIT:
       self.close()

  def textChanged(self):
    self.modified_=TRUE;
    self.enableCommand(ID_SAVE)
    self.enableCommand(ID_SAVEAS)

  def initMenuBar(self):
    self.file=QPopupMenu()
    self.file.insertItem(i18n("&Open..."), ID_OPEN)
    self.file.insertItem(i18n("&New..."), ID_NEW)
    self.file.insertItem(i18n("&Save"), ID_SAVE)    
    self.file.insertItem(i18n("Save &As..."), ID_SAVEAS)
    self.file.insertSeparator()
    self.file.insertItem(i18n("&Exit"), ID_EXIT)

    self.help=QPopupMenu()
    self.help.insertItem(i18n("&Contents..."), ID_HELPCONTENTS)
    self.help.insertItem(i18n("&About..."), ID_ABOUT)
    
    self.menu=KMenuBar(self)
    self.menu.insertItem(i18n("&File"), self.file)
    self.menu.insertItem(i18n("&Help"), self.help)
    self.menu.show()
    self.setMenu(self.menu)
    
    self.connect(self.file
           ,SIGNAL("activated(int)")
           ,self.commandCallback)
    self.connect(self.help
           ,SIGNAL("activated(int)")
           ,self.commandCallback)



  def initToolBar(self):
    self.toolbar=KToolBar(self)
    self.toolbar.insertButton(Icon("filenew.xpm")
                             ,ID_NEW, TRUE
                             ,i18n("Create a new file")
                             )
    self.toolbar.insertButton(Icon("fileopen.xpm")
                             ,ID_OPEN, FALSE
                             ,i18n("Open a file")
                             )
    self.toolbar.insertButton(Icon("filefloppy.xpm")
                             ,ID_SAVE, FALSE
                             ,i18n("Save the current file")
                             )    
    self.addToolBar(self.toolbar)
    self.toolbar.show()
    self.connect(self.toolbar
                ,SIGNAL("clicked(int)")
                ,self.commandCallback
                )
    
  def initStatusBar(self):
    self.statusbar=KStatusBar(self)
    self.statusbar.insertItem("Welcome to Edit",ID_HINTTEXT)
    self.statusbar.show()
    self.setStatusBar(self.statusbar)
    
  def setHint(self,text):
    self.statusbar.changeItem(text, ID_HINTTEXT)

  def closeEvent(self, QCloseEvent):
    if self.exit()==TRUE:
      QCloseEvent.accept()

  def exit(self):
    die=FALSE
    if self.modified_==FALSE:
      die=TRUE
    else:
      if QMessageBox.warning ( self
                             , i18n("Unsaved Changes")
                             , "You have unsaved changes, you will loose them \nif you exit now."
                             , 1
                             , 2
                             ) == 2:
        die=FALSE
      else:
        die=TRUE
    return die
   
  def enableCommand(self,id):
    self.toolbar.setItemEnabled(id, TRUE)
    self.menu.setItemEnabled(id, TRUE)

  def disableCommand(self,id):
    self.toolbar.setItemEnabled(id, FALSE)
    self.menu.setItemEnabled(id, FALSE)
    
  def saveAs(self, filename):
    f=open(filename, "w+")
    f.write(self.view.text())
    self.filename_=filename
    self.setHint(filename)
    
  def load(self, filename):
    self.view.setText(urllib.urlopen(filename).read())
    self.filename_=filename
    self.setHint(filename)
    self.modified_=FALSE

#
# Helper functions
#

def usage():
  print 'edit ' + EDIT_VERSION_STRING + ' (c) Boudewijn Rempt 1999'
  print 'An adaptation of Richard J. Moores sample application'

#
# Main clause
#

if len(sys.argv) > 1:
  if sys.argv[1] == '-h' or sys.argv[1] == '-help' or sys.argv[1] =='--help':
    usage()
    sys.exit()
  
app=KApplication(sys.argv,"EditApp")
toplevel=Edit()
app.setMainWidget(toplevel)
toplevel.show()
app.exec_loop()

We have a second state variable, self.modified_, which helps us in remembering whether the user has made a change in his text. Depending on the state of self.modified__, we decide whether to let a warning dialog pop up that asks us about losing our work. That mechanism should be reasonably clear: we connect to the textChanged signal to set the self.modified__ variable to true. Of course, after loading a file, the contents of the editor have changed, but the text itself not, so in the load method we set self.modified_ to false after loading.

In a previous lesson, we said kapp.quit() in the closeEvent. This time we are a bit more sophisticated: we tell pass the event on, saying that we've accepted it: QCloseEvent.accept(). I've been unable to ascertain whether there's any important difference, though.

We also have a nice message appear on standard output when someone starts the editor up with -h, --help or -help from the commandline.

Finally, upon loading, the statusbar text is updated. To do this we need the identifying constant we used to put the initial text in the statusbar.

Maybe it's possible to rewrite the editor so that remote saving becomes possible. The KDE docs don't say it's impossible. Have a go at it, I'd say.


Changes