]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/shell.py
Don't scroll too far if the child getting the focus is large.
[wxWidgets.git] / wxPython / wx / lib / shell.py
index f3f7d272e4f9072a31a04dfae030ae0f4cd3bdaf..428a2cbbef43ada6af7599c117b23099c37fa444 100644 (file)
@@ -1,8 +1,377 @@
+# shell.py
+#----------------------------------------------------------------------
+# 12/10/2003 - Jeff Grimmett (grimmtooth@softhome.net)
+#
+# o 2.5 compatability update.
+# o Added deprecation warning.
+#
 
-"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
+"""wxPython interactive shell
+
+Copyright (c) 1999 SIA "ANK"
+
+this module is free software.  it may be used under same terms as Python itself
+
+Notes:
+i would like to use command completion (see rlcompleter library module),
+but i cannot load it because i don't have readline...
+
+History:
+03-oct-1999 [als] created
+04-oct-1999 [als] PyShellOutput.intro moved from __init__ parameters
+                  to class attributes; html debug disabled
+04-oct-1999 [als] fixed bug with class attributes
+                  input prompts and output styles added to customized demo
+                  some html cleanups
+04-oct-1999 [rpd] Changed to use the new sizers
+05-oct-1999 [als] changes inspired by code.InteractiveInterpreter()
+                  from Python Library.  if i knew about this class earlier,
+                  i would rather inherit from it.
+                  renamed to wxPyShell.py since i've renounced the 8.3 scheme
+
+8-10-2001         THIS MODULE IS NOW DEPRECATED.  Please see the most excellent
+                  PyCrust package instead.
+
+"""
+__version__ ="$Revision$"
+# $RCSfile$
+
+import  code
+import  sys
+import  traceback
+import  warnings
+
+import  wx
+import  wx.html
+
+warningmsg = r"""\
+
+########################################\
+# THIS MODULE IS NOW DEPRECATED         |
+#                                       |
+# Please see the most excellent PyCrust |
+# package instead.                      |
+########################################/
+
+"""
+
+warnings.warn(warningmsg, DeprecationWarning, stacklevel=2)
+
+#----------------------------------------------------------------------
+
+class PyShellInput(wx.Panel):
+    """PyShell input window
+
+    """
+    PS1 =" Enter Command:"
+    PS2 ="... continue:"
+    def __init__(self, parent, shell, id=-1):
+        """Create input window
+
+        shell must be a PyShell object.
+        it is used for exception handling, eval() namespaces,
+        and shell.output is used for output
+        (print's go to overridden stdout)
+        """
+        wx.Panel.__init__(self, parent, id)
+        self.shell =shell
+        # make a private copy of class attrs
+        self.PS1 =PyShellInput.PS1
+        self.PS2 =PyShellInput.PS2
+        # create controls
+        self.label =wx.StaticText(self, -1, self.PS1)
+        tid =wx.NewId()
+        self.entry =wx.TextCtrl(self, tid, style = wx.TE_MULTILINE)
+        self.entry.Bind(wx.EVT_CHAR, self.OnChar)
+        self.entry.SetFont(wx.Font(9, wx.MODERN, wx.NORMAL, wx.NORMAL, False))
+        sizer =wx.BoxSizer(wx.VERTICAL)
+        sizer.AddMany([(self.label, 0, wx.EXPAND), (self.entry, 1, wx.EXPAND)])
+        self.SetSizer(sizer)
+        self.SetAutoLayout(True)
+        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
+        # when in "continuation" mode,
+        # two consecutive newlines are required
+        # to avoid execution of unfinished block
+        self.first_line =1
+
+    def OnSetFocus(self, event):
+        self.entry.SetFocus()
+
+
+    def Clear(self, event=None):
+        """reset input state"""
+        self.label.SetLabel(self.PS1)
+        self.label.Refresh()
+        self.entry.SetSelection(0, self.entry.GetLastPosition())
+        self.first_line =1
+        # self.entry.SetFocus()
+
+    def OnChar(self, event):
+        """called on CHARevent.  executes input on newline"""
+        # print "On Char:", event.__dict__.keys()
+        if event.KeyCode() !=wx.WXK_RETURN:
+            # not of our business
+            event.Skip()
+            return
+        text =self.entry.GetValue()
+        # weird CRLF thingy
+        text = text.replace("\r\n", "\n")
+        # see if we've finished
+        if (not (self.first_line or text[-1] =="\n")  # in continuation mode
+            or (text[-1] =="\\")  # escaped newline
+          ):
+            # XXX should escaped newline put myself i "continuation" mode?
+            event.Skip()
+            return
+        # ok, we can try to execute this
+        rc =self.shell.TryExec(text)
+        if rc:
+            # code is incomplete; continue input
+            if self.first_line:
+                self.label.SetLabel(self.PS2)
+                self.label.Refresh()
+                self.first_line =0
+            event.Skip()
+        else:
+            self.Clear()
+
+class PyShellOutput(wx.Panel):
+    """PyShell output window
+
+    for now, it is based on simple wxTextCtrl,
+    but i'm looking at HTML classes to provide colorized output
+    """
+    # attributes for for different (input, output, exception) display styles:
+    # begin tag, end tag, newline
+    in_style =(" <font color=\"#000080\"><tt>&gt;&gt;&gt;&nbsp;",
+               "</tt></font><br>\n", "<br>\n...&nbsp;")
+    out_style =("<tt>", "</tt>\n", "<br>\n")
+    exc_style =("<font color=\"#FF0000\"><tt>",
+                "</tt></font>\n", "<br>\n")
+    intro ="<H3>wxPython Interactive Shell</H3>\n"
+    html_debug =0
+    # entity references
+    erefs =(("&", "&amp;"), (">", "&gt;"), ("<", "&lt;"), ("  ", "&nbsp; "))
+    def __init__(self, parent, id=-1):
+        wx.Panel.__init__(self, parent, id)
+        # make a private copy of class attrs
+        self.in_style =PyShellOutput.in_style
+        self.out_style =PyShellOutput.out_style
+        self.exc_style =PyShellOutput.exc_style
+        self.intro =PyShellOutput.intro
+        self.html_debug =PyShellOutput.html_debug
+        # create windows
+        if self.html_debug:
+            # this was used in html debugging,
+            # but i don't want to delete it; it's funny
+            splitter =wx.SplitterWindow(self, -1)
+            self.view =wx.TextCtrl(splitter, -1,
+                       style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
+            self.html =wx.html.HtmlWindow(splitter)
+            splitter.SplitVertically(self.view, self.html)
+            splitter.SetSashPosition(40)
+            splitter.SetMinimumPaneSize(3)
+            self.client =splitter
+        else:
+            self.view =None
+            self.html =wx.html.HtmlWindow(self)
+            self.client =self.html  # used in OnSize()
+        self.text =self.intro
+        self.html.SetPage(self.text)
+        self.html.SetAutoLayout(True)
+        self.line_buffer =""
+        # refreshes are annoying
+        self.in_batch =0
+        self.dirty =0
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+        self.Bind(wx.EVT_IDLE, self.OnIdle)
+
+    def OnSize(self, event):
+        self.client.SetSize(self.GetClientSize())
+
+    def OnIdle(self, event):
+        """when there's nothing to do, we can update display"""
+        if self.in_batch and self.dirty: self.UpdWindow()
+
+    def BeginBatch(self):
+        """do not refresh display till EndBatch()"""
+        self.in_batch =1
+
+    def EndBatch(self):
+        """end batch; start updating display immediately"""
+        self.in_batch =0
+        if self.dirty: self.UpdWindow()
+
+    def UpdWindow(self):
+        """sync display with text buffer"""
+        html =self.html
+        html.SetPage(self.text)
+        self.dirty =0
+        # scroll to the end
+        (x,y) =html.GetVirtualSize()
+        html.Scroll(0, y)
+
+    def AddText(self, text, style=None):
+        """write text to output window"""
+        # a trick needed to defer default from compile-time to execute-time
+        if style ==None: style =self.out_style
+        if 0 and __debug__: sys.__stdout__.write(text)
+        # handle entities
+        for (symbol, eref) in self.erefs:
+            text = text.replace(symbol, eref)
+        # replace newlines
+        text = text.replace("\n", style[2])
+        # add to contents
+        self.text =self.text +style[0] +text +style[1]
+        if not self.in_batch: self.UpdWindow()
+        else: self.dirty =1
+        if self.html_debug:
+            # html debug output needn't to be too large
+            self.view.SetValue(self.text[-4096:])
+
+    def write(self, str, style=None):
+        """stdout-like interface"""
+        if style ==None: style =self.out_style
+        # do not process incomplete lines
+        if len(str) <1:
+            # hm... what was i supposed to do?
+            return
+        elif str[-1] !="\n":
+            self.line_buffer =self.line_buffer +str
+        else:
+            self.AddText(self.line_buffer +str, style)
+            self.line_buffer =""
+
+    def flush(self, style=None):
+        """write out all that was left in line buffer"""
+        if style ==None: style =self.out_style
+        self.AddText(self.line_buffer +"\n", style)
+
+    def write_in(self, str, style=None):
+        """write text in "input" style"""
+        if style ==None: style =self.in_style
+        self.AddText(str, style)
+
+    def write_exc(self, str, style=None):
+        """write text in "exception" style"""
+        if style ==None: style =self.exc_style
+        self.AddText(str, style)
+
+class PyShell(wx.Panel):
+    """interactive Python shell with wxPython interface
+
+    """
+    def __init__(self, parent, globals=globals(), locals={},
+                 id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize,
+                 style=wx.TAB_TRAVERSAL, name="shell"):
+        """create PyShell window"""
+        wx.Panel.__init__(self, parent, id, pos, size, style, name)
+        self.globals =globals
+        self.locals =locals
+        splitter =wx.SplitterWindow(self, -1)
+        self.output =PyShellOutput(splitter)
+        self.input =PyShellInput(splitter, self)
+        self.input.SetFocus()
+        splitter.SplitHorizontally(self.input, self.output)
+        splitter.SetSashPosition(100)
+        splitter.SetMinimumPaneSize(20)
+        self.splitter =splitter
+        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+
+    def OnSetFocus(self, event):
+        self.input.SetFocus()
+
+    def TryExec(self, source, symbol="single"):
+        """Compile and run some source in the interpreter.
+
+        borrowed from code.InteractiveInterpreter().runsource()
+        as i said above, i would rather like to inherit from that class
+
+        returns 1 if more input is required, or 0, otherwise
+        """
+        try:
+            cc = code.compile_command(source, symbol=symbol)
+        except (OverflowError, SyntaxError):
+            # [als] hm... never seen anything of that kind
+            self.ShowSyntaxError()
+            return 0
+        if cc is None:
+            # source is incomplete
+            return 1
+        # source is sucessfully compiled
+        out =self.output
+        # redirect system stdout to the output window
+        prev_out =sys.stdout
+        sys.stdout =out
+        # begin printout batch (html updates are deferred until EndBatch())
+        out.BeginBatch()
+        out.write_in(source)
+        try:
+            exec cc in self.globals, self.locals
+        except SystemExit:
+            # SystemExit is not handled and has to be re-raised
+            raise
+        except:
+            # all other exceptions produce traceback output
+            self.ShowException()
+        # switch back to saved stdout
+        sys.stdout =prev_out
+        # commit printout
+        out.flush()
+        out.EndBatch()
+        return 0
+
+    def ShowException(self):
+        """display the traceback for the latest exception"""
+        (etype, value, tb) =sys.exc_info()
+        # remove myself from traceback
+        tblist =traceback.extract_tb(tb)[1:]
+        msg = ' '.join(traceback.format_exception_only(etype, value)
+                        +traceback.format_list(tblist))
+        self.output.write_exc(msg)
+
+    def ShowSyntaxError(self):
+        """display message about syntax error (no traceback here)"""
+        (etype, value, tb) =sys.exc_info()
+        msg = ' '.join(traceback.format_exception_only(etype, value))
+        self.output.write_exc(msg)
+
+    def OnSize(self, event):
+        self.splitter.SetSize(self.GetClientSize())
+
+#----------------------------------------------------------------------
+if __name__ == '__main__':
+    class MyFrame(wx.Frame):
+        """Very standard Frame class. Nothing special here!"""
+        def __init__(self, parent=None, id =-1,
+                     title="wxPython Interactive Shell"):
+            wx.Frame.__init__(self, parent, id, title)
+            self.shell =PyShell(self)
+
+    class MyApp(wx.App):
+        """Demonstrates usage of both default and customized shells"""
+        def OnInit(self):
+            frame = MyFrame()
+            frame.Show(True)
+            self.SetTopWindow(frame)
+##             PyShellInput.PS1 =" let's get some work done..."
+##             PyShellInput.PS2 =" ok, what do you really mean?"
+##             PyShellOutput.in_style =(
+##                 "<I><font color=\"#008000\"><tt>&gt;&gt;&gt;&nbsp;",
+##                 "</tt></font></I><br>\n", "<br>\n...&nbsp;")
+##             PyShellOutput.out_style =(
+##                 "<font color=\"#000080\"><tt>",
+##                 "</tt></font><br>\n", "<br>\n")
+##             PyShellOutput.exc_style =("<B><font color=\"#FF0000\"><tt>",
+##                 "</tt></font></B>\n", "<br>\n")
+##             PyShellOutput.intro ="<I><B>Customized wxPython Shell</B>" \
+##                 "<br>&lt;-- move this sash to see html debug output</I><br>\n"
+##             PyShellOutput.html_debug =1
+##             frame = MyFrame(title="Customized wxPython Shell")
+##             frame.Show(True)
+            return True
+
+    app = MyApp(0)
+    app.MainLoop()
 
-from wx import _rename
-from wxPython.lib import shell
-_rename(globals(), shell.__dict__, modulename='lib.shell')
-del shell
-del _rename