I l@ve RuBoard Previous Section Next Section

9.4 PyEdit: A Text Editor Program/Object

In the last few decades, I've typed text into a lot of programs. Most were closed systems (I had to live with whatever decisions their designers made), and many ran on only one platform. The PyEdit program presented in this section does better on both counts: it implements a full-featured, graphical text editor program in roughly 470 lines of portable Python code (including whitespace and comments). Despite its size, PyEdit was sufficiently powerful and robust to serve as the primary tool used to code most examples in this book.

PyEdit supports all the usual mouse and keyboard text-editing operations: cut and paste, search and replace, open and save, and so on. But really, PyEdit is a bit more than just another text editor -- it is designed to be used as both a program and a library component, and can be run in a variety of roles:

Standalone mode

As a standalone text editor program, with or without the name of a file to be edited passed in on the command line. In this mode, PyEdit is roughly like other text-editing utility programs (e.g., Notepad on Windows), but also provides advanced functions such as running Python program code being edited, changing fonts and colors, and so on. More importantly, because it is coded in Python, PyEdit is easy to customize, and runs portably on Windows, X Windows, and Macintosh.

Pop-up mode

Within a new pop-up window, allowing an arbitrary number of copies to appear as pop-ups at once in a program. Because state information is stored in class instance attributes, each PyEdit object created operates independently. In this mode and the next, PyEdit serves as a library object for use in other scripts, not a canned application.

Embedded mode

As an attached component, to provide a text- editing widget for other GUIs. When attached, PyEdit uses a frame-based menu, and can optionally disable some of its menu options for an embedded role. For instance, PyView (later in this chapter) uses PyEdit in embedded mode this way to serve as a note editor for photos, and PyMail (in Chapter 11) attaches it to get an email text editor for free.

While such mixed-mode behavior may sound complicated to implement, most of PyEdit's modes are a natural by-product of coding GUIs with the class-based techniques we've seen in the last three chapters.

9.4.1 Running PyEdit

PyEdit sports lots of features, and the best way to learn how it works is to test drive it for yourself -- it can be run by starting the file textEditor.pyw, or from the PyDemo and PyGadget launcher bars described in the previous chapter (the launchers themselves live in the top level of the book examples directory tree). To give you a sampling of its interfaces, Figure 9-10 shows the main window's default appearance, after opening PyEdit's source code file.

Figure 9-10. PyEdit main window, editing itself
figs/ppy2_0910.gif

The main part of this window is a Text widget object, and if you read the last chapter's coverage of this widget, PyEdit text-editing operations will be familiar. It uses text marks, tags, and indexes, and implements cut-and-paste operations with the system clipboard so that PyEdit can paste data to and from other applications. Both vertical and horizontal scrollbars are cross-linked to the Text widget, to support movement through arbitrary files.

If PyEdit's menu and toolbars look familiar, they should -- it builds the main window with minimal code and appropriate clipping and expansion policies, by mixing in the GuiMaker class we met earlier in this chapter. The toolbar at the bottom contains shortcut buttons for operations I tend to use most often; if my preferences don't match yours, simply change the toolbar list in the source code to show the buttons you want (this is Python, after all). As usual for Tkinter menus, shortcut key combinations can be used to invoke menu options quickly, too -- press Alt plus all the underlined keys of entries along the path to the desired action.

PyEdit pops up a variety of modal and nonmodal dialogs, both standard and custom. Figure 9-11 shows the custom and nonmodal change dialog, along with a standard dialog used to display file statistics.

Figure 9-11. PyEdit with colors, font, and a few pop-ups
figs/ppy2_0911.gif

The main window here has been given new foreground and background colors (with the standard color selection dialog), and a new text font has been selected from a canned list in the script that users can change to suit their preferences (this is Python, after all). The standard file open and save selection dialogs in PyEdit use object-based interfaces to remember the last directory visited, so you don't have to renavigate there every time.

One of the more unique features of PyEdit is that it can actually run Python program code that you are editing. This isn't as hard as it may sound either -- because Python provides built-ins for both compiling and running code strings and launching programs, PyEdit simply has to make the right calls for this to work. For example, it's easy to code a simple-minded Python interpreter in Python (though you need a bit more to handle multiple-line statements), as shown in Example 9-13.

Example 9-13. PP2E\Gui\TextEditor\simpleshell.py
namespace= {}
while 1:
    try:
        line = raw_input('>>> ')      # single line statements only
    except EOFError:
        break
    else:
        exec line in namespace        # or eval(  ) and print result

