Welcome, guest | Sign In | My Account | Store | Cart
4

I find that I often want to test some small code samples, too long to type at the interpreter (and retype, with some small changes), but not worth saving. The following wxPython based mini-app implements the basic for a small Python editor with a side output window. It also handles input() and raw_input().

Python, 115 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import wx   # requires wxPython
import sys  # required to redirect the output

class EditorSashWindow(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        winids = []

        # Left window has fixed size and contains control buttons
        self.controls = wx.SashLayoutWindow(self, -1)
        winids.append(self.controls.GetId())
        
        self.controls.SetDefaultSize((80, 600))
        self.controls.SetOrientation(wx.LAYOUT_VERTICAL)
        self.controls.SetAlignment(wx.LAYOUT_LEFT)
        
        b = wx.Button(self.controls, -1, "Open", (3, 20))
        self.Bind(wx.EVT_BUTTON, self.openFile, b)
        b.SetDefault()
        b2 = wx.Button(self.controls, -1, "Save", (3, 60))
        self.Bind(wx.EVT_BUTTON, self.saveFile, b2)
        b2.SetDefault()
        b3 = wx.Button(self.controls, -1, "Run", (3, 100))
        self.Bind(wx.EVT_BUTTON, self.run, b3)
        b3.SetDefault()
        b4 = wx.Button(self.controls, -1, "Clear", (3, 140))
        self.Bind(wx.EVT_BUTTON, self.clear, b4)
        b4.SetDefault()

        # This will occupy the space not used by the Layout Algorithm
        self.remainingSpace = wx.SashLayoutWindow(
                                 self, -1, style=wx.NO_BORDER|wx.SW_3D)
                                
        self.python_editor = wx.TextCtrl(self.remainingSpace,
                        -1, "", wx.DefaultPosition, wx.DefaultSize, 
                        wx.TE_MULTILINE|wx.SUNKEN_BORDER
                        )
        self.python_editor.SetValue("#Editor window")

        # The output window is at the extreme right
        win =  wx.SashLayoutWindow(
                self, -1, wx.DefaultPosition, (200, 30), 
                wx.NO_BORDER|wx.SW_3D
                )
        winids.append(win.GetId())
        win.SetDefaultSize((300, 600))
        win.SetOrientation(wx.LAYOUT_VERTICAL)
        win.SetAlignment(wx.LAYOUT_RIGHT)
        win.SetSashVisible(wx.SASH_LEFT, True)
        win.SetExtraBorderSize(10)
        self.rightWindow = win
        self.output_window = wx.TextCtrl(win, -1, "", wx.DefaultPosition, 
                        wx.DefaultSize, wx.TE_MULTILINE|wx.SUNKEN_BORDER)
        self.output_window.SetValue("Output Window\n")
        #redirecting output
        sys.stdout = self.output_window
        sys.stderr = self.output_window   

        self.Bind(wx.EVT_SASH_DRAGGED_RANGE, self.OnSashDrag, id=min(winids), 
                  id2=max(winids))
        self.Bind(wx.EVT_SIZE, self.OnSize)
        
    def OnSashDrag(self, event):
        eobj = event.GetEventObject()
        if eobj is self.rightWindow:
            self.rightWindow.SetDefaultSize((event.GetDragRect().width, 600))
        wx.LayoutAlgorithm().LayoutWindow(self, self.remainingSpace)
        self.remainingSpace.Refresh()

    def OnSize(self, event):
        wx.LayoutAlgorithm().LayoutWindow(self, self.remainingSpace)

    def saveFile(self, event):
        self.output_window.SetValue("Save File not implemented")
        
    def openFile(self, event):
        self.output_window.SetValue("Open File not implemented")
        
    def run(self, event):
        '''Runs the user code; input() and raw_input() are implemented
           with dialogs.'''
        user_code = self.python_editor.GetValue()
        myGlobals = globals()
        myGlobals['raw_input'] = self.myRawInput
        myGlobals['input'] = self.myInput
        exec user_code in myGlobals
 
    def clear(self, event):
        '''Clears the output window'''
        self.output_window.SetValue("")

    def myRawInput(self, text):
        dlg = wx.TextEntryDialog(self, text, 'raw_input() request', '')
        if dlg.ShowModal() == wx.ID_OK:
            user_response = dlg.GetValue()
        dlg.Destroy()
        return user_response
    
    def myInput(self, text):
        dlg = wx.TextEntryDialog(self, text, 'input() request', '')
        if dlg.ShowModal() == wx.ID_OK:
            user_response = dlg.GetValue()
        dlg.Destroy()
        return eval(user_response)

class MainWindow(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self,parent, -1, title, size=(800, 600),
                    style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)
        self.app = EditorSashWindow(self)
        self.Show(True)

app = wx.PySimpleApp()
frame=MainWindow(None, 'Lightning Compiler')
app.MainLoop()

As a teaching tool, I wanted to implement something a bit more user friendly than the standard interpreter. This version runs the user code (in the "editor" window) and shows the output in a secondary window. The code is executed by the exec statement, which, on its own, does not allow to use input() or raw_input(). After looking in various books and websites, I couldn't find any hints as to how to solve this problem and I came up with the version shown above, which simply rebinds input() and raw_input() to wxPython dialogs, something which should be obvious to the experts but not necessarily so to others.

In the interest of keeping the code relatively short, I based the editor used here on a simple TextCtrl class, instead of a scintilla based editor which allows proper syntax colouring. While I used wxPython for the GUI, the principle is independent of the chosen GUI.

Since I wrote this, I almost never use the standard Python interpreter to test code snippets!

5 comments

runsun pan 8 years, 7 months ago  # | flag

Very good tool. Thx for sharing.

Andre Roberge (author) 8 years, 7 months ago  # | flag

Version with colorized source. I should probably add that a version with the scintilla editor embedded for colorizing the source is available at http://sourceforge.net/project/showfiles.php?group_id=125834 under "lightning".

Franz steinhaeusler 8 years, 7 months ago  # | flag

Thank you, I like it.

May I suggest:

1) # -- encoding: latin-1 --

at the beginning (so no encode error appear).

For even faster typing and operating:

2) even faster to start would be to assign a hotkey.

I think, F5 is commonly used to run something.

class EditorSashWindow(wx.Panel):

def __init__(self, parent):

    ...

    ...

    runid = wx.NewId()

    b3 = wx.Button(self.controls, runid, "Run", (3, 100))

    ...

    ...

    aTable = wx.AcceleratorTable([(wx.ACCEL_NORMAL, wx.WXK_F5, runid)])

    self.SetAcceleratorTable(aTable)

3) I would add a LF at the end to be able to type immediatly:

self.python_editor.AddText("#Editor window\n")

4) SetFocus to the edit window immediatly:

class EditorSashWindow(wx.Panel):

    def __init__(self, parent):

        ...

        ...

        self.python_editor.SetFocus()
Andre Roberge (author) 8 years, 7 months ago  # | flag

Suggestions implemented! Thanks for the suggestions. I implemented them, and made a few minor changes. New version on sourceforge

Franz steinhaeusler 8 years, 7 months ago  # | flag

Great, thank you (for your changes and mentioning me! ;))

May I suggest one small thing.

(No need to update the program again, only of my suggestion)

the scrolling of the output window isn't working;

so if the output exceeds line 28 (on my pc) the next outputs

are not seen automatically.

So I added:

class LogWindow(python_editor):

...

def write(self, text):

    self.AddText(text)

=> self.EnsureCaretVisible()

Add a comment

Sign in to comment