Hi all,
Here's one that has me stumped. I'm trying to build a system shell with a Tkinter GUI in Python, and I can't figure out what to do with one of the widgets. Here's the program concept:
I have two major widgets: a ScrolledText widget and an Entry widget. The purpose of the ScrolledText widget is to display the results (from STDOUT or STDERR) of what the user has typed. To that end, it is user-uneditable. The Entry widget contains both the prompt and the command line; when the application starts, this widget displays the prompt (which looks like "CURRENT_DIR >> "), steals focus from the application, and sets the cursor to the rightmost index, just after the double-carets. The Entry widget is bound to a command-processing function, so that when the user hits the Enter key, the command-processing function grabs whatever has been typed in the Entry widget and does the appropriate thing.
The problem: because Entry widgets are designed to let users do whatever they want to the text inside, the user can delete the prompt! This is no good. I need some way to protect the prompt text without disabling the entire widget. I don't want to move the prompt to another widget, because that will mess up the feel I'm trying to give the program (the feel shared by every terminal application ever created). The only solution I could come up with was to bind the Entry widget to a "correcting function" so that whenever Tk registers Backspace, Delete, or Left-click events, the function activates and rewrites whatever part of the prompt the user has gotten rid of. This seems to me like a pretty horrific kludge, and I'm sure it will backfire on me somehow.
Does anyone have any better ideas (within the confines of Tkinter)? Is there a secret widget I've never heard of that emulates terminals?
Thanks in advance for any help you can give. Here is the code (I ripped out the guts of the processing function to save space, and the kludge I mentioned is not included):
import os
from Tkinter import *
from ScrolledText import ScrolledText
# -- The application class
class Dumbshell:
# -- Initializes new instances of Dumbshell
def __init__(self, root):
# Set instance-specific variables (widget dimensions,
# command variable)
self.display_height = 20
self.display_width = 80
self.line_width = 80
self.command = StringVar()
self.command.set(os.path.basename(os.path.abspath("."))+ \
" >> ")
# Add the window Frame to which all widgets will be bound
self.mainframe = Frame(root)
self.mainframe.pack()
# Add a ScrolledText widget (the display)
self.display = ScrolledText(self.mainframe,
height=self.display_height,
width=self.display_width,
state=NORMAL)
self.display.pack()
# Blank out the display by inserting newlines
# Ensures that our output gets printed at the bottom of the
# display
for x in range(self.display_height): self.display.insert(END, "\n")
# Make the display user-uneditable by changing state to
# DISABLED
self.display.config(state=DISABLED)
# Add an Entry widget (the command-line)
self.entry = Entry(self.mainframe,
width=self.line_width,
textvariable=self.command,
state=NORMAL)
self.entry.pack(anchor="w")
# Bind the Entry widget to the Enter button and self.process
self.entry.bind(sequence="<Return>", func=self.process)
# Steal the focus
self.entry.focus_set()
# Send the cursor to the end of the prompt, where it belongs
self.entry.icursor(END)
# -- Processes entered commands, does the actual shell work
def process(self, args):
# First things first: print the entered command in the display
self.sendToDisplay(self.command.get()+"\n")
# Process it
# Lots of complicated stuff happens here, don't worry about it
# Reset the command window to display only the prompt
self.command.set(os.path.basename(os.path.abspath("."))+ \
" >> ");
# -- Prints input string to the display widget
def sendToDisplay(self, string):
# Make the display editable
self.display.config(state=NORMAL)
# Add our string
self.display.insert(END, string)
# Scroll down to the bottom again
self.display.see(END)
# Make the display uneditable
self.display.config(state=DISABLED)
# Other functions which handle the builtins, other commands, etc
# Don't sweat it
# -- The following code executes upon command-line invocation
root = Tk()
ds = Dumbshell(root)
root.mainloop()