11.5. 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 GUIincluding 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. PP3E\Gui\Tools\guiStreams.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 '\n' 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 + '\n' # 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. 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 blockingthey 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. 11.5.1. 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. PP3E\Gui\ShellGui\packdlg-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. Figure 11-9. Routing script outputs to GUI pop ups |