Depending on the user's preference, PyEdit either does something similar to this to run code fetched from the text widget, or uses the launchmodes module we wrote at the end of Chapter 3 to run the code's file as an independent program. There are a variety of options in both schemes that you can customize as you like (this is Python, after all). See the onRunCode method for details, or simply edit and run some Python code on your own.

Figure 9-12 shows four independently started instances of PyEdit running with a variety of color schemes, sizes, and fonts. This figure also captures two PyEdit torn-off menus (lower right) and the PyEdit help pop-up (upper right). The edit windows' backgrounds are shades of yellow, blue, purple, and orange; use the Tools menu's Pick options to set colors as you like.

Figure 9-12. Multiple PyEdit sessions at work
figs/ppy2_0912.gif

Since these four PyEdit sessions are all editing Python source-coded text, you can run their contents with the Run Code option in the Tools pull-down menu. Code run from files is spawned independently; the standard streams of code run not from a file (i.e., fetched from the text widget itself) are mapped to the PyEdit session's console window. This isn't an IDE by any means; it's just something I added because I found it to be useful. It's nice to run code you're editing without fishing through directories.

One caveat before I turn you loose on the source code: PyEdit does not yet have an Undo button in this release. I don't use such a mode myself, and it's easy to undo cuts and pastes right after you've done them (simply paste back from the clipboard, or cut the pasted and selected text). Adding a general undo option would make for a fun exercise if you are so motivated. An interesting approach may be to subclass either the TextEditor class here or the Tkinter Text class itself. Such a subclass would record text operations on a limited-length list and run calls to back out of each logged operation on demand and in reverse. It could also be used to make PyEdit smarter about knowing when to ask about saving the file before it exits. By adding undo as an external subclass, exiting PyEdit code would not have to be instrumented to keep track of everything it does to the text. This is Python, after all.

9.4.2 PyEdit Source Code

