Tkinter renaming reversal: move modules back in their place.
This commit is contained in:
parent
248e3a8b7f
commit
33cece05b9
|
@ -0,0 +1,49 @@
|
||||||
|
# dialog.py -- Tkinter interface to the tk_dialog script.
|
||||||
|
|
||||||
|
from tkinter import *
|
||||||
|
from tkinter import _cnfmerge
|
||||||
|
|
||||||
|
if TkVersion <= 3.6:
|
||||||
|
DIALOG_ICON = 'warning'
|
||||||
|
else:
|
||||||
|
DIALOG_ICON = 'questhead'
|
||||||
|
|
||||||
|
|
||||||
|
class Dialog(Widget):
|
||||||
|
def __init__(self, master=None, cnf={}, **kw):
|
||||||
|
cnf = _cnfmerge((cnf, kw))
|
||||||
|
self.widgetName = '__dialog__'
|
||||||
|
Widget._setup(self, master, cnf)
|
||||||
|
self.num = self.tk.getint(
|
||||||
|
self.tk.call(
|
||||||
|
'tk_dialog', self._w,
|
||||||
|
cnf['title'], cnf['text'],
|
||||||
|
cnf['bitmap'], cnf['default'],
|
||||||
|
*cnf['strings']))
|
||||||
|
try: Widget.destroy(self)
|
||||||
|
except TclError: pass
|
||||||
|
def destroy(self): pass
|
||||||
|
|
||||||
|
def _test():
|
||||||
|
d = Dialog(None, {'title': 'File Modified',
|
||||||
|
'text':
|
||||||
|
'File "Python.h" has been modified'
|
||||||
|
' since the last time it was saved.'
|
||||||
|
' Do you want to save it before'
|
||||||
|
' exiting the application.',
|
||||||
|
'bitmap': DIALOG_ICON,
|
||||||
|
'default': 0,
|
||||||
|
'strings': ('Save File',
|
||||||
|
'Discard Changes',
|
||||||
|
'Return to Editor')})
|
||||||
|
print d.num
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
t = Button(None, {'text': 'Test',
|
||||||
|
'command': _test,
|
||||||
|
Pack: {}})
|
||||||
|
q = Button(None, {'text': 'Quit',
|
||||||
|
'command': t.quit,
|
||||||
|
Pack: {}})
|
||||||
|
t.mainloop()
|
|
@ -0,0 +1,274 @@
|
||||||
|
"""File selection dialog classes.
|
||||||
|
|
||||||
|
Classes:
|
||||||
|
|
||||||
|
- FileDialog
|
||||||
|
- LoadFileDialog
|
||||||
|
- SaveFileDialog
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from Tkinter import *
|
||||||
|
from Dialog import Dialog
|
||||||
|
|
||||||
|
import os
|
||||||
|
import fnmatch
|
||||||
|
|
||||||
|
|
||||||
|
dialogstates = {}
|
||||||
|
|
||||||
|
|
||||||
|
class FileDialog:
|
||||||
|
|
||||||
|
"""Standard file selection dialog -- no checks on selected file.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
d = FileDialog(master)
|
||||||
|
fname = d.go(dir_or_file, pattern, default, key)
|
||||||
|
if fname is None: ...canceled...
|
||||||
|
else: ...open file...
|
||||||
|
|
||||||
|
All arguments to go() are optional.
|
||||||
|
|
||||||
|
The 'key' argument specifies a key in the global dictionary
|
||||||
|
'dialogstates', which keeps track of the values for the directory
|
||||||
|
and pattern arguments, overriding the values passed in (it does
|
||||||
|
not keep track of the default argument!). If no key is specified,
|
||||||
|
the dialog keeps no memory of previous state. Note that memory is
|
||||||
|
kept even when the dialog is canceled. (All this emulates the
|
||||||
|
behavior of the Macintosh file selection dialogs.)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
title = "File Selection Dialog"
|
||||||
|
|
||||||
|
def __init__(self, master, title=None):
|
||||||
|
if title is None: title = self.title
|
||||||
|
self.master = master
|
||||||
|
self.directory = None
|
||||||
|
|
||||||
|
self.top = Toplevel(master)
|
||||||
|
self.top.title(title)
|
||||||
|
self.top.iconname(title)
|
||||||
|
|
||||||
|
self.botframe = Frame(self.top)
|
||||||
|
self.botframe.pack(side=BOTTOM, fill=X)
|
||||||
|
|
||||||
|
self.selection = Entry(self.top)
|
||||||
|
self.selection.pack(side=BOTTOM, fill=X)
|
||||||
|
self.selection.bind('<Return>', self.ok_event)
|
||||||
|
|
||||||
|
self.filter = Entry(self.top)
|
||||||
|
self.filter.pack(side=TOP, fill=X)
|
||||||
|
self.filter.bind('<Return>', self.filter_command)
|
||||||
|
|
||||||
|
self.midframe = Frame(self.top)
|
||||||
|
self.midframe.pack(expand=YES, fill=BOTH)
|
||||||
|
|
||||||
|
self.filesbar = Scrollbar(self.midframe)
|
||||||
|
self.filesbar.pack(side=RIGHT, fill=Y)
|
||||||
|
self.files = Listbox(self.midframe, exportselection=0,
|
||||||
|
yscrollcommand=(self.filesbar, 'set'))
|
||||||
|
self.files.pack(side=RIGHT, expand=YES, fill=BOTH)
|
||||||
|
btags = self.files.bindtags()
|
||||||
|
self.files.bindtags(btags[1:] + btags[:1])
|
||||||
|
self.files.bind('<ButtonRelease-1>', self.files_select_event)
|
||||||
|
self.files.bind('<Double-ButtonRelease-1>', self.files_double_event)
|
||||||
|
self.filesbar.config(command=(self.files, 'yview'))
|
||||||
|
|
||||||
|
self.dirsbar = Scrollbar(self.midframe)
|
||||||
|
self.dirsbar.pack(side=LEFT, fill=Y)
|
||||||
|
self.dirs = Listbox(self.midframe, exportselection=0,
|
||||||
|
yscrollcommand=(self.dirsbar, 'set'))
|
||||||
|
self.dirs.pack(side=LEFT, expand=YES, fill=BOTH)
|
||||||
|
self.dirsbar.config(command=(self.dirs, 'yview'))
|
||||||
|
btags = self.dirs.bindtags()
|
||||||
|
self.dirs.bindtags(btags[1:] + btags[:1])
|
||||||
|
self.dirs.bind('<ButtonRelease-1>', self.dirs_select_event)
|
||||||
|
self.dirs.bind('<Double-ButtonRelease-1>', self.dirs_double_event)
|
||||||
|
|
||||||
|
self.ok_button = Button(self.botframe,
|
||||||
|
text="OK",
|
||||||
|
command=self.ok_command)
|
||||||
|
self.ok_button.pack(side=LEFT)
|
||||||
|
self.filter_button = Button(self.botframe,
|
||||||
|
text="Filter",
|
||||||
|
command=self.filter_command)
|
||||||
|
self.filter_button.pack(side=LEFT, expand=YES)
|
||||||
|
self.cancel_button = Button(self.botframe,
|
||||||
|
text="Cancel",
|
||||||
|
command=self.cancel_command)
|
||||||
|
self.cancel_button.pack(side=RIGHT)
|
||||||
|
|
||||||
|
self.top.protocol('WM_DELETE_WINDOW', self.cancel_command)
|
||||||
|
# XXX Are the following okay for a general audience?
|
||||||
|
self.top.bind('<Alt-w>', self.cancel_command)
|
||||||
|
self.top.bind('<Alt-W>', self.cancel_command)
|
||||||
|
|
||||||
|
def go(self, dir_or_file=os.curdir, pattern="*", default="", key=None):
|
||||||
|
if key and dialogstates.has_key(key):
|
||||||
|
self.directory, pattern = dialogstates[key]
|
||||||
|
else:
|
||||||
|
dir_or_file = os.path.expanduser(dir_or_file)
|
||||||
|
if os.path.isdir(dir_or_file):
|
||||||
|
self.directory = dir_or_file
|
||||||
|
else:
|
||||||
|
self.directory, default = os.path.split(dir_or_file)
|
||||||
|
self.set_filter(self.directory, pattern)
|
||||||
|
self.set_selection(default)
|
||||||
|
self.filter_command()
|
||||||
|
self.selection.focus_set()
|
||||||
|
self.top.wait_visibility() # window needs to be visible for the grab
|
||||||
|
self.top.grab_set()
|
||||||
|
self.how = None
|
||||||
|
self.master.mainloop() # Exited by self.quit(how)
|
||||||
|
if key:
|
||||||
|
directory, pattern = self.get_filter()
|
||||||
|
if self.how:
|
||||||
|
directory = os.path.dirname(self.how)
|
||||||
|
dialogstates[key] = directory, pattern
|
||||||
|
self.top.destroy()
|
||||||
|
return self.how
|
||||||
|
|
||||||
|
def quit(self, how=None):
|
||||||
|
self.how = how
|
||||||
|
self.master.quit() # Exit mainloop()
|
||||||
|
|
||||||
|
def dirs_double_event(self, event):
|
||||||
|
self.filter_command()
|
||||||
|
|
||||||
|
def dirs_select_event(self, event):
|
||||||
|
dir, pat = self.get_filter()
|
||||||
|
subdir = self.dirs.get('active')
|
||||||
|
dir = os.path.normpath(os.path.join(self.directory, subdir))
|
||||||
|
self.set_filter(dir, pat)
|
||||||
|
|
||||||
|
def files_double_event(self, event):
|
||||||
|
self.ok_command()
|
||||||
|
|
||||||
|
def files_select_event(self, event):
|
||||||
|
file = self.files.get('active')
|
||||||
|
self.set_selection(file)
|
||||||
|
|
||||||
|
def ok_event(self, event):
|
||||||
|
self.ok_command()
|
||||||
|
|
||||||
|
def ok_command(self):
|
||||||
|
self.quit(self.get_selection())
|
||||||
|
|
||||||
|
def filter_command(self, event=None):
|
||||||
|
dir, pat = self.get_filter()
|
||||||
|
try:
|
||||||
|
names = os.listdir(dir)
|
||||||
|
except os.error:
|
||||||
|
self.master.bell()
|
||||||
|
return
|
||||||
|
self.directory = dir
|
||||||
|
self.set_filter(dir, pat)
|
||||||
|
names.sort()
|
||||||
|
subdirs = [os.pardir]
|
||||||
|
matchingfiles = []
|
||||||
|
for name in names:
|
||||||
|
fullname = os.path.join(dir, name)
|
||||||
|
if os.path.isdir(fullname):
|
||||||
|
subdirs.append(name)
|
||||||
|
elif fnmatch.fnmatch(name, pat):
|
||||||
|
matchingfiles.append(name)
|
||||||
|
self.dirs.delete(0, END)
|
||||||
|
for name in subdirs:
|
||||||
|
self.dirs.insert(END, name)
|
||||||
|
self.files.delete(0, END)
|
||||||
|
for name in matchingfiles:
|
||||||
|
self.files.insert(END, name)
|
||||||
|
head, tail = os.path.split(self.get_selection())
|
||||||
|
if tail == os.curdir: tail = ''
|
||||||
|
self.set_selection(tail)
|
||||||
|
|
||||||
|
def get_filter(self):
|
||||||
|
filter = self.filter.get()
|
||||||
|
filter = os.path.expanduser(filter)
|
||||||
|
if filter[-1:] == os.sep or os.path.isdir(filter):
|
||||||
|
filter = os.path.join(filter, "*")
|
||||||
|
return os.path.split(filter)
|
||||||
|
|
||||||
|
def get_selection(self):
|
||||||
|
file = self.selection.get()
|
||||||
|
file = os.path.expanduser(file)
|
||||||
|
return file
|
||||||
|
|
||||||
|
def cancel_command(self, event=None):
|
||||||
|
self.quit()
|
||||||
|
|
||||||
|
def set_filter(self, dir, pat):
|
||||||
|
if not os.path.isabs(dir):
|
||||||
|
try:
|
||||||
|
pwd = os.getcwd()
|
||||||
|
except os.error:
|
||||||
|
pwd = None
|
||||||
|
if pwd:
|
||||||
|
dir = os.path.join(pwd, dir)
|
||||||
|
dir = os.path.normpath(dir)
|
||||||
|
self.filter.delete(0, END)
|
||||||
|
self.filter.insert(END, os.path.join(dir or os.curdir, pat or "*"))
|
||||||
|
|
||||||
|
def set_selection(self, file):
|
||||||
|
self.selection.delete(0, END)
|
||||||
|
self.selection.insert(END, os.path.join(self.directory, file))
|
||||||
|
|
||||||
|
|
||||||
|
class LoadFileDialog(FileDialog):
|
||||||
|
|
||||||
|
"""File selection dialog which checks that the file exists."""
|
||||||
|
|
||||||
|
title = "Load File Selection Dialog"
|
||||||
|
|
||||||
|
def ok_command(self):
|
||||||
|
file = self.get_selection()
|
||||||
|
if not os.path.isfile(file):
|
||||||
|
self.master.bell()
|
||||||
|
else:
|
||||||
|
self.quit(file)
|
||||||
|
|
||||||
|
|
||||||
|
class SaveFileDialog(FileDialog):
|
||||||
|
|
||||||
|
"""File selection dialog which checks that the file may be created."""
|
||||||
|
|
||||||
|
title = "Save File Selection Dialog"
|
||||||
|
|
||||||
|
def ok_command(self):
|
||||||
|
file = self.get_selection()
|
||||||
|
if os.path.exists(file):
|
||||||
|
if os.path.isdir(file):
|
||||||
|
self.master.bell()
|
||||||
|
return
|
||||||
|
d = Dialog(self.top,
|
||||||
|
title="Overwrite Existing File Question",
|
||||||
|
text="Overwrite existing file %r?" % (file,),
|
||||||
|
bitmap='questhead',
|
||||||
|
default=1,
|
||||||
|
strings=("Yes", "Cancel"))
|
||||||
|
if d.num != 0:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
head, tail = os.path.split(file)
|
||||||
|
if not os.path.isdir(head):
|
||||||
|
self.master.bell()
|
||||||
|
return
|
||||||
|
self.quit(file)
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
"""Simple test program."""
|
||||||
|
root = Tk()
|
||||||
|
root.withdraw()
|
||||||
|
fd = LoadFileDialog(root)
|
||||||
|
loadfile = fd.go(key="test")
|
||||||
|
fd = SaveFileDialog(root)
|
||||||
|
savefile = fd.go(key="test")
|
||||||
|
print loadfile, savefile
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
|
@ -0,0 +1,41 @@
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
# Delay import _tkinter until we have set TCL_LIBRARY,
|
||||||
|
# so that Tcl_FindExecutable has a chance to locate its
|
||||||
|
# encoding directory.
|
||||||
|
|
||||||
|
# Unfortunately, we cannot know the TCL_LIBRARY directory
|
||||||
|
# if we don't know the tcl version, which we cannot find out
|
||||||
|
# without import Tcl. Fortunately, Tcl will itself look in
|
||||||
|
# <TCL_LIBRARY>\..\tcl<TCL_VERSION>, so anything close to
|
||||||
|
# the real Tcl library will do.
|
||||||
|
|
||||||
|
prefix = os.path.join(sys.prefix,"tcl")
|
||||||
|
if not os.path.exists(prefix):
|
||||||
|
# devdir/../tcltk/lib
|
||||||
|
prefix = os.path.join(sys.prefix, os.path.pardir, "tcltk", "lib")
|
||||||
|
prefix = os.path.abspath(prefix)
|
||||||
|
# if this does not exist, no further search is needed
|
||||||
|
if os.path.exists(prefix):
|
||||||
|
if not os.environ.has_key("TCL_LIBRARY"):
|
||||||
|
for name in os.listdir(prefix):
|
||||||
|
if name.startswith("tcl"):
|
||||||
|
tcldir = os.path.join(prefix,name)
|
||||||
|
if os.path.isdir(tcldir):
|
||||||
|
os.environ["TCL_LIBRARY"] = tcldir
|
||||||
|
# Compute TK_LIBRARY, knowing that it has the same version
|
||||||
|
# as Tcl
|
||||||
|
import _tkinter
|
||||||
|
ver = str(_tkinter.TCL_VERSION)
|
||||||
|
if not os.environ.has_key("TK_LIBRARY"):
|
||||||
|
v = os.path.join(prefix, 'tk'+ver)
|
||||||
|
if os.path.exists(os.path.join(v, "tclIndex")):
|
||||||
|
os.environ['TK_LIBRARY'] = v
|
||||||
|
# We don't know the Tix version, so we must search the entire
|
||||||
|
# directory
|
||||||
|
if not os.environ.has_key("TIX_LIBRARY"):
|
||||||
|
for name in os.listdir(prefix):
|
||||||
|
if name.startswith("tix"):
|
||||||
|
tixdir = os.path.join(prefix,name)
|
||||||
|
if os.path.isdir(tixdir):
|
||||||
|
os.environ["TIX_LIBRARY"] = tixdir
|
|
@ -0,0 +1,43 @@
|
||||||
|
# A ScrolledText widget feels like a text widget but also has a
|
||||||
|
# vertical scroll bar on its right. (Later, options may be added to
|
||||||
|
# add a horizontal bar as well, to make the bars disappear
|
||||||
|
# automatically when not needed, to move them to the other side of the
|
||||||
|
# window, etc.)
|
||||||
|
#
|
||||||
|
# Configuration options are passed to the Text widget.
|
||||||
|
# A Frame widget is inserted between the master and the text, to hold
|
||||||
|
# the Scrollbar widget.
|
||||||
|
# Most methods calls are inherited from the Text widget; Pack methods
|
||||||
|
# are redirected to the Frame widget however.
|
||||||
|
|
||||||
|
from tkinter import *
|
||||||
|
from tkinter import _cnfmerge
|
||||||
|
|
||||||
|
class ScrolledText(Text):
|
||||||
|
def __init__(self, master=None, cnf=None, **kw):
|
||||||
|
if cnf is None:
|
||||||
|
cnf = {}
|
||||||
|
if kw:
|
||||||
|
cnf = _cnfmerge((cnf, kw))
|
||||||
|
fcnf = {}
|
||||||
|
for k in cnf.keys():
|
||||||
|
if type(k) == ClassType or k == 'name':
|
||||||
|
fcnf[k] = cnf[k]
|
||||||
|
del cnf[k]
|
||||||
|
self.frame = Frame(master, **fcnf)
|
||||||
|
self.vbar = Scrollbar(self.frame, name='vbar')
|
||||||
|
self.vbar.pack(side=RIGHT, fill=Y)
|
||||||
|
cnf['name'] = 'text'
|
||||||
|
Text.__init__(self, self.frame, **cnf)
|
||||||
|
self.pack(side=LEFT, fill=BOTH, expand=1)
|
||||||
|
self['yscrollcommand'] = self.vbar.set
|
||||||
|
self.vbar['command'] = self.yview
|
||||||
|
|
||||||
|
# Copy geometry methods of self.frame -- hack!
|
||||||
|
methods = Pack.__dict__.keys()
|
||||||
|
methods = methods + Grid.__dict__.keys()
|
||||||
|
methods = methods + Place.__dict__.keys()
|
||||||
|
|
||||||
|
for m in methods:
|
||||||
|
if m[0] != '_' and m != 'config' and m != 'configure':
|
||||||
|
setattr(self, m, getattr(self.frame, m))
|
|
@ -0,0 +1,112 @@
|
||||||
|
"""A simple but flexible modal dialog box."""
|
||||||
|
|
||||||
|
|
||||||
|
from Tkinter import *
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleDialog:
|
||||||
|
|
||||||
|
def __init__(self, master,
|
||||||
|
text='', buttons=[], default=None, cancel=None,
|
||||||
|
title=None, class_=None):
|
||||||
|
if class_:
|
||||||
|
self.root = Toplevel(master, class_=class_)
|
||||||
|
else:
|
||||||
|
self.root = Toplevel(master)
|
||||||
|
if title:
|
||||||
|
self.root.title(title)
|
||||||
|
self.root.iconname(title)
|
||||||
|
self.message = Message(self.root, text=text, aspect=400)
|
||||||
|
self.message.pack(expand=1, fill=BOTH)
|
||||||
|
self.frame = Frame(self.root)
|
||||||
|
self.frame.pack()
|
||||||
|
self.num = default
|
||||||
|
self.cancel = cancel
|
||||||
|
self.default = default
|
||||||
|
self.root.bind('<Return>', self.return_event)
|
||||||
|
for num in range(len(buttons)):
|
||||||
|
s = buttons[num]
|
||||||
|
b = Button(self.frame, text=s,
|
||||||
|
command=(lambda self=self, num=num: self.done(num)))
|
||||||
|
if num == default:
|
||||||
|
b.config(relief=RIDGE, borderwidth=8)
|
||||||
|
b.pack(side=LEFT, fill=BOTH, expand=1)
|
||||||
|
self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window)
|
||||||
|
self._set_transient(master)
|
||||||
|
|
||||||
|
def _set_transient(self, master, relx=0.5, rely=0.3):
|
||||||
|
widget = self.root
|
||||||
|
widget.withdraw() # Remain invisible while we figure out the geometry
|
||||||
|
widget.transient(master)
|
||||||
|
widget.update_idletasks() # Actualize geometry information
|
||||||
|
if master.winfo_ismapped():
|
||||||
|
m_width = master.winfo_width()
|
||||||
|
m_height = master.winfo_height()
|
||||||
|
m_x = master.winfo_rootx()
|
||||||
|
m_y = master.winfo_rooty()
|
||||||
|
else:
|
||||||
|
m_width = master.winfo_screenwidth()
|
||||||
|
m_height = master.winfo_screenheight()
|
||||||
|
m_x = m_y = 0
|
||||||
|
w_width = widget.winfo_reqwidth()
|
||||||
|
w_height = widget.winfo_reqheight()
|
||||||
|
x = m_x + (m_width - w_width) * relx
|
||||||
|
y = m_y + (m_height - w_height) * rely
|
||||||
|
if x+w_width > master.winfo_screenwidth():
|
||||||
|
x = master.winfo_screenwidth() - w_width
|
||||||
|
elif x < 0:
|
||||||
|
x = 0
|
||||||
|
if y+w_height > master.winfo_screenheight():
|
||||||
|
y = master.winfo_screenheight() - w_height
|
||||||
|
elif y < 0:
|
||||||
|
y = 0
|
||||||
|
widget.geometry("+%d+%d" % (x, y))
|
||||||
|
widget.deiconify() # Become visible at the desired location
|
||||||
|
|
||||||
|
def go(self):
|
||||||
|
self.root.wait_visibility()
|
||||||
|
self.root.grab_set()
|
||||||
|
self.root.mainloop()
|
||||||
|
self.root.destroy()
|
||||||
|
return self.num
|
||||||
|
|
||||||
|
def return_event(self, event):
|
||||||
|
if self.default is None:
|
||||||
|
self.root.bell()
|
||||||
|
else:
|
||||||
|
self.done(self.default)
|
||||||
|
|
||||||
|
def wm_delete_window(self):
|
||||||
|
if self.cancel is None:
|
||||||
|
self.root.bell()
|
||||||
|
else:
|
||||||
|
self.done(self.cancel)
|
||||||
|
|
||||||
|
def done(self, num):
|
||||||
|
self.num = num
|
||||||
|
self.root.quit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
def test():
|
||||||
|
root = Tk()
|
||||||
|
def doit(root=root):
|
||||||
|
d = SimpleDialog(root,
|
||||||
|
text="This is a test dialog. "
|
||||||
|
"Would this have been an actual dialog, "
|
||||||
|
"the buttons below would have been glowing "
|
||||||
|
"in soft pink light.\n"
|
||||||
|
"Do you believe this?",
|
||||||
|
buttons=["Yes", "No", "Cancel"],
|
||||||
|
default=0,
|
||||||
|
cancel=2,
|
||||||
|
title="Test Dialog")
|
||||||
|
print d.go()
|
||||||
|
t = Button(root, text='Test', command=doit)
|
||||||
|
t.pack()
|
||||||
|
q = Button(root, text='Quit', command=t.quit)
|
||||||
|
q.pack()
|
||||||
|
t.mainloop()
|
||||||
|
|
||||||
|
test()
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,110 @@
|
||||||
|
# Symbolic constants for Tk
|
||||||
|
|
||||||
|
# Booleans
|
||||||
|
NO=FALSE=OFF=0
|
||||||
|
YES=TRUE=ON=1
|
||||||
|
|
||||||
|
# -anchor and -sticky
|
||||||
|
N='n'
|
||||||
|
S='s'
|
||||||
|
W='w'
|
||||||
|
E='e'
|
||||||
|
NW='nw'
|
||||||
|
SW='sw'
|
||||||
|
NE='ne'
|
||||||
|
SE='se'
|
||||||
|
NS='ns'
|
||||||
|
EW='ew'
|
||||||
|
NSEW='nsew'
|
||||||
|
CENTER='center'
|
||||||
|
|
||||||
|
# -fill
|
||||||
|
NONE='none'
|
||||||
|
X='x'
|
||||||
|
Y='y'
|
||||||
|
BOTH='both'
|
||||||
|
|
||||||
|
# -side
|
||||||
|
LEFT='left'
|
||||||
|
TOP='top'
|
||||||
|
RIGHT='right'
|
||||||
|
BOTTOM='bottom'
|
||||||
|
|
||||||
|
# -relief
|
||||||
|
RAISED='raised'
|
||||||
|
SUNKEN='sunken'
|
||||||
|
FLAT='flat'
|
||||||
|
RIDGE='ridge'
|
||||||
|
GROOVE='groove'
|
||||||
|
SOLID = 'solid'
|
||||||
|
|
||||||
|
# -orient
|
||||||
|
HORIZONTAL='horizontal'
|
||||||
|
VERTICAL='vertical'
|
||||||
|
|
||||||
|
# -tabs
|
||||||
|
NUMERIC='numeric'
|
||||||
|
|
||||||
|
# -wrap
|
||||||
|
CHAR='char'
|
||||||
|
WORD='word'
|
||||||
|
|
||||||
|
# -align
|
||||||
|
BASELINE='baseline'
|
||||||
|
|
||||||
|
# -bordermode
|
||||||
|
INSIDE='inside'
|
||||||
|
OUTSIDE='outside'
|
||||||
|
|
||||||
|
# Special tags, marks and insert positions
|
||||||
|
SEL='sel'
|
||||||
|
SEL_FIRST='sel.first'
|
||||||
|
SEL_LAST='sel.last'
|
||||||
|
END='end'
|
||||||
|
INSERT='insert'
|
||||||
|
CURRENT='current'
|
||||||
|
ANCHOR='anchor'
|
||||||
|
ALL='all' # e.g. Canvas.delete(ALL)
|
||||||
|
|
||||||
|
# Text widget and button states
|
||||||
|
NORMAL='normal'
|
||||||
|
DISABLED='disabled'
|
||||||
|
ACTIVE='active'
|
||||||
|
# Canvas state
|
||||||
|
HIDDEN='hidden'
|
||||||
|
|
||||||
|
# Menu item types
|
||||||
|
CASCADE='cascade'
|
||||||
|
CHECKBUTTON='checkbutton'
|
||||||
|
COMMAND='command'
|
||||||
|
RADIOBUTTON='radiobutton'
|
||||||
|
SEPARATOR='separator'
|
||||||
|
|
||||||
|
# Selection modes for list boxes
|
||||||
|
SINGLE='single'
|
||||||
|
BROWSE='browse'
|
||||||
|
MULTIPLE='multiple'
|
||||||
|
EXTENDED='extended'
|
||||||
|
|
||||||
|
# Activestyle for list boxes
|
||||||
|
# NONE='none' is also valid
|
||||||
|
DOTBOX='dotbox'
|
||||||
|
UNDERLINE='underline'
|
||||||
|
|
||||||
|
# Various canvas styles
|
||||||
|
PIESLICE='pieslice'
|
||||||
|
CHORD='chord'
|
||||||
|
ARC='arc'
|
||||||
|
FIRST='first'
|
||||||
|
LAST='last'
|
||||||
|
BUTT='butt'
|
||||||
|
PROJECTING='projecting'
|
||||||
|
ROUND='round'
|
||||||
|
BEVEL='bevel'
|
||||||
|
MITER='miter'
|
||||||
|
|
||||||
|
# Arguments to xview/yview
|
||||||
|
MOVETO='moveto'
|
||||||
|
SCROLL='scroll'
|
||||||
|
UNITS='units'
|
||||||
|
PAGES='pages'
|
|
@ -0,0 +1,321 @@
|
||||||
|
"""Drag-and-drop support for Tkinter.
|
||||||
|
|
||||||
|
This is very preliminary. I currently only support dnd *within* one
|
||||||
|
application, between different windows (or within the same window).
|
||||||
|
|
||||||
|
I an trying to make this as generic as possible -- not dependent on
|
||||||
|
the use of a particular widget or icon type, etc. I also hope that
|
||||||
|
this will work with Pmw.
|
||||||
|
|
||||||
|
To enable an object to be dragged, you must create an event binding
|
||||||
|
for it that starts the drag-and-drop process. Typically, you should
|
||||||
|
bind <ButtonPress> to a callback function that you write. The function
|
||||||
|
should call Tkdnd.dnd_start(source, event), where 'source' is the
|
||||||
|
object to be dragged, and 'event' is the event that invoked the call
|
||||||
|
(the argument to your callback function). Even though this is a class
|
||||||
|
instantiation, the returned instance should not be stored -- it will
|
||||||
|
be kept alive automatically for the duration of the drag-and-drop.
|
||||||
|
|
||||||
|
When a drag-and-drop is already in process for the Tk interpreter, the
|
||||||
|
call is *ignored*; this normally averts starting multiple simultaneous
|
||||||
|
dnd processes, e.g. because different button callbacks all
|
||||||
|
dnd_start().
|
||||||
|
|
||||||
|
The object is *not* necessarily a widget -- it can be any
|
||||||
|
application-specific object that is meaningful to potential
|
||||||
|
drag-and-drop targets.
|
||||||
|
|
||||||
|
Potential drag-and-drop targets are discovered as follows. Whenever
|
||||||
|
the mouse moves, and at the start and end of a drag-and-drop move, the
|
||||||
|
Tk widget directly under the mouse is inspected. This is the target
|
||||||
|
widget (not to be confused with the target object, yet to be
|
||||||
|
determined). If there is no target widget, there is no dnd target
|
||||||
|
object. If there is a target widget, and it has an attribute
|
||||||
|
dnd_accept, this should be a function (or any callable object). The
|
||||||
|
function is called as dnd_accept(source, event), where 'source' is the
|
||||||
|
object being dragged (the object passed to dnd_start() above), and
|
||||||
|
'event' is the most recent event object (generally a <Motion> event;
|
||||||
|
it can also be <ButtonPress> or <ButtonRelease>). If the dnd_accept()
|
||||||
|
function returns something other than None, this is the new dnd target
|
||||||
|
object. If dnd_accept() returns None, or if the target widget has no
|
||||||
|
dnd_accept attribute, the target widget's parent is considered as the
|
||||||
|
target widget, and the search for a target object is repeated from
|
||||||
|
there. If necessary, the search is repeated all the way up to the
|
||||||
|
root widget. If none of the target widgets can produce a target
|
||||||
|
object, there is no target object (the target object is None).
|
||||||
|
|
||||||
|
The target object thus produced, if any, is called the new target
|
||||||
|
object. It is compared with the old target object (or None, if there
|
||||||
|
was no old target widget). There are several cases ('source' is the
|
||||||
|
source object, and 'event' is the most recent event object):
|
||||||
|
|
||||||
|
- Both the old and new target objects are None. Nothing happens.
|
||||||
|
|
||||||
|
- The old and new target objects are the same object. Its method
|
||||||
|
dnd_motion(source, event) is called.
|
||||||
|
|
||||||
|
- The old target object was None, and the new target object is not
|
||||||
|
None. The new target object's method dnd_enter(source, event) is
|
||||||
|
called.
|
||||||
|
|
||||||
|
- The new target object is None, and the old target object is not
|
||||||
|
None. The old target object's method dnd_leave(source, event) is
|
||||||
|
called.
|
||||||
|
|
||||||
|
- The old and new target objects differ and neither is None. The old
|
||||||
|
target object's method dnd_leave(source, event), and then the new
|
||||||
|
target object's method dnd_enter(source, event) is called.
|
||||||
|
|
||||||
|
Once this is done, the new target object replaces the old one, and the
|
||||||
|
Tk mainloop proceeds. The return value of the methods mentioned above
|
||||||
|
is ignored; if they raise an exception, the normal exception handling
|
||||||
|
mechanisms take over.
|
||||||
|
|
||||||
|
The drag-and-drop processes can end in two ways: a final target object
|
||||||
|
is selected, or no final target object is selected. When a final
|
||||||
|
target object is selected, it will always have been notified of the
|
||||||
|
potential drop by a call to its dnd_enter() method, as described
|
||||||
|
above, and possibly one or more calls to its dnd_motion() method; its
|
||||||
|
dnd_leave() method has not been called since the last call to
|
||||||
|
dnd_enter(). The target is notified of the drop by a call to its
|
||||||
|
method dnd_commit(source, event).
|
||||||
|
|
||||||
|
If no final target object is selected, and there was an old target
|
||||||
|
object, its dnd_leave(source, event) method is called to complete the
|
||||||
|
dnd sequence.
|
||||||
|
|
||||||
|
Finally, the source object is notified that the drag-and-drop process
|
||||||
|
is over, by a call to source.dnd_end(target, event), specifying either
|
||||||
|
the selected target object, or None if no target object was selected.
|
||||||
|
The source object can use this to implement the commit action; this is
|
||||||
|
sometimes simpler than to do it in the target's dnd_commit(). The
|
||||||
|
target's dnd_commit() method could then simply be aliased to
|
||||||
|
dnd_leave().
|
||||||
|
|
||||||
|
At any time during a dnd sequence, the application can cancel the
|
||||||
|
sequence by calling the cancel() method on the object returned by
|
||||||
|
dnd_start(). This will call dnd_leave() if a target is currently
|
||||||
|
active; it will never call dnd_commit().
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import tkinter
|
||||||
|
|
||||||
|
|
||||||
|
# The factory function
|
||||||
|
|
||||||
|
def dnd_start(source, event):
|
||||||
|
h = DndHandler(source, event)
|
||||||
|
if h.root:
|
||||||
|
return h
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# The class that does the work
|
||||||
|
|
||||||
|
class DndHandler:
|
||||||
|
|
||||||
|
root = None
|
||||||
|
|
||||||
|
def __init__(self, source, event):
|
||||||
|
if event.num > 5:
|
||||||
|
return
|
||||||
|
root = event.widget._root()
|
||||||
|
try:
|
||||||
|
root.__dnd
|
||||||
|
return # Don't start recursive dnd
|
||||||
|
except AttributeError:
|
||||||
|
root.__dnd = self
|
||||||
|
self.root = root
|
||||||
|
self.source = source
|
||||||
|
self.target = None
|
||||||
|
self.initial_button = button = event.num
|
||||||
|
self.initial_widget = widget = event.widget
|
||||||
|
self.release_pattern = "<B%d-ButtonRelease-%d>" % (button, button)
|
||||||
|
self.save_cursor = widget['cursor'] or ""
|
||||||
|
widget.bind(self.release_pattern, self.on_release)
|
||||||
|
widget.bind("<Motion>", self.on_motion)
|
||||||
|
widget['cursor'] = "hand2"
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
root = self.root
|
||||||
|
self.root = None
|
||||||
|
if root:
|
||||||
|
try:
|
||||||
|
del root.__dnd
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_motion(self, event):
|
||||||
|
x, y = event.x_root, event.y_root
|
||||||
|
target_widget = self.initial_widget.winfo_containing(x, y)
|
||||||
|
source = self.source
|
||||||
|
new_target = None
|
||||||
|
while target_widget:
|
||||||
|
try:
|
||||||
|
attr = target_widget.dnd_accept
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
new_target = attr(source, event)
|
||||||
|
if new_target:
|
||||||
|
break
|
||||||
|
target_widget = target_widget.master
|
||||||
|
old_target = self.target
|
||||||
|
if old_target is new_target:
|
||||||
|
if old_target:
|
||||||
|
old_target.dnd_motion(source, event)
|
||||||
|
else:
|
||||||
|
if old_target:
|
||||||
|
self.target = None
|
||||||
|
old_target.dnd_leave(source, event)
|
||||||
|
if new_target:
|
||||||
|
new_target.dnd_enter(source, event)
|
||||||
|
self.target = new_target
|
||||||
|
|
||||||
|
def on_release(self, event):
|
||||||
|
self.finish(event, 1)
|
||||||
|
|
||||||
|
def cancel(self, event=None):
|
||||||
|
self.finish(event, 0)
|
||||||
|
|
||||||
|
def finish(self, event, commit=0):
|
||||||
|
target = self.target
|
||||||
|
source = self.source
|
||||||
|
widget = self.initial_widget
|
||||||
|
root = self.root
|
||||||
|
try:
|
||||||
|
del root.__dnd
|
||||||
|
self.initial_widget.unbind(self.release_pattern)
|
||||||
|
self.initial_widget.unbind("<Motion>")
|
||||||
|
widget['cursor'] = self.save_cursor
|
||||||
|
self.target = self.source = self.initial_widget = self.root = None
|
||||||
|
if target:
|
||||||
|
if commit:
|
||||||
|
target.dnd_commit(source, event)
|
||||||
|
else:
|
||||||
|
target.dnd_leave(source, event)
|
||||||
|
finally:
|
||||||
|
source.dnd_end(target, event)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# The rest is here for testing and demonstration purposes only!
|
||||||
|
|
||||||
|
class Icon:
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.canvas = self.label = self.id = None
|
||||||
|
|
||||||
|
def attach(self, canvas, x=10, y=10):
|
||||||
|
if canvas is self.canvas:
|
||||||
|
self.canvas.coords(self.id, x, y)
|
||||||
|
return
|
||||||
|
if self.canvas:
|
||||||
|
self.detach()
|
||||||
|
if not canvas:
|
||||||
|
return
|
||||||
|
label = tkinter.Label(canvas, text=self.name,
|
||||||
|
borderwidth=2, relief="raised")
|
||||||
|
id = canvas.create_window(x, y, window=label, anchor="nw")
|
||||||
|
self.canvas = canvas
|
||||||
|
self.label = label
|
||||||
|
self.id = id
|
||||||
|
label.bind("<ButtonPress>", self.press)
|
||||||
|
|
||||||
|
def detach(self):
|
||||||
|
canvas = self.canvas
|
||||||
|
if not canvas:
|
||||||
|
return
|
||||||
|
id = self.id
|
||||||
|
label = self.label
|
||||||
|
self.canvas = self.label = self.id = None
|
||||||
|
canvas.delete(id)
|
||||||
|
label.destroy()
|
||||||
|
|
||||||
|
def press(self, event):
|
||||||
|
if dnd_start(self, event):
|
||||||
|
# where the pointer is relative to the label widget:
|
||||||
|
self.x_off = event.x
|
||||||
|
self.y_off = event.y
|
||||||
|
# where the widget is relative to the canvas:
|
||||||
|
self.x_orig, self.y_orig = self.canvas.coords(self.id)
|
||||||
|
|
||||||
|
def move(self, event):
|
||||||
|
x, y = self.where(self.canvas, event)
|
||||||
|
self.canvas.coords(self.id, x, y)
|
||||||
|
|
||||||
|
def putback(self):
|
||||||
|
self.canvas.coords(self.id, self.x_orig, self.y_orig)
|
||||||
|
|
||||||
|
def where(self, canvas, event):
|
||||||
|
# where the corner of the canvas is relative to the screen:
|
||||||
|
x_org = canvas.winfo_rootx()
|
||||||
|
y_org = canvas.winfo_rooty()
|
||||||
|
# where the pointer is relative to the canvas widget:
|
||||||
|
x = event.x_root - x_org
|
||||||
|
y = event.y_root - y_org
|
||||||
|
# compensate for initial pointer offset
|
||||||
|
return x - self.x_off, y - self.y_off
|
||||||
|
|
||||||
|
def dnd_end(self, target, event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Tester:
|
||||||
|
|
||||||
|
def __init__(self, root):
|
||||||
|
self.top = tkinter.Toplevel(root)
|
||||||
|
self.canvas = tkinter.Canvas(self.top, width=100, height=100)
|
||||||
|
self.canvas.pack(fill="both", expand=1)
|
||||||
|
self.canvas.dnd_accept = self.dnd_accept
|
||||||
|
|
||||||
|
def dnd_accept(self, source, event):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def dnd_enter(self, source, event):
|
||||||
|
self.canvas.focus_set() # Show highlight border
|
||||||
|
x, y = source.where(self.canvas, event)
|
||||||
|
x1, y1, x2, y2 = source.canvas.bbox(source.id)
|
||||||
|
dx, dy = x2-x1, y2-y1
|
||||||
|
self.dndid = self.canvas.create_rectangle(x, y, x+dx, y+dy)
|
||||||
|
self.dnd_motion(source, event)
|
||||||
|
|
||||||
|
def dnd_motion(self, source, event):
|
||||||
|
x, y = source.where(self.canvas, event)
|
||||||
|
x1, y1, x2, y2 = self.canvas.bbox(self.dndid)
|
||||||
|
self.canvas.move(self.dndid, x-x1, y-y1)
|
||||||
|
|
||||||
|
def dnd_leave(self, source, event):
|
||||||
|
self.top.focus_set() # Hide highlight border
|
||||||
|
self.canvas.delete(self.dndid)
|
||||||
|
self.dndid = None
|
||||||
|
|
||||||
|
def dnd_commit(self, source, event):
|
||||||
|
self.dnd_leave(source, event)
|
||||||
|
x, y = source.where(self.canvas, event)
|
||||||
|
source.attach(self.canvas, x, y)
|
||||||
|
|
||||||
|
def test():
|
||||||
|
root = tkinter.Tk()
|
||||||
|
root.geometry("+1+1")
|
||||||
|
tkinter.Button(command=root.quit, text="Quit").pack()
|
||||||
|
t1 = Tester(root)
|
||||||
|
t1.top.geometry("+1+60")
|
||||||
|
t2 = Tester(root)
|
||||||
|
t2.top.geometry("+120+60")
|
||||||
|
t3 = Tester(root)
|
||||||
|
t3.top.geometry("+240+60")
|
||||||
|
i1 = Icon("ICON1")
|
||||||
|
i2 = Icon("ICON2")
|
||||||
|
i3 = Icon("ICON3")
|
||||||
|
i1.attach(t1.canvas)
|
||||||
|
i2.attach(t2.canvas)
|
||||||
|
i3.attach(t3.canvas)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,70 @@
|
||||||
|
# tk common colour chooser dialogue
|
||||||
|
#
|
||||||
|
# this module provides an interface to the native color dialogue
|
||||||
|
# available in Tk 4.2 and newer.
|
||||||
|
#
|
||||||
|
# written by Fredrik Lundh, May 1997
|
||||||
|
#
|
||||||
|
# fixed initialcolor handling in August 1998
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# options (all have default values):
|
||||||
|
#
|
||||||
|
# - initialcolor: colour to mark as selected when dialog is displayed
|
||||||
|
# (given as an RGB triplet or a Tk color string)
|
||||||
|
#
|
||||||
|
# - parent: which window to place the dialog on top of
|
||||||
|
#
|
||||||
|
# - title: dialog title
|
||||||
|
#
|
||||||
|
|
||||||
|
from tkinter.commondialog import Dialog
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# color chooser class
|
||||||
|
|
||||||
|
class Chooser(Dialog):
|
||||||
|
"Ask for a color"
|
||||||
|
|
||||||
|
command = "tk_chooseColor"
|
||||||
|
|
||||||
|
def _fixoptions(self):
|
||||||
|
try:
|
||||||
|
# make sure initialcolor is a tk color string
|
||||||
|
color = self.options["initialcolor"]
|
||||||
|
if type(color) == type(()):
|
||||||
|
# assume an RGB triplet
|
||||||
|
self.options["initialcolor"] = "#%02x%02x%02x" % color
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _fixresult(self, widget, result):
|
||||||
|
# to simplify application code, the color chooser returns
|
||||||
|
# an RGB tuple together with the Tk color string
|
||||||
|
if not result:
|
||||||
|
return None, None # canceled
|
||||||
|
r, g, b = widget.winfo_rgb(result)
|
||||||
|
return (r/256, g/256, b/256), result
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# convenience stuff
|
||||||
|
|
||||||
|
def askcolor(color = None, **options):
|
||||||
|
"Ask for a color"
|
||||||
|
|
||||||
|
if color:
|
||||||
|
options = options.copy()
|
||||||
|
options["initialcolor"] = color
|
||||||
|
|
||||||
|
return Chooser(**options).show()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# test stuff
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
print "color", askcolor()
|
|
@ -0,0 +1,60 @@
|
||||||
|
# base class for tk common dialogues
|
||||||
|
#
|
||||||
|
# this module provides a base class for accessing the common
|
||||||
|
# dialogues available in Tk 4.2 and newer. use filedialog,
|
||||||
|
# colorchooser, and messagebox to access the individual
|
||||||
|
# dialogs.
|
||||||
|
#
|
||||||
|
# written by Fredrik Lundh, May 1997
|
||||||
|
#
|
||||||
|
|
||||||
|
from tkinter import *
|
||||||
|
|
||||||
|
class Dialog:
|
||||||
|
|
||||||
|
command = None
|
||||||
|
|
||||||
|
def __init__(self, master=None, **options):
|
||||||
|
|
||||||
|
# FIXME: should this be placed on the module level instead?
|
||||||
|
if TkVersion < 4.2:
|
||||||
|
raise TclError, "this module requires Tk 4.2 or newer"
|
||||||
|
|
||||||
|
self.master = master
|
||||||
|
self.options = options
|
||||||
|
if not master and options.get('parent'):
|
||||||
|
self.master = options['parent']
|
||||||
|
|
||||||
|
def _fixoptions(self):
|
||||||
|
pass # hook
|
||||||
|
|
||||||
|
def _fixresult(self, widget, result):
|
||||||
|
return result # hook
|
||||||
|
|
||||||
|
def show(self, **options):
|
||||||
|
|
||||||
|
# update instance options
|
||||||
|
for k, v in options.items():
|
||||||
|
self.options[k] = v
|
||||||
|
|
||||||
|
self._fixoptions()
|
||||||
|
|
||||||
|
# we need a dummy widget to properly process the options
|
||||||
|
# (at least as long as we use Tkinter 1.63)
|
||||||
|
w = Frame(self.master)
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
s = w.tk.call(self.command, *w._options(self.options))
|
||||||
|
|
||||||
|
s = self._fixresult(w, s)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
|
||||||
|
try:
|
||||||
|
# get rid of the widget
|
||||||
|
w.destroy()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return s
|
|
@ -0,0 +1,215 @@
|
||||||
|
#
|
||||||
|
# Instant Python
|
||||||
|
# $Id: tkFileDialog.py 36560 2004-07-18 06:16:08Z tim_one $
|
||||||
|
#
|
||||||
|
# tk common file dialogues
|
||||||
|
#
|
||||||
|
# this module provides interfaces to the native file dialogues
|
||||||
|
# available in Tk 4.2 and newer, and the directory dialogue available
|
||||||
|
# in Tk 8.3 and newer.
|
||||||
|
#
|
||||||
|
# written by Fredrik Lundh, May 1997.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# options (all have default values):
|
||||||
|
#
|
||||||
|
# - defaultextension: added to filename if not explicitly given
|
||||||
|
#
|
||||||
|
# - filetypes: sequence of (label, pattern) tuples. the same pattern
|
||||||
|
# may occur with several patterns. use "*" as pattern to indicate
|
||||||
|
# all files.
|
||||||
|
#
|
||||||
|
# - initialdir: initial directory. preserved by dialog instance.
|
||||||
|
#
|
||||||
|
# - initialfile: initial file (ignored by the open dialog). preserved
|
||||||
|
# by dialog instance.
|
||||||
|
#
|
||||||
|
# - parent: which window to place the dialog on top of
|
||||||
|
#
|
||||||
|
# - title: dialog title
|
||||||
|
#
|
||||||
|
# - multiple: if true user may select more than one file
|
||||||
|
#
|
||||||
|
# options for the directory chooser:
|
||||||
|
#
|
||||||
|
# - initialdir, parent, title: see above
|
||||||
|
#
|
||||||
|
# - mustexist: if true, user must pick an existing directory
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
from tkCommonDialog import Dialog
|
||||||
|
|
||||||
|
class _Dialog(Dialog):
|
||||||
|
|
||||||
|
def _fixoptions(self):
|
||||||
|
try:
|
||||||
|
# make sure "filetypes" is a tuple
|
||||||
|
self.options["filetypes"] = tuple(self.options["filetypes"])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _fixresult(self, widget, result):
|
||||||
|
if result:
|
||||||
|
# keep directory and filename until next time
|
||||||
|
import os
|
||||||
|
# convert Tcl path objects to strings
|
||||||
|
try:
|
||||||
|
result = result.string
|
||||||
|
except AttributeError:
|
||||||
|
# it already is a string
|
||||||
|
pass
|
||||||
|
path, file = os.path.split(result)
|
||||||
|
self.options["initialdir"] = path
|
||||||
|
self.options["initialfile"] = file
|
||||||
|
self.filename = result # compatibility
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# file dialogs
|
||||||
|
|
||||||
|
class Open(_Dialog):
|
||||||
|
"Ask for a filename to open"
|
||||||
|
|
||||||
|
command = "tk_getOpenFile"
|
||||||
|
|
||||||
|
def _fixresult(self, widget, result):
|
||||||
|
if isinstance(result, tuple):
|
||||||
|
# multiple results:
|
||||||
|
result = tuple([getattr(r, "string", r) for r in result])
|
||||||
|
if result:
|
||||||
|
import os
|
||||||
|
path, file = os.path.split(result[0])
|
||||||
|
self.options["initialdir"] = path
|
||||||
|
# don't set initialfile or filename, as we have multiple of these
|
||||||
|
return result
|
||||||
|
if not widget.tk.wantobjects() and "multiple" in self.options:
|
||||||
|
# Need to split result explicitly
|
||||||
|
return self._fixresult(widget, widget.tk.splitlist(result))
|
||||||
|
return _Dialog._fixresult(self, widget, result)
|
||||||
|
|
||||||
|
class SaveAs(_Dialog):
|
||||||
|
"Ask for a filename to save as"
|
||||||
|
|
||||||
|
command = "tk_getSaveFile"
|
||||||
|
|
||||||
|
|
||||||
|
# the directory dialog has its own _fix routines.
|
||||||
|
class Directory(Dialog):
|
||||||
|
"Ask for a directory"
|
||||||
|
|
||||||
|
command = "tk_chooseDirectory"
|
||||||
|
|
||||||
|
def _fixresult(self, widget, result):
|
||||||
|
if result:
|
||||||
|
# convert Tcl path objects to strings
|
||||||
|
try:
|
||||||
|
result = result.string
|
||||||
|
except AttributeError:
|
||||||
|
# it already is a string
|
||||||
|
pass
|
||||||
|
# keep directory until next time
|
||||||
|
self.options["initialdir"] = result
|
||||||
|
self.directory = result # compatibility
|
||||||
|
return result
|
||||||
|
|
||||||
|
#
|
||||||
|
# convenience stuff
|
||||||
|
|
||||||
|
def askopenfilename(**options):
|
||||||
|
"Ask for a filename to open"
|
||||||
|
|
||||||
|
return Open(**options).show()
|
||||||
|
|
||||||
|
def asksaveasfilename(**options):
|
||||||
|
"Ask for a filename to save as"
|
||||||
|
|
||||||
|
return SaveAs(**options).show()
|
||||||
|
|
||||||
|
def askopenfilenames(**options):
|
||||||
|
"""Ask for multiple filenames to open
|
||||||
|
|
||||||
|
Returns a list of filenames or empty list if
|
||||||
|
cancel button selected
|
||||||
|
"""
|
||||||
|
options["multiple"]=1
|
||||||
|
return Open(**options).show()
|
||||||
|
|
||||||
|
# FIXME: are the following perhaps a bit too convenient?
|
||||||
|
|
||||||
|
def askopenfile(mode = "r", **options):
|
||||||
|
"Ask for a filename to open, and returned the opened file"
|
||||||
|
|
||||||
|
filename = Open(**options).show()
|
||||||
|
if filename:
|
||||||
|
return open(filename, mode)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def askopenfiles(mode = "r", **options):
|
||||||
|
"""Ask for multiple filenames and return the open file
|
||||||
|
objects
|
||||||
|
|
||||||
|
returns a list of open file objects or an empty list if
|
||||||
|
cancel selected
|
||||||
|
"""
|
||||||
|
|
||||||
|
files = askopenfilenames(**options)
|
||||||
|
if files:
|
||||||
|
ofiles=[]
|
||||||
|
for filename in files:
|
||||||
|
ofiles.append(open(filename, mode))
|
||||||
|
files=ofiles
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def asksaveasfile(mode = "w", **options):
|
||||||
|
"Ask for a filename to save as, and returned the opened file"
|
||||||
|
|
||||||
|
filename = SaveAs(**options).show()
|
||||||
|
if filename:
|
||||||
|
return open(filename, mode)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def askdirectory (**options):
|
||||||
|
"Ask for a directory, and return the file name"
|
||||||
|
return Directory(**options).show()
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# test stuff
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Since the file name may contain non-ASCII characters, we need
|
||||||
|
# to find an encoding that likely supports the file name, and
|
||||||
|
# displays correctly on the terminal.
|
||||||
|
|
||||||
|
# Start off with UTF-8
|
||||||
|
enc = "utf-8"
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# See whether CODESET is defined
|
||||||
|
try:
|
||||||
|
import locale
|
||||||
|
locale.setlocale(locale.LC_ALL,'')
|
||||||
|
enc = locale.nl_langinfo(locale.CODESET)
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# dialog for openening files
|
||||||
|
|
||||||
|
openfilename=askopenfilename(filetypes=[("all files", "*")])
|
||||||
|
try:
|
||||||
|
fp=open(openfilename,"r")
|
||||||
|
fp.close()
|
||||||
|
except:
|
||||||
|
print "Could not open File: "
|
||||||
|
print sys.exc_info()[1]
|
||||||
|
|
||||||
|
print "open", openfilename.encode(enc)
|
||||||
|
|
||||||
|
# dialog for saving files
|
||||||
|
|
||||||
|
saveasfilename=asksaveasfilename()
|
||||||
|
print "saveas", saveasfilename.encode(enc)
|
|
@ -0,0 +1,216 @@
|
||||||
|
# Tkinter font wrapper
|
||||||
|
#
|
||||||
|
# written by Fredrik Lundh, February 1998
|
||||||
|
#
|
||||||
|
# FIXME: should add 'displayof' option where relevant (actual, families,
|
||||||
|
# measure, and metrics)
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.9"
|
||||||
|
|
||||||
|
import tkinter
|
||||||
|
|
||||||
|
# weight/slant
|
||||||
|
NORMAL = "normal"
|
||||||
|
ROMAN = "roman"
|
||||||
|
BOLD = "bold"
|
||||||
|
ITALIC = "italic"
|
||||||
|
|
||||||
|
def nametofont(name):
|
||||||
|
"""Given the name of a tk named font, returns a Font representation.
|
||||||
|
"""
|
||||||
|
return Font(name=name, exists=True)
|
||||||
|
|
||||||
|
class Font:
|
||||||
|
|
||||||
|
"""Represents a named font.
|
||||||
|
|
||||||
|
Constructor options are:
|
||||||
|
|
||||||
|
font -- font specifier (name, system font, or (family, size, style)-tuple)
|
||||||
|
name -- name to use for this font configuration (defaults to a unique name)
|
||||||
|
exists -- does a named font by this name already exist?
|
||||||
|
Creates a new named font if False, points to the existing font if True.
|
||||||
|
Raises _tkinter.TclError if the assertion is false.
|
||||||
|
|
||||||
|
the following are ignored if font is specified:
|
||||||
|
|
||||||
|
family -- font 'family', e.g. Courier, Times, Helvetica
|
||||||
|
size -- font size in points
|
||||||
|
weight -- font thickness: NORMAL, BOLD
|
||||||
|
slant -- font slant: ROMAN, ITALIC
|
||||||
|
underline -- font underlining: false (0), true (1)
|
||||||
|
overstrike -- font strikeout: false (0), true (1)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _set(self, kw):
|
||||||
|
options = []
|
||||||
|
for k, v in kw.items():
|
||||||
|
options.append("-"+k)
|
||||||
|
options.append(str(v))
|
||||||
|
return tuple(options)
|
||||||
|
|
||||||
|
def _get(self, args):
|
||||||
|
options = []
|
||||||
|
for k in args:
|
||||||
|
options.append("-"+k)
|
||||||
|
return tuple(options)
|
||||||
|
|
||||||
|
def _mkdict(self, args):
|
||||||
|
options = {}
|
||||||
|
for i in range(0, len(args), 2):
|
||||||
|
options[args[i][1:]] = args[i+1]
|
||||||
|
return options
|
||||||
|
|
||||||
|
def __init__(self, root=None, font=None, name=None, exists=False, **options):
|
||||||
|
if not root:
|
||||||
|
root = tkinter._default_root
|
||||||
|
if font:
|
||||||
|
# get actual settings corresponding to the given font
|
||||||
|
font = root.tk.splitlist(root.tk.call("font", "actual", font))
|
||||||
|
else:
|
||||||
|
font = self._set(options)
|
||||||
|
if not name:
|
||||||
|
name = "font" + str(id(self))
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
if exists:
|
||||||
|
self.delete_font = False
|
||||||
|
# confirm font exists
|
||||||
|
if self.name not in root.tk.call("font", "names"):
|
||||||
|
raise tkinter._tkinter.TclError, "named font %s does not already exist" % (self.name,)
|
||||||
|
# if font config info supplied, apply it
|
||||||
|
if font:
|
||||||
|
root.tk.call("font", "configure", self.name, *font)
|
||||||
|
else:
|
||||||
|
# create new font (raises TclError if the font exists)
|
||||||
|
root.tk.call("font", "create", self.name, *font)
|
||||||
|
self.delete_font = True
|
||||||
|
# backlinks!
|
||||||
|
self._root = root
|
||||||
|
self._split = root.tk.splitlist
|
||||||
|
self._call = root.tk.call
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.name == other.name and isinstance(other, Font)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.cget(key)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.configure(**{key: value})
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
try:
|
||||||
|
if self.delete_font:
|
||||||
|
self._call("font", "delete", self.name)
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
"Return a distinct copy of the current font"
|
||||||
|
return Font(self._root, **self.actual())
|
||||||
|
|
||||||
|
def actual(self, option=None):
|
||||||
|
"Return actual font attributes"
|
||||||
|
if option:
|
||||||
|
return self._call("font", "actual", self.name, "-"+option)
|
||||||
|
else:
|
||||||
|
return self._mkdict(
|
||||||
|
self._split(self._call("font", "actual", self.name))
|
||||||
|
)
|
||||||
|
|
||||||
|
def cget(self, option):
|
||||||
|
"Get font attribute"
|
||||||
|
return self._call("font", "config", self.name, "-"+option)
|
||||||
|
|
||||||
|
def config(self, **options):
|
||||||
|
"Modify font attributes"
|
||||||
|
if options:
|
||||||
|
self._call("font", "config", self.name,
|
||||||
|
*self._set(options))
|
||||||
|
else:
|
||||||
|
return self._mkdict(
|
||||||
|
self._split(self._call("font", "config", self.name))
|
||||||
|
)
|
||||||
|
|
||||||
|
configure = config
|
||||||
|
|
||||||
|
def measure(self, text):
|
||||||
|
"Return text width"
|
||||||
|
return int(self._call("font", "measure", self.name, text))
|
||||||
|
|
||||||
|
def metrics(self, *options):
|
||||||
|
"""Return font metrics.
|
||||||
|
|
||||||
|
For best performance, create a dummy widget
|
||||||
|
using this font before calling this method."""
|
||||||
|
|
||||||
|
if options:
|
||||||
|
return int(
|
||||||
|
self._call("font", "metrics", self.name, self._get(options))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
res = self._split(self._call("font", "metrics", self.name))
|
||||||
|
options = {}
|
||||||
|
for i in range(0, len(res), 2):
|
||||||
|
options[res[i][1:]] = int(res[i+1])
|
||||||
|
return options
|
||||||
|
|
||||||
|
def families(root=None):
|
||||||
|
"Get font families (as a tuple)"
|
||||||
|
if not root:
|
||||||
|
root = tkinter._default_root
|
||||||
|
return root.tk.splitlist(root.tk.call("font", "families"))
|
||||||
|
|
||||||
|
def names(root=None):
|
||||||
|
"Get names of defined fonts (as a tuple)"
|
||||||
|
if not root:
|
||||||
|
root = tkinter._default_root
|
||||||
|
return root.tk.splitlist(root.tk.call("font", "names"))
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# test stuff
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
root = tkinter.Tk()
|
||||||
|
|
||||||
|
# create a font
|
||||||
|
f = Font(family="times", size=30, weight=NORMAL)
|
||||||
|
|
||||||
|
print f.actual()
|
||||||
|
print f.actual("family")
|
||||||
|
print f.actual("weight")
|
||||||
|
|
||||||
|
print f.config()
|
||||||
|
print f.cget("family")
|
||||||
|
print f.cget("weight")
|
||||||
|
|
||||||
|
print names()
|
||||||
|
|
||||||
|
print f.measure("hello"), f.metrics("linespace")
|
||||||
|
|
||||||
|
print f.metrics()
|
||||||
|
|
||||||
|
f = Font(font=("Courier", 20, "bold"))
|
||||||
|
print f.measure("hello"), f.metrics("linespace")
|
||||||
|
|
||||||
|
w = tkinter.Label(root, text="Hello, world", font=f)
|
||||||
|
w.pack()
|
||||||
|
|
||||||
|
w = tkinter.Button(root, text="Quit!", command=root.destroy)
|
||||||
|
w.pack()
|
||||||
|
|
||||||
|
fb = Font(font=w["font"]).copy()
|
||||||
|
fb.config(weight=BOLD)
|
||||||
|
|
||||||
|
w.config(font=fb)
|
||||||
|
|
||||||
|
tkinter.mainloop()
|
|
@ -0,0 +1,132 @@
|
||||||
|
# tk common message boxes
|
||||||
|
#
|
||||||
|
# this module provides an interface to the native message boxes
|
||||||
|
# available in Tk 4.2 and newer.
|
||||||
|
#
|
||||||
|
# written by Fredrik Lundh, May 1997
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# options (all have default values):
|
||||||
|
#
|
||||||
|
# - default: which button to make default (one of the reply codes)
|
||||||
|
#
|
||||||
|
# - icon: which icon to display (see below)
|
||||||
|
#
|
||||||
|
# - message: the message to display
|
||||||
|
#
|
||||||
|
# - parent: which window to place the dialog on top of
|
||||||
|
#
|
||||||
|
# - title: dialog title
|
||||||
|
#
|
||||||
|
# - type: dialog type; that is, which buttons to display (see below)
|
||||||
|
#
|
||||||
|
|
||||||
|
from tkinter.commondialog import Dialog
|
||||||
|
|
||||||
|
#
|
||||||
|
# constants
|
||||||
|
|
||||||
|
# icons
|
||||||
|
ERROR = "error"
|
||||||
|
INFO = "info"
|
||||||
|
QUESTION = "question"
|
||||||
|
WARNING = "warning"
|
||||||
|
|
||||||
|
# types
|
||||||
|
ABORTRETRYIGNORE = "abortretryignore"
|
||||||
|
OK = "ok"
|
||||||
|
OKCANCEL = "okcancel"
|
||||||
|
RETRYCANCEL = "retrycancel"
|
||||||
|
YESNO = "yesno"
|
||||||
|
YESNOCANCEL = "yesnocancel"
|
||||||
|
|
||||||
|
# replies
|
||||||
|
ABORT = "abort"
|
||||||
|
RETRY = "retry"
|
||||||
|
IGNORE = "ignore"
|
||||||
|
OK = "ok"
|
||||||
|
CANCEL = "cancel"
|
||||||
|
YES = "yes"
|
||||||
|
NO = "no"
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# message dialog class
|
||||||
|
|
||||||
|
class Message(Dialog):
|
||||||
|
"A message box"
|
||||||
|
|
||||||
|
command = "tk_messageBox"
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# convenience stuff
|
||||||
|
|
||||||
|
# Rename _icon and _type options to allow overriding them in options
|
||||||
|
def _show(title=None, message=None, _icon=None, _type=None, **options):
|
||||||
|
if _icon and "icon" not in options: options["icon"] = _icon
|
||||||
|
if _type and "type" not in options: options["type"] = _type
|
||||||
|
if title: options["title"] = title
|
||||||
|
if message: options["message"] = message
|
||||||
|
res = Message(**options).show()
|
||||||
|
# In some Tcl installations, Tcl converts yes/no into a boolean
|
||||||
|
if isinstance(res, bool):
|
||||||
|
if res: return YES
|
||||||
|
return NO
|
||||||
|
return res
|
||||||
|
|
||||||
|
def showinfo(title=None, message=None, **options):
|
||||||
|
"Show an info message"
|
||||||
|
return _show(title, message, INFO, OK, **options)
|
||||||
|
|
||||||
|
def showwarning(title=None, message=None, **options):
|
||||||
|
"Show a warning message"
|
||||||
|
return _show(title, message, WARNING, OK, **options)
|
||||||
|
|
||||||
|
def showerror(title=None, message=None, **options):
|
||||||
|
"Show an error message"
|
||||||
|
return _show(title, message, ERROR, OK, **options)
|
||||||
|
|
||||||
|
def askquestion(title=None, message=None, **options):
|
||||||
|
"Ask a question"
|
||||||
|
return _show(title, message, QUESTION, YESNO, **options)
|
||||||
|
|
||||||
|
def askokcancel(title=None, message=None, **options):
|
||||||
|
"Ask if operation should proceed; return true if the answer is ok"
|
||||||
|
s = _show(title, message, QUESTION, OKCANCEL, **options)
|
||||||
|
return s == OK
|
||||||
|
|
||||||
|
def askyesno(title=None, message=None, **options):
|
||||||
|
"Ask a question; return true if the answer is yes"
|
||||||
|
s = _show(title, message, QUESTION, YESNO, **options)
|
||||||
|
return s == YES
|
||||||
|
|
||||||
|
def askyesnocancel(title=None, message=None, **options):
|
||||||
|
"Ask a question; return true if the answer is yes, None if cancelled."
|
||||||
|
s = _show(title, message, QUESTION, YESNOCANCEL, **options)
|
||||||
|
# s might be a Tcl index object, so convert it to a string
|
||||||
|
s = str(s)
|
||||||
|
if s == CANCEL:
|
||||||
|
return None
|
||||||
|
return s == YES
|
||||||
|
|
||||||
|
def askretrycancel(title=None, message=None, **options):
|
||||||
|
"Ask if operation should be retried; return true if the answer is yes"
|
||||||
|
s = _show(title, message, WARNING, RETRYCANCEL, **options)
|
||||||
|
return s == RETRY
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# test stuff
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
print "info", showinfo("Spam", "Egg Information")
|
||||||
|
print "warning", showwarning("Spam", "Egg Warning")
|
||||||
|
print "error", showerror("Spam", "Egg Alert")
|
||||||
|
print "question", askquestion("Spam", "Question?")
|
||||||
|
print "proceed", askokcancel("Spam", "Proceed?")
|
||||||
|
print "yes/no", askyesno("Spam", "Got it?")
|
||||||
|
print "yes/no/cancel", askyesnocancel("Spam", "Want it?")
|
||||||
|
print "try again", askretrycancel("Spam", "Try again?")
|
|
@ -0,0 +1,319 @@
|
||||||
|
#
|
||||||
|
# An Introduction to Tkinter
|
||||||
|
# tkSimpleDialog.py
|
||||||
|
#
|
||||||
|
# Copyright (c) 1997 by Fredrik Lundh
|
||||||
|
#
|
||||||
|
# fredrik@pythonware.com
|
||||||
|
# http://www.pythonware.com
|
||||||
|
#
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# dialog base class
|
||||||
|
|
||||||
|
'''Dialog boxes
|
||||||
|
|
||||||
|
This module handles dialog boxes. It contains the following
|
||||||
|
public symbols:
|
||||||
|
|
||||||
|
Dialog -- a base class for dialogs
|
||||||
|
|
||||||
|
askinteger -- get an integer from the user
|
||||||
|
|
||||||
|
askfloat -- get a float from the user
|
||||||
|
|
||||||
|
askstring -- get a string from the user
|
||||||
|
'''
|
||||||
|
|
||||||
|
from Tkinter import *
|
||||||
|
|
||||||
|
class Dialog(Toplevel):
|
||||||
|
|
||||||
|
'''Class to open dialogs.
|
||||||
|
|
||||||
|
This class is intended as a base class for custom dialogs
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, parent, title = None):
|
||||||
|
|
||||||
|
'''Initialize a dialog.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
parent -- a parent window (the application window)
|
||||||
|
|
||||||
|
title -- the dialog title
|
||||||
|
'''
|
||||||
|
Toplevel.__init__(self, parent)
|
||||||
|
|
||||||
|
# If the master is not viewable, don't
|
||||||
|
# make the child transient, or else it
|
||||||
|
# would be opened withdrawn
|
||||||
|
if parent.winfo_viewable():
|
||||||
|
self.transient(parent)
|
||||||
|
|
||||||
|
if title:
|
||||||
|
self.title(title)
|
||||||
|
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
self.result = None
|
||||||
|
|
||||||
|
body = Frame(self)
|
||||||
|
self.initial_focus = self.body(body)
|
||||||
|
body.pack(padx=5, pady=5)
|
||||||
|
|
||||||
|
self.buttonbox()
|
||||||
|
|
||||||
|
self.wait_visibility() # window needs to be visible for the grab
|
||||||
|
self.grab_set()
|
||||||
|
|
||||||
|
if not self.initial_focus:
|
||||||
|
self.initial_focus = self
|
||||||
|
|
||||||
|
self.protocol("WM_DELETE_WINDOW", self.cancel)
|
||||||
|
|
||||||
|
if self.parent is not None:
|
||||||
|
self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
|
||||||
|
parent.winfo_rooty()+50))
|
||||||
|
|
||||||
|
self.initial_focus.focus_set()
|
||||||
|
|
||||||
|
self.wait_window(self)
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
'''Destroy the window'''
|
||||||
|
self.initial_focus = None
|
||||||
|
Toplevel.destroy(self)
|
||||||
|
|
||||||
|
#
|
||||||
|
# construction hooks
|
||||||
|
|
||||||
|
def body(self, master):
|
||||||
|
'''create dialog body.
|
||||||
|
|
||||||
|
return widget that should have initial focus.
|
||||||
|
This method should be overridden, and is called
|
||||||
|
by the __init__ method.
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
def buttonbox(self):
|
||||||
|
'''add standard button box.
|
||||||
|
|
||||||
|
override if you do not want the standard buttons
|
||||||
|
'''
|
||||||
|
|
||||||
|
box = Frame(self)
|
||||||
|
|
||||||
|
w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
|
||||||
|
w.pack(side=LEFT, padx=5, pady=5)
|
||||||
|
w = Button(box, text="Cancel", width=10, command=self.cancel)
|
||||||
|
w.pack(side=LEFT, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.bind("<Return>", self.ok)
|
||||||
|
self.bind("<Escape>", self.cancel)
|
||||||
|
|
||||||
|
box.pack()
|
||||||
|
|
||||||
|
#
|
||||||
|
# standard button semantics
|
||||||
|
|
||||||
|
def ok(self, event=None):
|
||||||
|
|
||||||
|
if not self.validate():
|
||||||
|
self.initial_focus.focus_set() # put focus back
|
||||||
|
return
|
||||||
|
|
||||||
|
self.withdraw()
|
||||||
|
self.update_idletasks()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.apply()
|
||||||
|
finally:
|
||||||
|
self.cancel()
|
||||||
|
|
||||||
|
def cancel(self, event=None):
|
||||||
|
|
||||||
|
# put focus back to the parent window
|
||||||
|
if self.parent is not None:
|
||||||
|
self.parent.focus_set()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
#
|
||||||
|
# command hooks
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
'''validate the data
|
||||||
|
|
||||||
|
This method is called automatically to validate the data before the
|
||||||
|
dialog is destroyed. By default, it always validates OK.
|
||||||
|
'''
|
||||||
|
|
||||||
|
return 1 # override
|
||||||
|
|
||||||
|
def apply(self):
|
||||||
|
'''process the data
|
||||||
|
|
||||||
|
This method is called automatically to process the data, *after*
|
||||||
|
the dialog is destroyed. By default, it does nothing.
|
||||||
|
'''
|
||||||
|
|
||||||
|
pass # override
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# convenience dialogues
|
||||||
|
|
||||||
|
class _QueryDialog(Dialog):
|
||||||
|
|
||||||
|
def __init__(self, title, prompt,
|
||||||
|
initialvalue=None,
|
||||||
|
minvalue = None, maxvalue = None,
|
||||||
|
parent = None):
|
||||||
|
|
||||||
|
if not parent:
|
||||||
|
import Tkinter
|
||||||
|
parent = Tkinter._default_root
|
||||||
|
|
||||||
|
self.prompt = prompt
|
||||||
|
self.minvalue = minvalue
|
||||||
|
self.maxvalue = maxvalue
|
||||||
|
|
||||||
|
self.initialvalue = initialvalue
|
||||||
|
|
||||||
|
Dialog.__init__(self, parent, title)
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
self.entry = None
|
||||||
|
Dialog.destroy(self)
|
||||||
|
|
||||||
|
def body(self, master):
|
||||||
|
|
||||||
|
w = Label(master, text=self.prompt, justify=LEFT)
|
||||||
|
w.grid(row=0, padx=5, sticky=W)
|
||||||
|
|
||||||
|
self.entry = Entry(master, name="entry")
|
||||||
|
self.entry.grid(row=1, padx=5, sticky=W+E)
|
||||||
|
|
||||||
|
if self.initialvalue:
|
||||||
|
self.entry.insert(0, self.initialvalue)
|
||||||
|
self.entry.select_range(0, END)
|
||||||
|
|
||||||
|
return self.entry
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
|
||||||
|
import tkMessageBox
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = self.getresult()
|
||||||
|
except ValueError:
|
||||||
|
tkMessageBox.showwarning(
|
||||||
|
"Illegal value",
|
||||||
|
self.errormessage + "\nPlease try again",
|
||||||
|
parent = self
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if self.minvalue is not None and result < self.minvalue:
|
||||||
|
tkMessageBox.showwarning(
|
||||||
|
"Too small",
|
||||||
|
"The allowed minimum value is %s. "
|
||||||
|
"Please try again." % self.minvalue,
|
||||||
|
parent = self
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if self.maxvalue is not None and result > self.maxvalue:
|
||||||
|
tkMessageBox.showwarning(
|
||||||
|
"Too large",
|
||||||
|
"The allowed maximum value is %s. "
|
||||||
|
"Please try again." % self.maxvalue,
|
||||||
|
parent = self
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
self.result = result
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class _QueryInteger(_QueryDialog):
|
||||||
|
errormessage = "Not an integer."
|
||||||
|
def getresult(self):
|
||||||
|
return int(self.entry.get())
|
||||||
|
|
||||||
|
def askinteger(title, prompt, **kw):
|
||||||
|
'''get an integer from the user
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
title -- the dialog title
|
||||||
|
prompt -- the label text
|
||||||
|
**kw -- see SimpleDialog class
|
||||||
|
|
||||||
|
Return value is an integer
|
||||||
|
'''
|
||||||
|
d = _QueryInteger(title, prompt, **kw)
|
||||||
|
return d.result
|
||||||
|
|
||||||
|
class _QueryFloat(_QueryDialog):
|
||||||
|
errormessage = "Not a floating point value."
|
||||||
|
def getresult(self):
|
||||||
|
return float(self.entry.get())
|
||||||
|
|
||||||
|
def askfloat(title, prompt, **kw):
|
||||||
|
'''get a float from the user
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
title -- the dialog title
|
||||||
|
prompt -- the label text
|
||||||
|
**kw -- see SimpleDialog class
|
||||||
|
|
||||||
|
Return value is a float
|
||||||
|
'''
|
||||||
|
d = _QueryFloat(title, prompt, **kw)
|
||||||
|
return d.result
|
||||||
|
|
||||||
|
class _QueryString(_QueryDialog):
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
if kw.has_key("show"):
|
||||||
|
self.__show = kw["show"]
|
||||||
|
del kw["show"]
|
||||||
|
else:
|
||||||
|
self.__show = None
|
||||||
|
_QueryDialog.__init__(self, *args, **kw)
|
||||||
|
|
||||||
|
def body(self, master):
|
||||||
|
entry = _QueryDialog.body(self, master)
|
||||||
|
if self.__show is not None:
|
||||||
|
entry.configure(show=self.__show)
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def getresult(self):
|
||||||
|
return self.entry.get()
|
||||||
|
|
||||||
|
def askstring(title, prompt, **kw):
|
||||||
|
'''get a string from the user
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
title -- the dialog title
|
||||||
|
prompt -- the label text
|
||||||
|
**kw -- see SimpleDialog class
|
||||||
|
|
||||||
|
Return value is a string
|
||||||
|
'''
|
||||||
|
d = _QueryString(title, prompt, **kw)
|
||||||
|
return d.result
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
root = Tk()
|
||||||
|
root.update()
|
||||||
|
|
||||||
|
print askinteger("Spam", "Egg count", initialvalue=12*12)
|
||||||
|
print askfloat("Spam", "Egg weight\n(in tons)", minvalue=1, maxvalue=100)
|
||||||
|
print askstring("Spam", "Egg label")
|
|
@ -0,0 +1,956 @@
|
||||||
|
# LogoMation-like turtle graphics
|
||||||
|
|
||||||
|
"""
|
||||||
|
Turtle graphics is a popular way for introducing programming to
|
||||||
|
kids. It was part of the original Logo programming language developed
|
||||||
|
by Wally Feurzeig and Seymour Papert in 1966.
|
||||||
|
|
||||||
|
Imagine a robotic turtle starting at (0, 0) in the x-y plane. Give it
|
||||||
|
the command turtle.forward(15), and it moves (on-screen!) 15 pixels in
|
||||||
|
the direction it is facing, drawing a line as it moves. Give it the
|
||||||
|
command turtle.left(25), and it rotates in-place 25 degrees clockwise.
|
||||||
|
|
||||||
|
By combining together these and similar commands, intricate shapes and
|
||||||
|
pictures can easily be drawn.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from math import * # Also for export
|
||||||
|
from time import sleep
|
||||||
|
import tkinter
|
||||||
|
|
||||||
|
speeds = ['fastest', 'fast', 'normal', 'slow', 'slowest']
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class RawPen:
|
||||||
|
|
||||||
|
def __init__(self, canvas):
|
||||||
|
self._canvas = canvas
|
||||||
|
self._items = []
|
||||||
|
self._tracing = 1
|
||||||
|
self._arrow = 0
|
||||||
|
self._delay = 10 # default delay for drawing
|
||||||
|
self._angle = 0.0
|
||||||
|
self.degrees()
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def degrees(self, fullcircle=360.0):
|
||||||
|
""" Set angle measurement units to degrees.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.degrees()
|
||||||
|
"""
|
||||||
|
# Don't try to change _angle if it is 0, because
|
||||||
|
# _fullcircle might not be set, yet
|
||||||
|
if self._angle:
|
||||||
|
self._angle = (self._angle / self._fullcircle) * fullcircle
|
||||||
|
self._fullcircle = fullcircle
|
||||||
|
self._invradian = pi / (fullcircle * 0.5)
|
||||||
|
|
||||||
|
def radians(self):
|
||||||
|
""" Set the angle measurement units to radians.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.radians()
|
||||||
|
"""
|
||||||
|
self.degrees(2.0*pi)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
""" Clear the screen, re-center the pen, and set variables to
|
||||||
|
the default values.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.position()
|
||||||
|
[0.0, -22.0]
|
||||||
|
>>> turtle.heading()
|
||||||
|
100.0
|
||||||
|
>>> turtle.reset()
|
||||||
|
>>> turtle.position()
|
||||||
|
[0.0, 0.0]
|
||||||
|
>>> turtle.heading()
|
||||||
|
0.0
|
||||||
|
"""
|
||||||
|
canvas = self._canvas
|
||||||
|
self._canvas.update()
|
||||||
|
width = canvas.winfo_width()
|
||||||
|
height = canvas.winfo_height()
|
||||||
|
if width <= 1:
|
||||||
|
width = canvas['width']
|
||||||
|
if height <= 1:
|
||||||
|
height = canvas['height']
|
||||||
|
self._origin = float(width)/2.0, float(height)/2.0
|
||||||
|
self._position = self._origin
|
||||||
|
self._angle = 0.0
|
||||||
|
self._drawing = 1
|
||||||
|
self._width = 1
|
||||||
|
self._color = "black"
|
||||||
|
self._filling = 0
|
||||||
|
self._path = []
|
||||||
|
self.clear()
|
||||||
|
canvas._root().tkraise()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
""" Clear the screen. The turtle does not move.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.clear()
|
||||||
|
"""
|
||||||
|
self.fill(0)
|
||||||
|
canvas = self._canvas
|
||||||
|
items = self._items
|
||||||
|
self._items = []
|
||||||
|
for item in items:
|
||||||
|
canvas.delete(item)
|
||||||
|
self._delete_turtle()
|
||||||
|
self._draw_turtle()
|
||||||
|
|
||||||
|
def tracer(self, flag):
|
||||||
|
""" Set tracing on if flag is True, and off if it is False.
|
||||||
|
Tracing means line are drawn more slowly, with an
|
||||||
|
animation of an arrow along the line.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.tracer(False) # turns off Tracer
|
||||||
|
"""
|
||||||
|
self._tracing = flag
|
||||||
|
if not self._tracing:
|
||||||
|
self._delete_turtle()
|
||||||
|
self._draw_turtle()
|
||||||
|
|
||||||
|
def forward(self, distance):
|
||||||
|
""" Go forward distance steps.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.position()
|
||||||
|
[0.0, 0.0]
|
||||||
|
>>> turtle.forward(25)
|
||||||
|
>>> turtle.position()
|
||||||
|
[25.0, 0.0]
|
||||||
|
>>> turtle.forward(-75)
|
||||||
|
>>> turtle.position()
|
||||||
|
[-50.0, 0.0]
|
||||||
|
"""
|
||||||
|
x0, y0 = start = self._position
|
||||||
|
x1 = x0 + distance * cos(self._angle*self._invradian)
|
||||||
|
y1 = y0 - distance * sin(self._angle*self._invradian)
|
||||||
|
self._goto(x1, y1)
|
||||||
|
|
||||||
|
def backward(self, distance):
|
||||||
|
""" Go backwards distance steps.
|
||||||
|
|
||||||
|
The turtle's heading does not change.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.position()
|
||||||
|
[0.0, 0.0]
|
||||||
|
>>> turtle.backward(30)
|
||||||
|
>>> turtle.position()
|
||||||
|
[-30.0, 0.0]
|
||||||
|
"""
|
||||||
|
self.forward(-distance)
|
||||||
|
|
||||||
|
def left(self, angle):
|
||||||
|
""" Turn left angle units (units are by default degrees,
|
||||||
|
but can be set via the degrees() and radians() functions.)
|
||||||
|
|
||||||
|
When viewed from above, the turning happens in-place around
|
||||||
|
its front tip.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.heading()
|
||||||
|
22
|
||||||
|
>>> turtle.left(45)
|
||||||
|
>>> turtle.heading()
|
||||||
|
67.0
|
||||||
|
"""
|
||||||
|
self._angle = (self._angle + angle) % self._fullcircle
|
||||||
|
self._draw_turtle()
|
||||||
|
|
||||||
|
def right(self, angle):
|
||||||
|
""" Turn right angle units (units are by default degrees,
|
||||||
|
but can be set via the degrees() and radians() functions.)
|
||||||
|
|
||||||
|
When viewed from above, the turning happens in-place around
|
||||||
|
its front tip.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.heading()
|
||||||
|
22
|
||||||
|
>>> turtle.right(45)
|
||||||
|
>>> turtle.heading()
|
||||||
|
337.0
|
||||||
|
"""
|
||||||
|
self.left(-angle)
|
||||||
|
|
||||||
|
def up(self):
|
||||||
|
""" Pull the pen up -- no drawing when moving.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.up()
|
||||||
|
"""
|
||||||
|
self._drawing = 0
|
||||||
|
|
||||||
|
def down(self):
|
||||||
|
""" Put the pen down -- draw when moving.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.down()
|
||||||
|
"""
|
||||||
|
self._drawing = 1
|
||||||
|
|
||||||
|
def width(self, width):
|
||||||
|
""" Set the line to thickness to width.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.width(10)
|
||||||
|
"""
|
||||||
|
self._width = float(width)
|
||||||
|
|
||||||
|
def color(self, *args):
|
||||||
|
""" Set the pen color.
|
||||||
|
|
||||||
|
Three input formats are allowed:
|
||||||
|
|
||||||
|
color(s)
|
||||||
|
s is a Tk specification string, such as "red" or "yellow"
|
||||||
|
|
||||||
|
color((r, g, b))
|
||||||
|
*a tuple* of r, g, and b, which represent, an RGB color,
|
||||||
|
and each of r, g, and b are in the range [0..1]
|
||||||
|
|
||||||
|
color(r, g, b)
|
||||||
|
r, g, and b represent an RGB color, and each of r, g, and b
|
||||||
|
are in the range [0..1]
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
>>> turtle.color('brown')
|
||||||
|
>>> tup = (0.2, 0.8, 0.55)
|
||||||
|
>>> turtle.color(tup)
|
||||||
|
>>> turtle.color(0, .5, 0)
|
||||||
|
"""
|
||||||
|
if not args:
|
||||||
|
raise Error, "no color arguments"
|
||||||
|
if len(args) == 1:
|
||||||
|
color = args[0]
|
||||||
|
if type(color) == type(""):
|
||||||
|
# Test the color first
|
||||||
|
try:
|
||||||
|
id = self._canvas.create_line(0, 0, 0, 0, fill=color)
|
||||||
|
except tkinter.TclError:
|
||||||
|
raise Error, "bad color string: %r" % (color,)
|
||||||
|
self._set_color(color)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
r, g, b = color
|
||||||
|
except:
|
||||||
|
raise Error, "bad color sequence: %r" % (color,)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
r, g, b = args
|
||||||
|
except:
|
||||||
|
raise Error, "bad color arguments: %r" % (args,)
|
||||||
|
assert 0 <= r <= 1
|
||||||
|
assert 0 <= g <= 1
|
||||||
|
assert 0 <= b <= 1
|
||||||
|
x = 255.0
|
||||||
|
y = 0.5
|
||||||
|
self._set_color("#%02x%02x%02x" % (int(r*x+y), int(g*x+y), int(b*x+y)))
|
||||||
|
|
||||||
|
def _set_color(self,color):
|
||||||
|
self._color = color
|
||||||
|
self._draw_turtle()
|
||||||
|
|
||||||
|
def write(self, text, move=False):
|
||||||
|
""" Write text at the current pen position.
|
||||||
|
|
||||||
|
If move is true, the pen is moved to the bottom-right corner
|
||||||
|
of the text. By default, move is False.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.write('The race is on!')
|
||||||
|
>>> turtle.write('Home = (0, 0)', True)
|
||||||
|
"""
|
||||||
|
x, y = self._position
|
||||||
|
x = x-1 # correction -- calibrated for Windows
|
||||||
|
item = self._canvas.create_text(x, y,
|
||||||
|
text=str(text), anchor="sw",
|
||||||
|
fill=self._color)
|
||||||
|
self._items.append(item)
|
||||||
|
if move:
|
||||||
|
x0, y0, x1, y1 = self._canvas.bbox(item)
|
||||||
|
self._goto(x1, y1)
|
||||||
|
self._draw_turtle()
|
||||||
|
|
||||||
|
def fill(self, flag):
|
||||||
|
""" Call fill(1) before drawing the shape you
|
||||||
|
want to fill, and fill(0) when done.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.fill(1)
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.left(90)
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.left(90)
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.left(90)
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.fill(0)
|
||||||
|
"""
|
||||||
|
if self._filling:
|
||||||
|
path = tuple(self._path)
|
||||||
|
smooth = self._filling < 0
|
||||||
|
if len(path) > 2:
|
||||||
|
item = self._canvas._create('polygon', path,
|
||||||
|
{'fill': self._color,
|
||||||
|
'smooth': smooth})
|
||||||
|
self._items.append(item)
|
||||||
|
self._path = []
|
||||||
|
self._filling = flag
|
||||||
|
if flag:
|
||||||
|
self._path.append(self._position)
|
||||||
|
|
||||||
|
def begin_fill(self):
|
||||||
|
""" Called just before drawing a shape to be filled.
|
||||||
|
Must eventually be followed by a corresponding end_fill() call.
|
||||||
|
Otherwise it will be ignored.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.begin_fill()
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.left(90)
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.left(90)
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.left(90)
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.end_fill()
|
||||||
|
"""
|
||||||
|
self._path = [self._position]
|
||||||
|
self._filling = 1
|
||||||
|
|
||||||
|
def end_fill(self):
|
||||||
|
""" Called after drawing a shape to be filled.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.begin_fill()
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.left(90)
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.left(90)
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.left(90)
|
||||||
|
>>> turtle.forward(100)
|
||||||
|
>>> turtle.end_fill()
|
||||||
|
"""
|
||||||
|
self.fill(0)
|
||||||
|
|
||||||
|
def circle(self, radius, extent = None):
|
||||||
|
""" Draw a circle with given radius.
|
||||||
|
The center is radius units left of the turtle; extent
|
||||||
|
determines which part of the circle is drawn. If not given,
|
||||||
|
the entire circle is drawn.
|
||||||
|
|
||||||
|
If extent is not a full circle, one endpoint of the arc is the
|
||||||
|
current pen position. The arc is drawn in a counter clockwise
|
||||||
|
direction if radius is positive, otherwise in a clockwise
|
||||||
|
direction. In the process, the direction of the turtle is
|
||||||
|
changed by the amount of the extent.
|
||||||
|
|
||||||
|
>>> turtle.circle(50)
|
||||||
|
>>> turtle.circle(120, 180) # half a circle
|
||||||
|
"""
|
||||||
|
if extent is None:
|
||||||
|
extent = self._fullcircle
|
||||||
|
frac = abs(extent)/self._fullcircle
|
||||||
|
steps = 1+int(min(11+abs(radius)/6.0, 59.0)*frac)
|
||||||
|
w = 1.0 * extent / steps
|
||||||
|
w2 = 0.5 * w
|
||||||
|
l = 2.0 * radius * sin(w2*self._invradian)
|
||||||
|
if radius < 0:
|
||||||
|
l, w, w2 = -l, -w, -w2
|
||||||
|
self.left(w2)
|
||||||
|
for i in range(steps):
|
||||||
|
self.forward(l)
|
||||||
|
self.left(w)
|
||||||
|
self.right(w2)
|
||||||
|
|
||||||
|
def heading(self):
|
||||||
|
""" Return the turtle's current heading.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.heading()
|
||||||
|
67.0
|
||||||
|
"""
|
||||||
|
return self._angle
|
||||||
|
|
||||||
|
def setheading(self, angle):
|
||||||
|
""" Set the turtle facing the given angle.
|
||||||
|
|
||||||
|
Here are some common directions in degrees:
|
||||||
|
|
||||||
|
0 - east
|
||||||
|
90 - north
|
||||||
|
180 - west
|
||||||
|
270 - south
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.setheading(90)
|
||||||
|
>>> turtle.heading()
|
||||||
|
90
|
||||||
|
>>> turtle.setheading(128)
|
||||||
|
>>> turtle.heading()
|
||||||
|
128
|
||||||
|
"""
|
||||||
|
self._angle = angle
|
||||||
|
self._draw_turtle()
|
||||||
|
|
||||||
|
def window_width(self):
|
||||||
|
""" Returns the width of the turtle window.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.window_width()
|
||||||
|
640
|
||||||
|
"""
|
||||||
|
width = self._canvas.winfo_width()
|
||||||
|
if width <= 1: # the window isn't managed by a geometry manager
|
||||||
|
width = self._canvas['width']
|
||||||
|
return width
|
||||||
|
|
||||||
|
def window_height(self):
|
||||||
|
""" Return the height of the turtle window.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.window_height()
|
||||||
|
768
|
||||||
|
"""
|
||||||
|
height = self._canvas.winfo_height()
|
||||||
|
if height <= 1: # the window isn't managed by a geometry manager
|
||||||
|
height = self._canvas['height']
|
||||||
|
return height
|
||||||
|
|
||||||
|
def position(self):
|
||||||
|
""" Return the current (x, y) location of the turtle.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.position()
|
||||||
|
[0.0, 240.0]
|
||||||
|
"""
|
||||||
|
x0, y0 = self._origin
|
||||||
|
x1, y1 = self._position
|
||||||
|
return [x1-x0, -y1+y0]
|
||||||
|
|
||||||
|
def setx(self, xpos):
|
||||||
|
""" Set the turtle's x coordinate to be xpos.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.position()
|
||||||
|
[10.0, 240.0]
|
||||||
|
>>> turtle.setx(10)
|
||||||
|
>>> turtle.position()
|
||||||
|
[10.0, 240.0]
|
||||||
|
"""
|
||||||
|
x0, y0 = self._origin
|
||||||
|
x1, y1 = self._position
|
||||||
|
self._goto(x0+xpos, y1)
|
||||||
|
|
||||||
|
def sety(self, ypos):
|
||||||
|
""" Set the turtle's y coordinate to be ypos.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.position()
|
||||||
|
[0.0, 0.0]
|
||||||
|
>>> turtle.sety(-22)
|
||||||
|
>>> turtle.position()
|
||||||
|
[0.0, -22.0]
|
||||||
|
"""
|
||||||
|
x0, y0 = self._origin
|
||||||
|
x1, y1 = self._position
|
||||||
|
self._goto(x1, y0-ypos)
|
||||||
|
|
||||||
|
def towards(self, *args):
|
||||||
|
"""Returs the angle, which corresponds to the line
|
||||||
|
from turtle-position to point (x,y).
|
||||||
|
|
||||||
|
Argument can be two coordinates or one pair of coordinates
|
||||||
|
or a RawPen/Pen instance.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.position()
|
||||||
|
[10.0, 10.0]
|
||||||
|
>>> turtle.towards(0,0)
|
||||||
|
225.0
|
||||||
|
"""
|
||||||
|
if len(args) == 2:
|
||||||
|
x, y = args
|
||||||
|
else:
|
||||||
|
arg = args[0]
|
||||||
|
if isinstance(arg, RawPen):
|
||||||
|
x, y = arg.position()
|
||||||
|
else:
|
||||||
|
x, y = arg
|
||||||
|
x0, y0 = self.position()
|
||||||
|
dx = x - x0
|
||||||
|
dy = y - y0
|
||||||
|
return (atan2(dy,dx) / self._invradian) % self._fullcircle
|
||||||
|
|
||||||
|
def goto(self, *args):
|
||||||
|
""" Go to the given point.
|
||||||
|
|
||||||
|
If the pen is down, then a line will be drawn. The turtle's
|
||||||
|
orientation does not change.
|
||||||
|
|
||||||
|
Two input formats are accepted:
|
||||||
|
|
||||||
|
goto(x, y)
|
||||||
|
go to point (x, y)
|
||||||
|
|
||||||
|
goto((x, y))
|
||||||
|
go to point (x, y)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.position()
|
||||||
|
[0.0, 0.0]
|
||||||
|
>>> turtle.goto(50, -45)
|
||||||
|
>>> turtle.position()
|
||||||
|
[50.0, -45.0]
|
||||||
|
"""
|
||||||
|
if len(args) == 1:
|
||||||
|
try:
|
||||||
|
x, y = args[0]
|
||||||
|
except:
|
||||||
|
raise Error, "bad point argument: %r" % (args[0],)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
x, y = args
|
||||||
|
except:
|
||||||
|
raise Error, "bad coordinates: %r" % (args[0],)
|
||||||
|
x0, y0 = self._origin
|
||||||
|
self._goto(x0+x, y0-y)
|
||||||
|
|
||||||
|
def _goto(self, x1, y1):
|
||||||
|
x0, y0 = self._position
|
||||||
|
self._position = map(float, (x1, y1))
|
||||||
|
if self._filling:
|
||||||
|
self._path.append(self._position)
|
||||||
|
if self._drawing:
|
||||||
|
if self._tracing:
|
||||||
|
dx = float(x1 - x0)
|
||||||
|
dy = float(y1 - y0)
|
||||||
|
distance = hypot(dx, dy)
|
||||||
|
nhops = int(distance)
|
||||||
|
item = self._canvas.create_line(x0, y0, x0, y0,
|
||||||
|
width=self._width,
|
||||||
|
capstyle="round",
|
||||||
|
fill=self._color)
|
||||||
|
try:
|
||||||
|
for i in range(1, 1+nhops):
|
||||||
|
x, y = x0 + dx*i/nhops, y0 + dy*i/nhops
|
||||||
|
self._canvas.coords(item, x0, y0, x, y)
|
||||||
|
self._draw_turtle((x,y))
|
||||||
|
self._canvas.update()
|
||||||
|
self._canvas.after(self._delay)
|
||||||
|
# in case nhops==0
|
||||||
|
self._canvas.coords(item, x0, y0, x1, y1)
|
||||||
|
self._canvas.itemconfigure(item, arrow="none")
|
||||||
|
except tkinter.TclError:
|
||||||
|
# Probably the window was closed!
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
item = self._canvas.create_line(x0, y0, x1, y1,
|
||||||
|
width=self._width,
|
||||||
|
capstyle="round",
|
||||||
|
fill=self._color)
|
||||||
|
self._items.append(item)
|
||||||
|
self._draw_turtle()
|
||||||
|
|
||||||
|
def speed(self, speed):
|
||||||
|
""" Set the turtle's speed.
|
||||||
|
|
||||||
|
speed must one of these five strings:
|
||||||
|
|
||||||
|
'fastest' is a 0 ms delay
|
||||||
|
'fast' is a 5 ms delay
|
||||||
|
'normal' is a 10 ms delay
|
||||||
|
'slow' is a 15 ms delay
|
||||||
|
'slowest' is a 20 ms delay
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.speed('slow')
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
speed = speed.strip().lower()
|
||||||
|
self._delay = speeds.index(speed) * 5
|
||||||
|
except:
|
||||||
|
raise ValueError("%r is not a valid speed. speed must be "
|
||||||
|
"one of %s" % (speed, speeds))
|
||||||
|
|
||||||
|
|
||||||
|
def delay(self, delay):
|
||||||
|
""" Set the drawing delay in milliseconds.
|
||||||
|
|
||||||
|
This is intended to allow finer control of the drawing speed
|
||||||
|
than the speed() method
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> turtle.delay(15)
|
||||||
|
"""
|
||||||
|
if int(delay) < 0:
|
||||||
|
raise ValueError("delay must be greater than or equal to 0")
|
||||||
|
self._delay = int(delay)
|
||||||
|
|
||||||
|
def _draw_turtle(self, position=[]):
|
||||||
|
if not self._tracing:
|
||||||
|
self._canvas.update()
|
||||||
|
return
|
||||||
|
if position == []:
|
||||||
|
position = self._position
|
||||||
|
x,y = position
|
||||||
|
distance = 8
|
||||||
|
dx = distance * cos(self._angle*self._invradian)
|
||||||
|
dy = distance * sin(self._angle*self._invradian)
|
||||||
|
self._delete_turtle()
|
||||||
|
self._arrow = self._canvas.create_line(x-dx,y+dy,x,y,
|
||||||
|
width=self._width,
|
||||||
|
arrow="last",
|
||||||
|
capstyle="round",
|
||||||
|
fill=self._color)
|
||||||
|
self._canvas.update()
|
||||||
|
|
||||||
|
def _delete_turtle(self):
|
||||||
|
if self._arrow != 0:
|
||||||
|
self._canvas.delete(self._arrow)
|
||||||
|
self._arrow = 0
|
||||||
|
|
||||||
|
|
||||||
|
_root = None
|
||||||
|
_canvas = None
|
||||||
|
_pen = None
|
||||||
|
_width = 0.50 # 50% of window width
|
||||||
|
_height = 0.75 # 75% of window height
|
||||||
|
_startx = None
|
||||||
|
_starty = None
|
||||||
|
_title = "Turtle Graphics" # default title
|
||||||
|
|
||||||
|
class Pen(RawPen):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
global _root, _canvas
|
||||||
|
if _root is None:
|
||||||
|
_root = tkinter.Tk()
|
||||||
|
_root.wm_protocol("WM_DELETE_WINDOW", self._destroy)
|
||||||
|
_root.title(_title)
|
||||||
|
|
||||||
|
if _canvas is None:
|
||||||
|
# XXX Should have scroll bars
|
||||||
|
_canvas = tkinter.Canvas(_root, background="white")
|
||||||
|
_canvas.pack(expand=1, fill="both")
|
||||||
|
|
||||||
|
setup(width=_width, height= _height, startx=_startx, starty=_starty)
|
||||||
|
|
||||||
|
RawPen.__init__(self, _canvas)
|
||||||
|
|
||||||
|
def _destroy(self):
|
||||||
|
global _root, _canvas, _pen
|
||||||
|
root = self._canvas._root()
|
||||||
|
if root is _root:
|
||||||
|
_pen = None
|
||||||
|
_root = None
|
||||||
|
_canvas = None
|
||||||
|
root.destroy()
|
||||||
|
|
||||||
|
def _getpen():
|
||||||
|
global _pen
|
||||||
|
if not _pen:
|
||||||
|
_pen = Pen()
|
||||||
|
return _pen
|
||||||
|
|
||||||
|
class Turtle(Pen):
|
||||||
|
pass
|
||||||
|
|
||||||
|
"""For documentation of the following functions see
|
||||||
|
the RawPen methods with the same names
|
||||||
|
"""
|
||||||
|
|
||||||
|
def degrees(): _getpen().degrees()
|
||||||
|
def radians(): _getpen().radians()
|
||||||
|
def reset(): _getpen().reset()
|
||||||
|
def clear(): _getpen().clear()
|
||||||
|
def tracer(flag): _getpen().tracer(flag)
|
||||||
|
def forward(distance): _getpen().forward(distance)
|
||||||
|
def backward(distance): _getpen().backward(distance)
|
||||||
|
def left(angle): _getpen().left(angle)
|
||||||
|
def right(angle): _getpen().right(angle)
|
||||||
|
def up(): _getpen().up()
|
||||||
|
def down(): _getpen().down()
|
||||||
|
def width(width): _getpen().width(width)
|
||||||
|
def color(*args): _getpen().color(*args)
|
||||||
|
def write(arg, move=0): _getpen().write(arg, move)
|
||||||
|
def fill(flag): _getpen().fill(flag)
|
||||||
|
def begin_fill(): _getpen().begin_fill()
|
||||||
|
def end_fill(): _getpen().end_fill()
|
||||||
|
def circle(radius, extent=None): _getpen().circle(radius, extent)
|
||||||
|
def goto(*args): _getpen().goto(*args)
|
||||||
|
def heading(): return _getpen().heading()
|
||||||
|
def setheading(angle): _getpen().setheading(angle)
|
||||||
|
def position(): return _getpen().position()
|
||||||
|
def window_width(): return _getpen().window_width()
|
||||||
|
def window_height(): return _getpen().window_height()
|
||||||
|
def setx(xpos): _getpen().setx(xpos)
|
||||||
|
def sety(ypos): _getpen().sety(ypos)
|
||||||
|
def towards(*args): return _getpen().towards(*args)
|
||||||
|
|
||||||
|
def done(): _root.mainloop()
|
||||||
|
def delay(delay): return _getpen().delay(delay)
|
||||||
|
def speed(speed): return _getpen().speed(speed)
|
||||||
|
|
||||||
|
for methodname in dir(RawPen):
|
||||||
|
""" copies RawPen docstrings to module functions of same name """
|
||||||
|
if not methodname.startswith("_"):
|
||||||
|
eval(methodname).__doc__ = RawPen.__dict__[methodname].__doc__
|
||||||
|
|
||||||
|
|
||||||
|
def setup(**geometry):
|
||||||
|
""" Sets the size and position of the main window.
|
||||||
|
|
||||||
|
Keywords are width, height, startx and starty:
|
||||||
|
|
||||||
|
width: either a size in pixels or a fraction of the screen.
|
||||||
|
Default is 50% of screen.
|
||||||
|
height: either the height in pixels or a fraction of the screen.
|
||||||
|
Default is 75% of screen.
|
||||||
|
|
||||||
|
Setting either width or height to None before drawing will force
|
||||||
|
use of default geometry as in older versions of turtle.py
|
||||||
|
|
||||||
|
startx: starting position in pixels from the left edge of the screen.
|
||||||
|
Default is to center window. Setting startx to None is the default
|
||||||
|
and centers window horizontally on screen.
|
||||||
|
|
||||||
|
starty: starting position in pixels from the top edge of the screen.
|
||||||
|
Default is to center window. Setting starty to None is the default
|
||||||
|
and centers window vertically on screen.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> setup (width=200, height=200, startx=0, starty=0)
|
||||||
|
|
||||||
|
sets window to 200x200 pixels, in upper left of screen
|
||||||
|
|
||||||
|
>>> setup(width=.75, height=0.5, startx=None, starty=None)
|
||||||
|
|
||||||
|
sets window to 75% of screen by 50% of screen and centers
|
||||||
|
|
||||||
|
>>> setup(width=None)
|
||||||
|
|
||||||
|
forces use of default geometry as in older versions of turtle.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
global _width, _height, _startx, _starty
|
||||||
|
|
||||||
|
width = geometry.get('width',_width)
|
||||||
|
if width >= 0 or width is None:
|
||||||
|
_width = width
|
||||||
|
else:
|
||||||
|
raise ValueError, "width can not be less than 0"
|
||||||
|
|
||||||
|
height = geometry.get('height',_height)
|
||||||
|
if height >= 0 or height is None:
|
||||||
|
_height = height
|
||||||
|
else:
|
||||||
|
raise ValueError, "height can not be less than 0"
|
||||||
|
|
||||||
|
startx = geometry.get('startx', _startx)
|
||||||
|
if startx >= 0 or startx is None:
|
||||||
|
_startx = _startx
|
||||||
|
else:
|
||||||
|
raise ValueError, "startx can not be less than 0"
|
||||||
|
|
||||||
|
starty = geometry.get('starty', _starty)
|
||||||
|
if starty >= 0 or starty is None:
|
||||||
|
_starty = starty
|
||||||
|
else:
|
||||||
|
raise ValueError, "startx can not be less than 0"
|
||||||
|
|
||||||
|
|
||||||
|
if _root and _width and _height:
|
||||||
|
if 0 < _width <= 1:
|
||||||
|
_width = _root.winfo_screenwidth() * +width
|
||||||
|
if 0 < _height <= 1:
|
||||||
|
_height = _root.winfo_screenheight() * _height
|
||||||
|
|
||||||
|
# center window on screen
|
||||||
|
if _startx is None:
|
||||||
|
_startx = (_root.winfo_screenwidth() - _width) / 2
|
||||||
|
|
||||||
|
if _starty is None:
|
||||||
|
_starty = (_root.winfo_screenheight() - _height) / 2
|
||||||
|
|
||||||
|
_root.geometry("%dx%d+%d+%d" % (_width, _height, _startx, _starty))
|
||||||
|
|
||||||
|
def title(title):
|
||||||
|
"""Set the window title.
|
||||||
|
|
||||||
|
By default this is set to 'Turtle Graphics'
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> title("My Window")
|
||||||
|
"""
|
||||||
|
|
||||||
|
global _title
|
||||||
|
_title = title
|
||||||
|
|
||||||
|
def demo():
|
||||||
|
reset()
|
||||||
|
tracer(1)
|
||||||
|
up()
|
||||||
|
backward(100)
|
||||||
|
down()
|
||||||
|
# draw 3 squares; the last filled
|
||||||
|
width(3)
|
||||||
|
for i in range(3):
|
||||||
|
if i == 2:
|
||||||
|
fill(1)
|
||||||
|
for j in range(4):
|
||||||
|
forward(20)
|
||||||
|
left(90)
|
||||||
|
if i == 2:
|
||||||
|
color("maroon")
|
||||||
|
fill(0)
|
||||||
|
up()
|
||||||
|
forward(30)
|
||||||
|
down()
|
||||||
|
width(1)
|
||||||
|
color("black")
|
||||||
|
# move out of the way
|
||||||
|
tracer(0)
|
||||||
|
up()
|
||||||
|
right(90)
|
||||||
|
forward(100)
|
||||||
|
right(90)
|
||||||
|
forward(100)
|
||||||
|
right(180)
|
||||||
|
down()
|
||||||
|
# some text
|
||||||
|
write("startstart", 1)
|
||||||
|
write("start", 1)
|
||||||
|
color("red")
|
||||||
|
# staircase
|
||||||
|
for i in range(5):
|
||||||
|
forward(20)
|
||||||
|
left(90)
|
||||||
|
forward(20)
|
||||||
|
right(90)
|
||||||
|
# filled staircase
|
||||||
|
fill(1)
|
||||||
|
for i in range(5):
|
||||||
|
forward(20)
|
||||||
|
left(90)
|
||||||
|
forward(20)
|
||||||
|
right(90)
|
||||||
|
fill(0)
|
||||||
|
tracer(1)
|
||||||
|
# more text
|
||||||
|
write("end")
|
||||||
|
|
||||||
|
def demo2():
|
||||||
|
# exercises some new and improved features
|
||||||
|
speed('fast')
|
||||||
|
width(3)
|
||||||
|
|
||||||
|
# draw a segmented half-circle
|
||||||
|
setheading(towards(0,0))
|
||||||
|
x,y = position()
|
||||||
|
r = (x**2+y**2)**.5/2.0
|
||||||
|
right(90)
|
||||||
|
pendown = True
|
||||||
|
for i in range(18):
|
||||||
|
if pendown:
|
||||||
|
up()
|
||||||
|
pendown = False
|
||||||
|
else:
|
||||||
|
down()
|
||||||
|
pendown = True
|
||||||
|
circle(r,10)
|
||||||
|
sleep(2)
|
||||||
|
|
||||||
|
reset()
|
||||||
|
left(90)
|
||||||
|
|
||||||
|
# draw a series of triangles
|
||||||
|
l = 10
|
||||||
|
color("green")
|
||||||
|
width(3)
|
||||||
|
left(180)
|
||||||
|
sp = 5
|
||||||
|
for i in range(-2,16):
|
||||||
|
if i > 0:
|
||||||
|
color(1.0-0.05*i,0,0.05*i)
|
||||||
|
fill(1)
|
||||||
|
color("green")
|
||||||
|
for j in range(3):
|
||||||
|
forward(l)
|
||||||
|
left(120)
|
||||||
|
l += 10
|
||||||
|
left(15)
|
||||||
|
if sp > 0:
|
||||||
|
sp = sp-1
|
||||||
|
speed(speeds[sp])
|
||||||
|
color(0.25,0,0.75)
|
||||||
|
fill(0)
|
||||||
|
|
||||||
|
# draw and fill a concave shape
|
||||||
|
left(120)
|
||||||
|
up()
|
||||||
|
forward(70)
|
||||||
|
right(30)
|
||||||
|
down()
|
||||||
|
color("red")
|
||||||
|
speed("fastest")
|
||||||
|
fill(1)
|
||||||
|
for i in range(4):
|
||||||
|
circle(50,90)
|
||||||
|
right(90)
|
||||||
|
forward(30)
|
||||||
|
right(90)
|
||||||
|
color("yellow")
|
||||||
|
fill(0)
|
||||||
|
left(90)
|
||||||
|
up()
|
||||||
|
forward(30)
|
||||||
|
down();
|
||||||
|
|
||||||
|
color("red")
|
||||||
|
|
||||||
|
# create a second turtle and make the original pursue and catch it
|
||||||
|
turtle=Turtle()
|
||||||
|
turtle.reset()
|
||||||
|
turtle.left(90)
|
||||||
|
turtle.speed('normal')
|
||||||
|
turtle.up()
|
||||||
|
turtle.goto(280,40)
|
||||||
|
turtle.left(24)
|
||||||
|
turtle.down()
|
||||||
|
turtle.speed('fast')
|
||||||
|
turtle.color("blue")
|
||||||
|
turtle.width(2)
|
||||||
|
speed('fastest')
|
||||||
|
|
||||||
|
# turn default turtle towards new turtle object
|
||||||
|
setheading(towards(turtle))
|
||||||
|
while ( abs(position()[0]-turtle.position()[0])>4 or
|
||||||
|
abs(position()[1]-turtle.position()[1])>4):
|
||||||
|
turtle.forward(3.5)
|
||||||
|
turtle.left(0.6)
|
||||||
|
# turn default turtle towards new turtle object
|
||||||
|
setheading(towards(turtle))
|
||||||
|
forward(4)
|
||||||
|
write("CAUGHT! ", move=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
demo()
|
||||||
|
sleep(3)
|
||||||
|
demo2()
|
||||||
|
done()
|
Loading…
Reference in New Issue