Section 12.2. PyEdit: A Text Editor ProgramObject


12.2. 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 600 lines of portable Python code (including whitespace, comments, and configuration settings). Despite its size, PyEdit was sufficiently powerful and robust to serve as the primary tool used to code most of the examples in this book.

PyEdit supports all the usual mouse and keyboard text-editing operations: cut and paste, search and replace, open and save, undo and redo, and so on. But really, PyEdit is a bit more than just another text editorit is designed to be used as both a program and a library component, and it 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 it also provides advanced functions such as running Python program code being edited, changing fonts and colors, and so on. More important, because it is coded in Python, PyEdit is easy to customize, and it 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 as 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 PyMailGUI (in Chapter 15) 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 byproduct of coding GUIs with the class-based techniques we've seen in the last three chapters.

12.2.1. Running PyEdit

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

Figure 12-1. PyEdit main window, editing itself


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

12.2.1.1. Menus and toolbars

If PyEdit's menu and toolbars look familiar, they shouldit builds the main window with minimal code and appropriate clipping and expansion policies, by mixing in the GuiMaker class we coded in the prior 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 toopress Alt plus all the underlined keys of entries along the path to the desired action. Menus can also be torn off at their dashed line to provide quick access to menu options in new top-level windows (handy for options without toolbar buttons).

12.2.1.2. Dialogs

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

Figure 12-2. PyEdit with colors, a font, and a few pop ups


The main window in Figure 12-2 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 navigate there every time.

12.2.1.3. Running program code

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 eitherbecause Python provides built-ins for compiling and running code strings and for 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 12-1.

Example 12-1. PP3E\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 5 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. When edited code is run in nonfile mode, you can view its printed output in PyEdit's console window.

Figure 12-3 shows three 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 blue, green, and red; use the Tools menu's Pick options to set colors as you like.

Figure 12-3. Multiple PyEdit sessions at work


Since these three PyEdit sessions are 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.

12.2.1.4. New features in version 2.0

