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.
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.
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.
[*] 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.
3.16.79.147