The program in Example 9-14 consists of just two source files -- a .pyw that can be run on Windows to avoid the DOS console streams window pop-up, and a main .py that can be either run or imported. We need both because PyEdit is both script and library, and .pyw files can only be run, not imported (see Chapter 2 if you've forgotten what that implies).

Example 9-14. PP2E\Gui\TextEditor\textEditor.pyw
#################################################################
# run PyEdit without DOS console popup for os.system on Windows;
# at present, ".pyw" files cannot be imported as modules;
# if you want a file to be both a program that launches without
# a dos console box on windows, and be imported from elsewhere,
# use ".py" for the main file and import .py code from a ".pyw";
# execfile('textEditor.py') fails when run from another dir,
# because the current working dir is the dir I'm run from;
#################################################################

import textEditor             # grab .py (or .pyc) file
textEditor.main(  )             # run top-level entry point

The module in Example 9-15 is PyEdit's implementation; the main classes used to start and embed a PyEdit object appear at the end of this file. Study this listing while you experiment with PyEdit, to learn about its features and techniques.

Example 9-15. PP2E\Gui\TextEditor\textEditor.py
################################################################################
# PyEdit 1.1: a Python/Tkinter text file editor and component.
# Uses the Tk text widget, plus GuiMaker menus and toolbar buttons
# to implement a full-featured text editor that can be run as a  
# stand-alone program, and attached as a component to other GUIs.
# Also used by PyMail and PyView to edit mail and image file notes.
################################################################################

Version = '1.1'
from Tkinter        import *               # base widgets, constants
from tkFileDialog   import *               # standard dialogs
from tkMessageBox   import *
from tkSimpleDialog import *
from tkColorChooser import askcolor
from string         import split, atoi
from PP2E.Gui.Tools.guimaker import *      # Frame + menu/toolbar builders

START     = '1.0'                          # index of first char: row=1,col=0
SEL_FIRST = SEL + '.first'                 # map sel tag to index
SEL_LAST  = SEL + '.last'                  # same as 'sel.last'

import sys, os, string
FontScale = 0                              # use bigger font on linux
if sys.platform[:3] != 'win':              # and other non-windows boxes
    FontScale = 3

class TextEditor:                          # mix with menu/toolbar Frame class
    startfiledir = '.'
    ftypes = [('All files',     '*'),                 # for file open dialog
              ('Text files',   '.txt'),               # customize in subclass
              ('Python files', '.py')]                # or set in each instance

    colors = [{'fg':'black',      'bg':'white'},      # color pick list
              {'fg':'yellow',     'bg':'black'},      # first item is default
              {'fg':'white',      'bg':'blue'},       # tailor me as desired
              {'fg':'black',      'bg':'beige'},      # or do PickBg/Fg chooser
              {'fg':'yellow',     'bg':'purple'},
              {'fg':'black',      'bg':'brown'},
              {'fg':'lightgreen', 'bg':'darkgreen'},
              {'fg':'darkblue',   'bg':'orange'},
              {'fg':'orange',     'bg':'darkblue'}]

    fonts  = [('courier',    9+FontScale, 'normal'),  # platform-neutral fonts
              ('courier',   12+FontScale, 'normal'),  # (family, size, style)
              ('courier',   10+FontScale, 'bold'),    # or popup a listbox
              ('courier',   10+FontScale, 'italic'),  # make bigger on linux
              ('times',     10+FontScale, 'normal'),
              ('helvetica', 10+FontScale, 'normal'),
              ('ariel',     10+FontScale, 'normal'),
              ('system',    10+FontScale, 'normal'),
              ('courier',   20+FontScale, 'normal')]

    def __init__(self, loadFirst=''):
        if not isinstance(self, GuiMaker):
            raise TypeError, 'TextEditor needs a GuiMaker mixin'
        self.setFileName(None)
        self.lastfind   = None
        self.openDialog = None
        self.saveDialog = None
        self.text.focus(  )                          # else must click in text
        if loadFirst: 
            self.onOpen(loadFirst)
 
    def start(self):                               # run by GuiMaker.__init__
        self.menuBar = [                           # configure menu/toolbar
            ('File', 0, 
                 [('Open...',    0, self.onOpen),
                  ('Save',       0, self.onSave),
                  ('Save As...', 5, self.onSaveAs),
                  ('New',        0, self.onNew),
                  'separator',
                  ('Quit...',    0, self.onQuit)]
            ),
            ('Edit', 0,
                 [('Cut',        0, self.onCut),
                  ('Copy',       1, self.onCopy),
                  ('Paste',      0, self.onPaste),
                  'separator',
                  ('Delete',     0, self.onDelete),
                  ('Select All', 0, self.onSelectAll)]
            ),
            ('Search', 0,
                 [('Goto...',    0, self.onGoto),
                  ('Find...',    0, self.onFind),
                  ('Refind',     0, self.onRefind),
                  ('Change...',  0, self.onChange)]
            ),
            ('Tools', 0,
                 [('Font List',   0, self.onFontList),
                  ('Pick Bg...',  4, self.onPickBg),
                  ('Pick Fg...',  0, self.onPickFg),
                  ('Color List',  0, self.onColorList),
                 'separator',
                  ('Info...',    0, self.onInfo),
                  ('Clone',      1, self.onClone),
                  ('Run Code',   0, self.onRunCode)]
            )]
        self.toolBar = [
            ('Save',  self.onSave,   {'side': LEFT}),
            ('Cut',   self.onCut,    {'side': LEFT}),
            ('Copy',  self.onCopy,   {'side': LEFT}),
            ('Paste', self.onPaste,  {'side': LEFT}),
            ('Find',  self.onRefind, {'side': LEFT}),
            ('Help',  self.help,     {'side': RIGHT}),
            ('Quit',  self.onQuit,   {'side': RIGHT})]

    def makeWidgets(self):                          # run by GuiMaker.__init__
        name = Label(self, bg='black', fg='white')  # add below menu, above tool
        name.pack(side=TOP, fill=X)                 # menu/toolbars are packed

        vbar  = Scrollbar(self)  
        hbar  = Scrollbar(self, orient='horizontal')
        text  = Text(self, padx=5, wrap='none') 

        vbar.pack(side=RIGHT,  fill=Y)
        hbar.pack(side=BOTTOM, fill=X)                 # pack text last
        text.pack(side=TOP,    fill=BOTH, expand=YES)  # else sbars clipped

        text.config(yscrollcommand=vbar.set)    # call vbar.set on text move
        text.config(xscrollcommand=hbar.set)
        vbar.config(command=text.yview)         # call text.yview on scroll move
        hbar.config(command=text.xview)         # or hbar['command']=text.xview

        text.config(font=self.fonts[0], 
                    bg=self.colors[0]['bg'], fg=self.colors[0]['fg'])
        self.text = text
        self.filelabel = name

    #####################
    # Edit menu commands
    #####################

    def onCopy(self):                           # get text selected by mouse,etc
        if not self.text.tag_ranges(SEL):       # save in cross-app clipboard
            showerror('PyEdit', 'No text selected')
        else:
            text = self.text.get(SEL_FIRST, SEL_LAST)  
            self.clipboard_clear(  )              
            self.clipboard_append(text)

    def onDelete(self):                         # delete selected text, no save
        if not self.text.tag_ranges(SEL):
            showerror('PyEdit', 'No text selected')
        else:
            self.text.delete(SEL_FIRST, SEL_LAST)

    def onCut(self):
        if not self.text.tag_ranges(SEL):
            showerror('PyEdit', 'No text selected')
        else: 
            self.onCopy(  )                       # save and delete selected text
            self.onDelete(  )

    def onPaste(self):
        try:
            text = self.selection_get(selection='CLIPBOARD')
        except TclError:
            showerror('PyEdit', 'Nothing to paste')
            return
        self.text.insert(INSERT, text)          # add at current insert cursor
        self.text.tag_remove(SEL, '1.0', END) 
        self.text.tag_add(SEL, INSERT+'-%dc' % len(text), INSERT)
        self.text.see(INSERT)                   # select it, so it can be cut

    def onSelectAll(self):
        self.text.tag_add(SEL, '1.0', END+'-1c')   # select entire text 
        self.text.mark_set(INSERT, '1.0')          # move insert point to top
        self.text.see(INSERT)                      # scroll to top

    ######################
    # Tools menu commands 
    ######################

    def onFontList(self):
        self.fonts.append(self.fonts[0])           # pick next font in list
        del self.fonts[0]                          # resizes the text area
        self.text.config(font=self.fonts[0]) 

    def onColorList(self):
        self.colors.append(self.colors[0])         # pick next color in list
        del self.colors[0]                         # move current to end
        self.text.config(fg=self.colors[0]['fg'], bg=self.colors[0]['bg']) 

    def onPickFg(self): 
        self.pickColor('fg')                       # added on 10/02/00
    def onPickBg(self):                            # select arbitrary color
        self.pickColor('bg')                       # in standard color dialog
    def pickColor(self, part):                     # this is way too easy
        (triple, hexstr) = askcolor(  )
        if hexstr:
            apply(self.text.config, (  ), {part: hexstr})

    def onInfo(self):
        text  = self.getAllText(  )                  # added on 5/3/00 in 15 mins
        bytes = len(text)                          # words uses a simple guess: 
        lines = len(string.split(text, '\n'))      # any separated by whitespace
        words = len(string.split(text)) 
        index = self.text.index(INSERT)
        where = tuple(string.split(index, '.'))
        showinfo('PyEdit Information',
                 'Current location:\n\n' +
                 'line:\t%s\ncolumn:\t%s\n\n' % where +
                 'File text statistics:\n\n' +
                 'bytes:\t%d\nlines:\t%d\nwords:\t%d\n' % (bytes, lines, words))

    def onClone(self):
        new = Toplevel(  )                # a new edit window in same process
        myclass = self.__class__        # instance's (lowest) class object
        myclass(new)                    # attach/run instance of my class

    def onRunCode(self, parallelmode=1):
        """
        run Python code being edited--not an ide, but handy;
        tries to run in file's dir, not cwd (may be pp2e root);
        inputs and adds command-line arguments for script files;
        code's stdin/out/err = editor's start window, if any;
        but parallelmode uses start to open a dos box for i/o;
        """
        from PP2E.launchmodes import System, Start, Fork
        filemode = 0
        thefile  = str(self.getFileName(  ))
        cmdargs  = askstring('PyEdit', 'Commandline arguments?') or ''
        if os.path.exists(thefile):
            filemode = askyesno('PyEdit', 'Run from file?')
        if not filemode:                                    # run text string
            namespace = {'__name__': '__main__'}            # run as top-level
            sys.argv = [thefile] + string.split(cmdargs)    # could use threads
            exec self.getAllText(  ) + '\n' in namespace      # exceptions ignored
        elif askyesno('PyEdit', 'Text saved in file?'):
            mycwd = os.getcwd(  )                             # cwd may be root
            os.chdir(os.path.dirname(thefile) or mycwd)     # cd for filenames
            thecmd  = thefile + ' ' + cmdargs
            if not parallelmode:                            # run as file
                System(thecmd, thecmd)(  )                    # block editor
            else:
                if sys.platform[:3] == 'win':               # spawn in parallel
                    Start(thecmd, thecmd)(  )                 # or use os.spawnv
                else:
                    Fork(thecmd, thecmd)(  )                  # spawn in parallel
            os.chdir(mycwd)

    #######################
    # Search menu commands
    #######################
 
    def onGoto(self):
        line = askinteger('PyEdit', 'Enter line number')
        self.text.update(  ) 
        self.text.focus(  )
        if line is not None:
            maxindex = self.text.index(END+'-1c')
            maxline  = atoi(split(maxindex, '.')[0])
            if line > 0 and line <= maxline:
                self.text.mark_set(INSERT, '%d.0' % line)      # goto line
                self.text.tag_remove(SEL, '1.0', END)          # delete selects
                self.text.tag_add(SEL, INSERT, 'insert + 1l')  # select line
                self.text.see(INSERT)                          # scroll to line
            else:
                showerror('PyEdit', 'Bad line number')

    def onFind(self, lastkey=None):
        key = lastkey or askstring('PyEdit', 'Enter search string')
        self.text.update(  )
        self.text.focus(  )
        self.lastfind = key
        if key:
            where = self.text.search(key, INSERT, END)        # don't wrap
            if not where:
                showerror('PyEdit', 'String not found')
            else:
                pastkey = where + '+%dc' % len(key)           # index past key
                self.text.tag_remove(SEL, '1.0', END)         # remove any sel
                self.text.tag_add(SEL, where, pastkey)        # select key 
                self.text.mark_set(INSERT, pastkey)           # for next find
                self.text.see(where)                          # scroll display

    def onRefind(self):
        self.onFind(self.lastfind)

    def onChange(self):
        new = Toplevel(self)
        Label(new, text='Find text:').grid(row=0, column=0)
        Label(new, text='Change to:').grid(row=1, column=0)
        self.change1 = Entry(new)
        self.change2 = Entry(new)
        self.change1.grid(row=0, column=1, sticky=EW)
        self.change2.grid(row=1, column=1, sticky=EW)
        Button(new, text='Find',  
               command=self.onDoFind).grid(row=0, column=2, sticky=EW)
        Button(new, text='Apply', 
               command=self.onDoChange).grid(row=1, column=2, sticky=EW)
        new.columnconfigure(1, weight=1)    # expandable entrys

    def onDoFind(self):
        self.onFind(self.change1.get(  ))                    # Find in change box

    def onDoChange(self):
        if self.text.tag_ranges(SEL):                      # must find first
            self.text.delete(SEL_FIRST, SEL_LAST)          # Apply in change
            self.text.insert(INSERT, self.change2.get(  ))   # deletes if empty
            self.text.see(INSERT)
            self.onFind(self.change1.get(  ))                # goto next appear
            self.text.update(  )                             # force refresh

    #####################
    # File menu commands
    #####################

    def my_askopenfilename(self):      # objects remember last result dir/file
        if not self.openDialog:
           self.openDialog = Open(initialdir=self.startfiledir, 
                                  filetypes=self.ftypes)
        return self.openDialog.show(  )

    def my_asksaveasfilename(self):    # objects remember last result dir/file
        if not self.saveDialog:
           self.saveDialog = SaveAs(initialdir=self.startfiledir, 
                                    filetypes=self.ftypes)
        return self.saveDialog.show(  )
        
    def onOpen(self, loadFirst=''):
        doit = self.isEmpty(  ) or askyesno('PyEdit', 'Disgard text?')
        if doit:
            file = loadFirst or self.my_askopenfilename(  )
            if file:
                try:
                    text = open(file, 'r').read(  )
                except:
                    showerror('PyEdit', 'Could not open file ' + file)
                else:
                    self.setAllText(text)
                    self.setFileName(file)

    def onSave(self):
        self.onSaveAs(self.currfile)  # may be None

    def onSaveAs(self, forcefile=None):
        file = forcefile or self.my_asksaveasfilename(  )
        if file:
            text = self.getAllText(  )
            try:
                open(file, 'w').write(text)
            except:
                showerror('PyEdit', 'Could not write file ' + file)
            else:
                self.setFileName(file)         # may be newly created

    def onNew(self):
        doit = self.isEmpty(  ) or askyesno('PyEdit', 'Disgard text?')
        if doit:
            self.setFileName(None)
            self.clearAllText(  )

    def onQuit(self):
        if askyesno('PyEdit', 'Really quit PyEdit?'):
            self.quit(  )                        # Frame.quit via GuiMaker

    ####################################
    # Others, useful outside this class
    ####################################

    def isEmpty(self):
        return not self.getAllText(  ) 

    def getAllText(self):
        return self.text.get('1.0', END+'-1c')  # extract text as a string

    def setAllText(self, text):
        self.text.delete('1.0', END)            # store text string in widget
        self.text.insert(END, text)             # or '1.0'
        self.text.mark_set(INSERT, '1.0')       # move insert point to top 
        self.text.see(INSERT)                   # scroll to top, insert set

    def clearAllText(self):
        self.text.delete('1.0', END)            # clear text in widget 

    def getFileName(self):
        return self.currfile

    def setFileName(self, name):
        self.currfile = name  # for save
        self.filelabel.config(text=str(name))

    def help(self):
        showinfo('About PyEdit', 
                 'PyEdit version %s\nOctober, 2000\n\n'
                 'A text editor program\nand object component\n'
                 'written in Python/Tk.\nProgramming Python 2E\n'
                 "O'Reilly & Associates" % Version)


##################################################################
# ready-to-use editor classes 
# mix in a Frame subclass that builds menu/toolbars
##################################################################


# when editor owns the window 

class TextEditorMain(TextEditor, GuiMakerWindowMenu):  # add menu/toolbar maker 
    def __init__(self, parent=None, loadFirst=''):     # when fills whole window
        GuiMaker.__init__(self, parent)                # use main window menus
        TextEditor.__init__(self, loadFirst)           # self has GuiMaker frame
        self.master.title('PyEdit ' + Version)         # title if stand-alone
        self.master.iconname('PyEdit')                 # catch wm delete button
        self.master.protocol('WM_DELETE_WINDOW', self.onQuit)

class TextEditorMainPopup(TextEditor, GuiMakerWindowMenu):
    def __init__(self, parent=None, loadFirst=''):     
        self.popup = Toplevel(parent)                  # create own window
        GuiMaker.__init__(self, self.popup)            # use main window menus
        TextEditor.__init__(self, loadFirst) 
        assert self.master == self.popup
        self.popup.title('PyEdit ' + Version) 
        self.popup.iconname('PyEdit')               
    def quit(self):
        self.popup.destroy(  )                           # kill this window only


# when embedded in another window

class TextEditorComponent(TextEditor, GuiMakerFrameMenu):     
    def __init__(self, parent=None, loadFirst=''):     # use Frame-based menus
        GuiMaker.__init__(self, parent)                # all menus, buttons on
        TextEditor.__init__(self, loadFirst)           # GuiMaker must init 1st

class TextEditorComponentMinimal(TextEditor, GuiMakerFrameMenu): 
    def __init__(self, parent=None, loadFirst='', deleteFile=1):   
        self.deleteFile = deleteFile
        GuiMaker.__init__(self, parent)             
        TextEditor.__init__(self, loadFirst) 
    def start(self):   
        TextEditor.start(self)                         # GuiMaker start call
        for i in range(len(self.toolBar)):             # delete quit in toolbar
            if self.toolBar[i][0] == 'Quit':           # delete file menu items
                del self.toolBar[i]; break             # or just disable file
        if self.deleteFile:
            for i in range(len(self.menuBar)):
                if self.menuBar[i][0] == 'File':
                    del self.menuBar[i]; break
        else:
            for (name, key, items) in self.menuBar:
                if name == 'File':
                    items.append([1,2,3,4,6]) 


# stand-alone program run
                                                     
def testPopup(  ):     
    # see PyView and PyMail for component tests
    root = Tk(  )
    TextEditorMainPopup(root)
    TextEditorMainPopup(root)
    Button(root, text='More', command=TextEditorMainPopup).pack(fill=X)
    Button(root, text='Quit', command=root.quit).pack(fill=X)
    root.mainloop(  )

def main(  ):                                           # may be typed or clicked
    try:                                              # or associated on Windows
        fname = sys.argv[1]                           # arg = optional filename
    except IndexError:
        fname = None
    TextEditorMain(loadFirst=fname).pack(expand=YES, fill=BOTH)
    mainloop(  )

if __name__ == '__main__':                            # when run as a script
    #testPopup(  )
    main(  )                                            # run .pyw for no dos box

    I l@ve RuBoard Previous Section Next Section