GuiStreams: Redirecting Streams to Widgets

The script in Example 11-9 arranges to map input and output sources to pop-up windows in a GUI application, much as we did with strings in the stream redirection topics in Chapter 3. Although this module is really just a first-cut prototype and needs improvement itself (e.g., each input line request pops up a new input dialog), it demonstrates the concepts in general.

Its GuiOutput and GuiInput objects define methods that allow them to masquerade as files in any interface that expects a real file. As we learned earlier in Chapter 3, this includes standard stream processing tools, such as print and raw_input, and it includes explicit read and write calls. The two top-level interfaces in this module handle common use cases:

  • The redirectedGuiFunc function uses this plug-and-play file compatibility to run a function with its standard input and output streams mapped completely to pop-up windows rather than to the console window (or wherever streams would otherwise be mapped in the system shell).

  • The redirectedGuiShellCmd function similarly routes the output of a spawned shell command line to a pop-up window. It can be used to display the output of any program in a GUI—including that printed by a Python program.

The module’s GuiInput and GuiOutput classes can also be used or customized directly by clients that need more fine-grained control over the process.

Example 11-9. PP3EGuiToolsguiStreams.py

##############################################################################
# first-cut implementation of file-like classes that can be used to redirect
# input and output streams to GUI displays; as is, input comes from a common
# dialog pop-up (a single output+input interface or a persistent Entry field
# for input would be better); this also does not properly span lines for read
# requests with a byte count > len(line); see guiStreamsTools.py for more;
##############################################################################

from Tkinter import *
from ScrolledText import ScrolledText
from tkSimpleDialog import askstring

class GuiOutput:
    font = ('courier', 9, 'normal')              # in class for all, self for one
    def _ _init_ _(self, parent=None):
        self.text = None
        if parent: self.popupnow(parent)         # pop up now or on first write
    def popupnow(self, parent=None):             # in parent now, Toplevel later
        if self.text: return
        self.text = ScrolledText(parent or Toplevel( ))
        self.text.config(font=self.font)
        self.text.pack( )
    def write(self, text):
        self.popupnow( )
        self.text.insert(END, str(text))
        self.text.see(END)
        self.text.update( )
    def writelines(self, lines):                 # lines already have '
'
        for line in lines: self.write(line)      # or map(self.write, lines)

class GuiInput:
    def _ _init_ _(self):
        self.buff = ''
    def inputLine(self):
        line = askstring('GuiInput', 'Enter input line + <crlf> (cancel=eof)')
        if line == None:
            return ''                            # pop-up dialog for each line
        else:                                    # cancel button means eof
            return line + '
'                   # else add end-line marker
    def read(self, bytes=None):
        if not self.buff:
            self.buff = self.inputLine( )
        if bytes:                                # read by byte count
            text = self.buff[:bytes]             # doesn't span lines
            self.buff = self.buff[bytes:]
        else:
            text = ''                            # read all till eof
            line = self.buff
            while line:
                text = text + line
                line = self.inputLine( )          # until cancel=eof=''
        return text
    def readline(self):
        text = self.buff or self.inputLine( )     # emulate file read methods
        self.buff = ''
        return text
    def readlines(self):
        lines = []                               # read all lines
        while 1:
            next = self.readline( )
            if not next: break
            lines.append(next)
        return lines

def redirectedGuiFunc(func, *pargs, **kargs):
    import sys
    saveStreams = sys.stdin, sys.stdout          # map func streams to pop ups
    sys.stdin   = GuiInput( )                        # pops up dialog as needed
    sys.stdout  = GuiOutput( )                      # new output window per call
    sys.stderr  = sys.stdout
    result = func(*pargs, **kargs)               # this is a blocking call
    sys.stdin, sys.stdout = saveStreams
    return result

def redirectedGuiShellCmd(command):
    import os
    input  = os.popen(command, 'r')
    output = GuiOutput( )
    def reader(input, output):                   # show a shell command's
        while True:                              # standard output in a new
            line = input.readline( )                 # pop-up text box widget;
            if not line: break                   # the readline call may block
            output.write(line)
    reader(input, output)