New for this edition of the book is a font input dialoga simple, three-entry, nonmodal dialog where you can type the font family, size, and style, instead of picking them from a list of preset options. (You can find more sophisticated Tk font selection dialogs in both the public domain and within the implementation of Python's standard IDLE development GUIas mentioned earlier, it is itself a Python/Tkinter program.)

Also new in this edition, PyEdit supports unlimited edit undo and redo, as well as an accurate modified check before quit, open, and new actions to prompt for saves (instead of always asking naïvely). The underlying Tk 8.4 library provides an API, which makes this simpleTk keeps undo and redo stacks automatically. They are enabled with the Text widget's undo configuration option and are accessed with the widget methods edit_undo and edit_redo. Similarly, edit_reset clears the stacks (e.g., after a new file open), and edit_modified checks or sets the automatic text modified flag.

It's also possible to undo cuts and pastes right after you've done them (simply paste back from the clipboard or cut the pasted and selected text), but the new undo/redo operations are more complete and simpler to use. Undo was a suggested exercise in the prior edition of this book, but it has been made almost trivial by the new Tk API.

For usability, this edition's version of PyEdit also allows users to set startup configuration options by assigning variables in a module, textConfig.py. If present, these assignments give initial values for font, colors, text window size, and search case sensitivity. Fonts and colors can be changed in the menus and windows can be freely resized, so this is largely just a convenience. Also note that this module's settings will be inherited by all instances of PyEditeven when it is a pop-up window or an embedded component of another application. Client applications should configure per their needs.

12.2.2. PyEdit Source Code

The PyEdit program consists of just a small configuration module and one main source filea .py that can be either run or imported. For use on Windows, there is also a one-line .pyw file that just executes the .py file's contents with an execfile('textEditor.py') call. The .pyw suffix avoids the DOS console streams window pop up when launched on Windows.

Today, .pyw files can be both imported and run, like normal .py files (they can also be double-clicked, and launched by Python tools such as os.system and os.startfile), so we don't really need a separate file to support both import and console-less run modes. I retained the .py, though, in order to see printed text during development and to use PyEdit as a simple IDEwhen the run code option is selected, in nonfile mode printed output from code being edited shows up in PyEdit's DOS console window in Windows. Clients will normally import the .py file.

First, PyEdit's user configuration module is listed in Example 12-2. As mentioned, this is mostly a convenience, for providing an initial look-and-feel other than the default. PyEdit is coded to work even if this module is missing or contains syntax errors.

Example 12-2. PP3E\Gui\TextEditor\textConfig.py

 ############################################################# # PyEdit (testEditor.py) user startup configuration module # comment-out any of these to accept Tk or program defaults # can also change font/colors from menus, and resize window ############################################################# # initial font                      # family, size, style font = ('courier', 9, 'normal')     # e.g., style: 'bold italic' # initial color                     # default=white, black bg = 'lightcyan'                    # colorname or RGB hexstr fg = 'black'                        # e.g., 'powder blue', '#690f96' # initial size height = 20                         # Tk default: 24 lines width  = 80                         # Tk default: 80 characters # search case-insensitive caseinsens = 1                      # default=1 (on) 

Next, Example 12-3 gives the .pyw launching file used to suppress a DOS pop up on Windows, but still allow for it when the .py file is run directly (to see the output of edited code run in nonfile mode, for example).

Example 12-3. PP3E\Gui\TextEditor\textEditorNoConsole.pyw

 ######################################################################## # run without a DOS pop up on Windows # could use just a .pyw for both inports and launch, # but .py file retained for seeing any printed text ######################################################################## execfile('textEditor.py')     # as if pasted here (or textEditor.main( )) 

And finally, the module in Example 12-4 is PyEdit's implementation. This file may run directly as a top-level script, or it can be imported from other applications. Its code is organized by the GUI's main menu options. 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 12-4. PP3E\Gui\TextEditor\textEditor.py

 ################################################################################ # PyEdit 2.0: 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 standalone # program, and attached as a component to other GUIs.  Also used by # PyMailGUI and PyView to edit mail text and image file notes, and # by PyMailGUI and PyDemos2 in pop-up mode to display source files. # # New in 2.0: # -added simple font components input dialog # -use Tk 8.4 undo stack API to add undo, redo modified test # -now verifies on quit, open, new, run, only if text modified and unsaved # -searches are case-insensitive now # -configuration module for initial font/color/size/searchcase # TBD: could also allow search case choice in GUI, and could use regexps. ################################################################################ Version = '2.0' import sys, os                             # platform, args, run tools from Tkinter        import *               # base widgets, constants from tkFileDialog   import Open, SaveAs    # standard dialogs from tkMessageBox   import showinfo, showerror, askyesno from tkSimpleDialog import askstring, askinteger from tkColorChooser import askcolor from PP2E.Gui.Tools.guimaker import *      # Frame + menu/toolbar builders try:     import textConfig                      # startup font and colors     configs = textConfig._ _dict_ _            # work if not on the path or bad except:     configs = {} helptext = """PyEdit version %s January, 2006 (1.0: October, 2000) Programming Python, 3rd Edition O'Reilly Media, Inc. A text editor program and embeddable object component, written in Python/Tkinter.  Use menu tear-offs and toolbar for quick access to actions, and Alt-key shortcuts for menus. New in version %s: - font pick dialog - unlimited undo/redo - quit/open/new/run prompt save only if changed - searches are case-insensitive - startup configuration module textConfig.py """ 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' FontScale = 0                              # use bigger font on Linux if sys.platform[:3] != 'win':              # and other non-Windows boxes     FontScale = 3 ################################################################################ # Main class: implements editor GUI, actions ################################################################################ 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 pop up a listbox               ('courier',   10+FontScale, 'italic'),  # make bigger on Linux               ('times',     10+FontScale, 'normal'),  # use 'bold italic' for 2               ('helvetica', 10+FontScale, 'normal'),  # also 'underline', etc.               ('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,                             # a GuiMaker menu def tree                  [('Open...',    0, self.onOpen),   # build in method for self                   ('Save',       0, self.onSave),   # label, shortcut, callback                   ('Save As...', 5, self.onSaveAs),                   ('New',        0, self.onNew),                   'separator',                   ('Quit...',    0, self.onQuit)]             ),             ('Edit', 0,                  [('Undo',       0, self.onUndo),                   ('Redo',       0, self.onRedo),                   'separator',                   ('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,                  [('Pick Font...', 6, self.onPickFont),                   ('Font List',    0, self.onFontList),                  'separator',                   ('Pick Bg...',   3, 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')         text.config(undo=1, autoseparators=1)          # 2.0, default is 0, 1         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         # 2.0: apply user configs or defaults         startfont = configs.get('font', self.fonts[0])         startbg   = configs.get('bg',   self.colors[0]['bg'])         startfg   = configs.get('fg',   self.colors[0]['fg'])         text.config(font=startfont, bg=startbg, fg=startfg)         if 'height' in configs: text.config(height=configs['height'])         if 'width'  in configs: text.config(width =configs['width'])         self.text = text         self.filelabel = name     ############################################################################     # 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 = (not self.text_edit_modified( ) or      # 2.0                 askyesno('PyEdit', 'Text has changed: discard changes?'))         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)                     self.text.edit_reset( )        # 2.0: clear undo/redo stks                     self.text.edit_modified(0)      # 2.0: clear modified flag     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                 self.text.edit_modified(0)         # 2.0: clear modified flag                                                    # don't clear undo/redo stks     def onNew(self):         doit = (not self.text_edit_modified( ) or   # 2.0                 askyesno('PyEdit', 'Text has changed: discard changes?'))         if doit:             self.setFileName(None)             self.clearAllText( )             self.text.edit_reset( )                 # 2.0: clear undo/redo stks             self.text.edit_modified(0)               # 2.0: clear modified flag     def onQuit(self):         doit = (not self.text_edit_modified( )      # 2.0                 or askyesno('PyEdit',                             'Text has changed: quit and discard changes?'))         if doit:             self.quit( )                            # Frame.quit via GuiMaker     def text_edit_modified(self):         """         2.0: self.text.edit_modified( ) broken in Python 2.4:         do manually for now (seems to be bool result type bug)         """         return self.tk.call((self.text._w, 'edit') + ('modified', None))     ############################################################################     # Edit menu commands     ############################################################################     def onUndo(self):                           # 2.0         try:                                    # tk8.4 keeps undo/redo stacks             self.text.edit_undo( )                   # exception if stacks empty         except TclError:                        # menu tear-offs for quick undo             showinfo('PyEdit', 'Nothing to undo')     def onRedo(self):                           # 2.0: redo an undone         try:             self.text.edit_redo( )         except TclError:             showinfo('PyEdit', 'Nothing to redo')     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     ############################################################################     # Search menu commands     ############################################################################     def onGoto(self, forceline=None):         line = forceline or askinteger('PyEdit', 'Enter line number')         self.text.update( )         self.text.focus( )         if line is not None:             maxindex = self.text.index(END+'-1c')             maxline  = int(maxindex.split('.')[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:                                                    # 2.0: nocase             nocase = configs.get('caseinsens', 1)                  # 2.0: config             where = self.text.search(key, INSERT, END, nocase=nocase)             if not where:                                          # don't wrap                 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 entries     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     ############################################################################     # 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 too easy         (triple, hexstr) = askcolor( )         if hexstr:             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(text.split('\n'))               # any separated by whitespace         words = len(text.split( ))         index = self.text.index(INSERT)         where = tuple(index.split('.'))         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=True):         """         run Python code being edited--not an IDE, but handy;         tries to run in file's dir, not cwd (may be PP3E root);         inputs and adds command-line arguments for script files;         code's stdin/out/err = editor's start window, if any:         run with a console window to see code's print outputs;         but parallelmode uses start to open a DOS box for I/O;         module search path will include '.' dir where started;         in non-file mode, code's Tk root window is PyEdit win;         """         def askcmdargs( ):             return askstring('PyEdit', 'Commandline arguments?') or ''         from PP3E.launchmodes import System, Start, Fork         filemode = False         thefile  = str(self.getFileName( ))         if os.path.exists(thefile):             filemode = askyesno('PyEdit', 'Run from file?')         if not filemode:                                    # run text string             cmdargs   = askcmdargs( )             namespace = {'_ _name_ _': '_ _main_ _'}           # run as top-level             sys.argv  = [thefile] + cmdargs.split( )         # could use threads             exec self.getAllText( ) + '\n' in namespace      # exceptions ignored         elif self.text_edit_modified( ):                     # 2.0: changed test             showerror('PyEdit', 'Text changed: save before run')         else:             cmdargs  = askcmdargs( )             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)     def onPickFont(self):         # 2.0 font spec dialog         new = Toplevel(self)         Label(new, text='Family:').grid(row=0, column=0)      # nonmodal dialog         Label(new, text='Size:  ').grid(row=1, column=0)      # see pick list         Label(new, text='Style: ').grid(row=2, column=0)      # for valid inputs         self.font1 = Entry(new)         self.font2 = Entry(new)         self.font3 = Entry(new)         self.font1.insert(0, 'courier')                       # suggested vals         self.font2.insert(0, '12')         self.font3.insert(0, 'bold italic')         self.font1.grid(row=0, column=1, sticky=EW)         self.font2.grid(row=1, column=1, sticky=EW)         self.font3.grid(row=2, column=1, sticky=EW)         Button(new, text='Apply',                command=self.onDoFont).grid(row=3, columnspan=2)         new.columnconfigure(1, weight=1)    # expandable entrys     def onDoFont(self):         try:             font = (self.font1.get(), int(self.font2.get()), self.font3.get( ))             self.text.config(font=font)         except:             showerror('PyEdit', 'Bad font specification')     ############################################################################     # Utilities, 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):                # also: onGoto(linenum)         self.currfile = name  # for save         self.filelabel.config(text=str(name))     def setBg(self, color):         self.text.config(bg=color)              # to set manually from code     def setFg(self, color):         self.text.config(fg=color)              # 'black', hexstring     def setFont(self, font):         self.text.config(font=font)             # ('family', size, 'style')     def setHeight(self, lines):                 # default = 24h x 80w         self.text.config(height=lines)          # may also be from textCongif.py     def setWidth(self, chars):         self.text.config(width=chars)     def clearModified(self):         self.text.edit_modified(0)              # clear modified flag     def isModified(self):         return self.text_edit_modified( )        # changed since last reset?     def help(self):         showinfo('About PyEdit', helptext % ((Version,)*2)) ################################################################################ # 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 standalone         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='', winTitle=''):         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 + winTitle)         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]) ################################################################################ # standalone 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 




Programming Python
Programming Python
ISBN: 0596009259
EAN: 2147483647
Year: 2004
Pages: 270
Authors: Mark Lutz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net