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

An editor - the works


An editor isn't much use unless you can actually load and save text files. To achieve this is actually surprisingly easy.


#!/usr/bin/env python

import sys
from kdeui import *
from kdecore 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

# Edit object

class Edit(KTMainWindow):
  
  def __init__ (self):
    KTMainWindow.__init__(self)
    
    self.filename_ = None
    
    self.initMenuBar()
    self.initToolBar()
    self.initStatusBar()
    
    self.view=QMultiLineEdit(self, "Main View")
    self.setView(self.view)
    self.show()
    
  def commandCallback (self, id):
    if id == ID_NEW:
      self.view.clear()
      self.filename_=None
    elif id == ID_OPEN:
      name=QFileDialog.getOpenFileName()
      if name is not None:
        self.load(name)
    elif id == ID_SAVE:
      if self.filename_ is not None:
        self.saveAs(self.filename_)
      else:
        name=QFileDialog.getSaveFileName()
        if name is not None:
          self.saveAs(name)
    elif id == ID_SAVEAS:
      name=QFileDialog.getSaveFileName()
      if name is not None:
        self.saveAs(name)
    elif id == ID_ABOUT:
      QMessageBox.about(self
                       , "About Edit"
                       , "This is a simple text editor"
                       )
    elif id == ID_EXIT:
       self.close()

  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 closeEvent(self, QCloseEvent):
      QCloseEvent.accept()

  def saveAs(self, filename):
    f=open(filename, "w+")
    f.write(self.view.text())
    self.filename_=filename
    
  def load(self, filename):
    f=open(filename)
    s=f.read()
    self.view.setText(s)
    self.filename_=filename

# Main clause

app=KApplication(sys.argv,"EditApp")
toplevel=Edit()
app.setMainWidget(toplevel)
toplevel.show()
app.exec_loop()

In this application, there's a central method, commandCallback, that handles the actions the user demanded from the menu or the butotonbar. Depending upon the command given, some action is performed. The signal-slot mechanics should be clear by now.

The actions performed in commandCallback are:

The Edit widget now has a state variable, namely self.filename_, that keeps the name of the file opened. The Open, Save and Save As commands ultimately lead to the saveAs and load methods. Both are quite simple.

The saveAs method opens a Python file object for writing and dumps the entire contents of the QMultiLineEdit widget into that file. Instant gratification:

  f.write(self.view.text())

The load method is as simple. A file is opened, read from begin to end and sucked into the QMultiLineEdit widget:

     self.view.setText(s)

Compared to the exertions of the C++ contortionists, this is almost embarrassingly simple:

void Edit::load(const char *filename) {
  QFile f( filename );
  if ( !f.open( IO_ReadOnly ) )
          return;

  view->setAutoUpdate( FALSE );
  view->clear();

  QTextStream t(&f);
  while ( !t.eof() ) {
    QString s = t.readLine();
    view->append( s );
   }
  f.close();

  view->setAutoUpdate( TRUE );
  view->repaint();
  filename_= filename;
}

And of course, the following is good Python too:

  def load(self, filename):
    file=open(filename)
    text=file.read()
    self.view.setText(text)
    self.filename_=filename

Another nice possiblity is to use KFileDialog.getOpenFileURL instead of the QFileDialog.getOpenFileName. It will return an url and you can use the urllib.urlopen function instead of a normal file function you have instant network transparancy. In fact, you should think twice, or preferably trice, before implementing just a local file dialog, instead of a network transparant one. Indeed, you shouldn't confine your users to their own desktop.

It is true that the dialog has a few problems - you can't browse with it yet, but it won't be long before that's solved, too.


Changes