if _ _name_ _ == '_ _main_ _':
    def makeUpper( ):                             # use standard streams
        while 1:
            try:
                line = raw_input('Line? ')
            except:
                break
            print line.upper( )
        print 'end of file'

    def makeLower(input, output):                # use explicit files
        while 1:
            line = input.readline( )
            if not line: break
            output.write(line.lower( ))
        print 'end of file'

    root = Tk( )
    Button(root, text='test streams',
           command=lambda: redirectedGuiFunc(makeUpper)).pack(fill=X)
    Button(root, text='test files  ',
           command=lambda: makeLower(GuiInput(), GuiOutput( )) ).pack(fill=X)
    Button(root, text='test popen  ',
           command=lambda: redirectedGuiShellCmd('dir *')).pack(fill=X)
    root.mainloop( )

As coded here, GuiOutput either attaches a ScrolledText to a parent container or pops up a new top-level window to serve as the container on the first write call. GuiInput pops up a new standard input dialog every time a read request requires a new line of input. Neither one of these policies is ideal for all scenarios (input would be better mapped to a more long-lived widget), but they prove the general point. Figure 11-8 shows the scene generated by this script’s self-test code, after capturing the output of a shell dir listing command (on the left) and two interactive loop tests (the one with “Line?” prompts and uppercase letters represents the makeUpper streams test). An input dialog has just popped up for a new makeLower files test.

guiStreams routing streams to pop-up windows

Figure 11-8. guiStreams routing streams to pop-up windows

Before we move on, we should note that this module’s calls to a redirected function as well as its loop that reads from a spawned shell command are potentially blocking—they won’t return to the GUI’s event loop until the function or shell command exits. In redirectedGuiShellCmd, for example, the call to input.readline will pause until input is received from the spawned program, rendering the GUI unresponsive. Because the output object runs an update call, the display is still updated during the pause (an update call enters the Tk event loop momentarily). This blocking model is simplistic, though, and might be an issue in a larger GUI. We’ll revisit this later in the chapter when we meet threads. For now, the code suits our present purpose.

Using Redirection for the Packing Scripts

Now, to use such redirection tools to map command-line script output back to a GUI, we simply run calls and command lines with the two redirected functions in this module. Example 11-10 shows one way to wrap the packing operation to force its printed output to appear in a pop-up window when generated, instead of in the console.

Example 11-10. PP3EGuiShellGuipackdlg-redirect.py

# wrap command-line script in GUI redirection tool to pop p its output

from Tkinter import *
from packdlg import runPackDialog
from PP3E.Gui.Tools.guiStreams import redirectedGuiFunc
def runPackDialog_Wrapped( ):
    redirectedGuiFunc(runPackDialog)    # wrap entire callback handler

if _ _name_ _ == '_ _main_ _':
    root = Tk( )
    Button(root, text='pop', command=runPackDialog_Wrapped).pack(fill=X)
    root.mainloop( )

You can run this script directly to test its effect, without bringing up the ShellGui window. Figure 11-9 shows the resulting stdout window after the pack input dialog is dismissed. This window pops up as soon as script output is generated, and it is a bit more GUI user friendly than hunting for messages in a console. You can similarly code the unpack parameters dialog to route its output to a pop-up.[*] In fact, you can use this technique to route the output of any function call or command line to a pop-up window; as usual, the notion of compatible object interfaces is at the heart of much of Python code’s flexibility.

Routing script outputs to GUI pop ups

Figure 11-9. Routing script outputs to GUI pop ups



[*] These two scripts are something of a unique case; because the App superclass they employ saves away standard streams in its own attributes at object creation time, you must kick off the GUI redirection wrapper calls as soon as possible so that App finds the redirected GUI streams in sys when saving them locally. Most other scripts aren’t quite as tricky when it comes to internal stream redirections. Trace through the code to see what I mean.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.79.147