1#
2# turtle.py: a Tkinter based turtle graphics module for Python
3# Version 1.0.1 - 24. 9. 2009
4#
5# Copyright (C) 2006 - 2010  Gregor Lingl
6# email: glingl@aon.at
7#
8# This software is provided 'as-is', without any express or implied
9# warranty.  In no event will the authors be held liable for any damages
10# arising from the use of this software.
11#
12# Permission is granted to anyone to use this software for any purpose,
13# including commercial applications, and to alter it and redistribute it
14# freely, subject to the following restrictions:
15#
16# 1. The origin of this software must not be misrepresented; you must not
17#    claim that you wrote the original software. If you use this software
18#    in a product, an acknowledgment in the product documentation would be
19#    appreciated but is not required.
20# 2. Altered source versions must be plainly marked as such, and must not be
21#    misrepresented as being the original software.
22# 3. This notice may not be removed or altered from any source distribution.
23
24
25"""
26Turtle graphics is a popular way for introducing programming to
27kids. It was part of the original Logo programming language developed
28by Wally Feurzig and Seymour Papert in 1966.
29
30Imagine a robotic turtle starting at (0, 0) in the x-y plane. After an ``import turtle``, give it
31the command turtle.forward(15), and it moves (on-screen!) 15 pixels in
32the direction it is facing, drawing a line as it moves. Give it the
33command turtle.right(25), and it rotates in-place 25 degrees clockwise.
34
35By combining together these and similar commands, intricate shapes and
36pictures can easily be drawn.
37
38----- turtle.py
39
40This module is an extended reimplementation of turtle.py from the
41Python standard distribution up to Python 2.5. (See: http://www.python.org)
42
43It tries to keep the merits of turtle.py and to be (nearly) 100%
44compatible with it. This means in the first place to enable the
45learning programmer to use all the commands, classes and methods
46interactively when using the module from within IDLE run with
47the -n switch.
48
49Roughly it has the following features added:
50
51- Better animation of the turtle movements, especially of turning the
52  turtle. So the turtles can more easily be used as a visual feedback
53  instrument by the (beginning) programmer.
54
55- Different turtle shapes, gif-images as turtle shapes, user defined
56  and user controllable turtle shapes, among them compound
57  (multicolored) shapes. Turtle shapes can be stretched and tilted, which
58  makes turtles very versatile geometrical objects.
59
60- Fine control over turtle movement and screen updates via delay(),
61  and enhanced tracer() and speed() methods.
62
63- Aliases for the most commonly used commands, like fd for forward etc.,
64  following the early Logo traditions. This reduces the boring work of
65  typing long sequences of commands, which often occur in a natural way
66  when kids try to program fancy pictures on their first encounter with
67  turtle graphics.
68
69- Turtles now have an undo()-method with configurable undo-buffer.
70
71- Some simple commands/methods for creating event driven programs
72  (mouse-, key-, timer-events). Especially useful for programming games.
73
74- A scrollable Canvas class. The default scrollable Canvas can be
75  extended interactively as needed while playing around with the turtle(s).
76
77- A TurtleScreen class with methods controlling background color or
78  background image, window and canvas size and other properties of the
79  TurtleScreen.
80
81- There is a method, setworldcoordinates(), to install a user defined
82  coordinate-system for the TurtleScreen.
83
84- The implementation uses a 2-vector class named Vec2D, derived from tuple.
85  This class is public, so it can be imported by the application programmer,
86  which makes certain types of computations very natural and compact.
87
88- Appearance of the TurtleScreen and the Turtles at startup/import can be
89  configured by means of a turtle.cfg configuration file.
90  The default configuration mimics the appearance of the old turtle module.
91
92- If configured appropriately the module reads in docstrings from a docstring
93  dictionary in some different language, supplied separately  and replaces
94  the English ones by those read in. There is a utility function
95  write_docstringdict() to write a dictionary with the original (English)
96  docstrings to disc, so it can serve as a template for translations.
97
98Behind the scenes there are some features included with possible
99extensions in mind. These will be commented and documented elsewhere.
100
101"""
102
103_ver = "turtle 1.0b1 - for Python 2.6   -  30. 5. 2008, 18:08"
104
105#print _ver
106
107import Tkinter as TK
108import types
109import math
110import time
111import os
112
113from os.path import isfile, split, join
114from copy import deepcopy
115
116from math import *    ## for compatibility with old turtle module
117
118_tg_classes = ['ScrolledCanvas', 'TurtleScreen', 'Screen',
119               'RawTurtle', 'Turtle', 'RawPen', 'Pen', 'Shape', 'Vec2D']
120_tg_screen_functions = ['addshape', 'bgcolor', 'bgpic', 'bye',
121        'clearscreen', 'colormode', 'delay', 'exitonclick', 'getcanvas',
122        'getshapes', 'listen', 'mode', 'onkey', 'onscreenclick', 'ontimer',
123        'register_shape', 'resetscreen', 'screensize', 'setup',
124        'setworldcoordinates', 'title', 'tracer', 'turtles', 'update',
125        'window_height', 'window_width']
126_tg_turtle_functions = ['back', 'backward', 'begin_fill', 'begin_poly', 'bk',
127        'circle', 'clear', 'clearstamp', 'clearstamps', 'clone', 'color',
128        'degrees', 'distance', 'dot', 'down', 'end_fill', 'end_poly', 'fd',
129        'fill', 'fillcolor', 'forward', 'get_poly', 'getpen', 'getscreen',
130        'getturtle', 'goto', 'heading', 'hideturtle', 'home', 'ht', 'isdown',
131        'isvisible', 'left', 'lt', 'onclick', 'ondrag', 'onrelease', 'pd',
132        'pen', 'pencolor', 'pendown', 'pensize', 'penup', 'pos', 'position',
133        'pu', 'radians', 'right', 'reset', 'resizemode', 'rt',
134        'seth', 'setheading', 'setpos', 'setposition', 'settiltangle',
135        'setundobuffer', 'setx', 'sety', 'shape', 'shapesize', 'showturtle',
136        'speed', 'st', 'stamp', 'tilt', 'tiltangle', 'towards', 'tracer',
137        'turtlesize', 'undo', 'undobufferentries', 'up', 'width',
138        'window_height', 'window_width', 'write', 'xcor', 'ycor']
139_tg_utilities = ['write_docstringdict', 'done', 'mainloop']
140_math_functions = ['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh',
141        'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log',
142        'log10', 'modf', 'pi', 'pow', 'sin', 'sinh', 'sqrt', 'tan', 'tanh']
143
144__all__ = (_tg_classes + _tg_screen_functions + _tg_turtle_functions +
145           _tg_utilities + ['Terminator'] + _math_functions)
146
147_alias_list = ['addshape', 'backward', 'bk', 'fd', 'ht', 'lt', 'pd', 'pos',
148               'pu', 'rt', 'seth', 'setpos', 'setposition', 'st',
149               'turtlesize', 'up', 'width']
150
151_CFG = {"width" : 0.5,               # Screen
152        "height" : 0.75,
153        "canvwidth" : 400,
154        "canvheight": 300,
155        "leftright": None,
156        "topbottom": None,
157        "mode": "standard",          # TurtleScreen
158        "colormode": 1.0,
159        "delay": 10,
160        "undobuffersize": 1000,      # RawTurtle
161        "shape": "classic",
162        "pencolor" : "black",
163        "fillcolor" : "black",
164        "resizemode" : "noresize",
165        "visible" : True,
166        "language": "english",        # docstrings
167        "exampleturtle": "turtle",
168        "examplescreen": "screen",
169        "title": "Python Turtle Graphics",
170        "using_IDLE": False
171       }
172
173##print "cwd:", os.getcwd()
174##print "__file__:", __file__
175##
176##def show(dictionary):
177##    print "=========================="
178##    for key in sorted(dictionary.keys()):
179##        print key, ":", dictionary[key]
180##    print "=========================="
181##    print
182
183def config_dict(filename):
184    """Convert content of config-file into dictionary."""
185    f = open(filename, "r")
186    cfglines = f.readlines()
187    f.close()
188    cfgdict = {}
189    for line in cfglines:
190        line = line.strip()
191        if not line or line.startswith("#"):
192            continue
193        try:
194            key, value = line.split("=")
195        except ValueError:
196            print "Bad line in config-file %s:\n%s" % (filename,line)
197            continue
198        key = key.strip()
199        value = value.strip()
200        if value in ["True", "False", "None", "''", '""']:
201            value = eval(value)
202        else:
203            try:
204                if "." in value:
205                    value = float(value)
206                else:
207                    value = int(value)
208            except ValueError:
209                pass # value need not be converted
210        cfgdict[key] = value
211    return cfgdict
212
213def readconfig(cfgdict):
214    """Read config-files, change configuration-dict accordingly.
215
216    If there is a turtle.cfg file in the current working directory,
217    read it from there. If this contains an importconfig-value,
218    say 'myway', construct filename turtle_mayway.cfg else use
219    turtle.cfg and read it from the import-directory, where
220    turtle.py is located.
221    Update configuration dictionary first according to config-file,
222    in the import directory, then according to config-file in the
223    current working directory.
224    If no config-file is found, the default configuration is used.
225    """
226    default_cfg = "turtle.cfg"
227    cfgdict1 = {}
228    cfgdict2 = {}
229    if isfile(default_cfg):
230        cfgdict1 = config_dict(default_cfg)
231        #print "1. Loading config-file %s from: %s" % (default_cfg, os.getcwd())
232    if "importconfig" in cfgdict1:
233        default_cfg = "turtle_%s.cfg" % cfgdict1["importconfig"]
234    try:
235        head, tail = split(__file__)
236        cfg_file2 = join(head, default_cfg)
237    except BaseException:
238        cfg_file2 = ""
239    if isfile(cfg_file2):
240        #print "2. Loading config-file %s:" % cfg_file2
241        cfgdict2 = config_dict(cfg_file2)
242##    show(_CFG)
243##    show(cfgdict2)
244    _CFG.update(cfgdict2)
245##    show(_CFG)
246##    show(cfgdict1)
247    _CFG.update(cfgdict1)
248##    show(_CFG)
249
250try:
251    readconfig(_CFG)
252except BaseException:
253    print "No configfile read, reason unknown"
254
255
256class Vec2D(tuple):
257    """A 2 dimensional vector class, used as a helper class
258    for implementing turtle graphics.
259    May be useful for turtle graphics programs also.
260    Derived from tuple, so a vector is a tuple!
261
262    Provides (for a, b vectors, k number):
263       a+b vector addition
264       a-b vector subtraction
265       a*b inner product
266       k*a and a*k multiplication with scalar
267       |a| absolute value of a
268       a.rotate(angle) rotation
269    """
270    def __new__(cls, x, y):
271        return tuple.__new__(cls, (x, y))
272    def __add__(self, other):
273        return Vec2D(self[0]+other[0], self[1]+other[1])
274    def __mul__(self, other):
275        if isinstance(other, Vec2D):
276            return self[0]*other[0]+self[1]*other[1]
277        return Vec2D(self[0]*other, self[1]*other)
278    def __rmul__(self, other):
279        if isinstance(other, (int, long, float)):
280            return Vec2D(self[0]*other, self[1]*other)
281    def __sub__(self, other):
282        return Vec2D(self[0]-other[0], self[1]-other[1])
283    def __neg__(self):
284        return Vec2D(-self[0], -self[1])
285    def __abs__(self):
286        return (self[0]**2 + self[1]**2)**0.5
287    def rotate(self, angle):
288        """rotate self counterclockwise by angle
289        """
290        perp = Vec2D(-self[1], self[0])
291        angle = angle * math.pi / 180.0
292        c, s = math.cos(angle), math.sin(angle)
293        return Vec2D(self[0]*c+perp[0]*s, self[1]*c+perp[1]*s)
294    def __getnewargs__(self):
295        return (self[0], self[1])
296    def __repr__(self):
297        return "(%.2f,%.2f)" % self
298
299
300##############################################################################
301### From here up to line    : Tkinter - Interface for turtle.py            ###
302### May be replaced by an interface to some different graphics toolkit     ###
303##############################################################################
304
305## helper functions for Scrolled Canvas, to forward Canvas-methods
306## to ScrolledCanvas class
307
308def __methodDict(cls, _dict):
309    """helper function for Scrolled Canvas"""
310    baseList = list(cls.__bases__)
311    baseList.reverse()
312    for _super in baseList:
313        __methodDict(_super, _dict)
314    for key, value in cls.__dict__.items():
315        if type(value) == types.FunctionType:
316            _dict[key] = value
317
318def __methods(cls):
319    """helper function for Scrolled Canvas"""
320    _dict = {}
321    __methodDict(cls, _dict)
322    return _dict.keys()
323
324__stringBody = (
325    'def %(method)s(self, *args, **kw): return ' +
326    'self.%(attribute)s.%(method)s(*args, **kw)')
327
328def __forwardmethods(fromClass, toClass, toPart, exclude = ()):
329    """Helper functions for Scrolled Canvas, used to forward
330    ScrolledCanvas-methods to Tkinter.Canvas class.
331    """
332    _dict = {}
333    __methodDict(toClass, _dict)
334    for ex in _dict.keys():
335        if ex[:1] == '_' or ex[-1:] == '_':
336            del _dict[ex]
337    for ex in exclude:
338        if ex in _dict:
339            del _dict[ex]
340    for ex in __methods(fromClass):
341        if ex in _dict:
342            del _dict[ex]
343
344    for method, func in _dict.items():
345        d = {'method': method, 'func': func}
346        if type(toPart) == types.StringType:
347            execString = \
348                __stringBody % {'method' : method, 'attribute' : toPart}
349        exec execString in d
350        fromClass.__dict__[method] = d[method]
351
352
353class ScrolledCanvas(TK.Frame):
354    """Modeled after the scrolled canvas class from Grayons's Tkinter book.
355
356    Used as the default canvas, which pops up automatically when
357    using turtle graphics functions or the Turtle class.
358    """
359    def __init__(self, master, width=500, height=350,
360                                          canvwidth=600, canvheight=500):
361        TK.Frame.__init__(self, master, width=width, height=height)
362        self._rootwindow = self.winfo_toplevel()
363        self.width, self.height = width, height
364        self.canvwidth, self.canvheight = canvwidth, canvheight
365        self.bg = "white"
366        self._canvas = TK.Canvas(master, width=width, height=height,
367                                 bg=self.bg, relief=TK.SUNKEN, borderwidth=2)
368        self.hscroll = TK.Scrollbar(master, command=self._canvas.xview,
369                                    orient=TK.HORIZONTAL)
370        self.vscroll = TK.Scrollbar(master, command=self._canvas.yview)
371        self._canvas.configure(xscrollcommand=self.hscroll.set,
372                               yscrollcommand=self.vscroll.set)
373        self.rowconfigure(0, weight=1, minsize=0)
374        self.columnconfigure(0, weight=1, minsize=0)
375        self._canvas.grid(padx=1, in_ = self, pady=1, row=0,
376                column=0, rowspan=1, columnspan=1, sticky='news')
377        self.vscroll.grid(padx=1, in_ = self, pady=1, row=0,
378                column=1, rowspan=1, columnspan=1, sticky='news')
379        self.hscroll.grid(padx=1, in_ = self, pady=1, row=1,
380                column=0, rowspan=1, columnspan=1, sticky='news')
381        self.reset()
382        self._rootwindow.bind('<Configure>', self.onResize)
383
384    def reset(self, canvwidth=None, canvheight=None, bg = None):
385        """Adjust canvas and scrollbars according to given canvas size."""
386        if canvwidth:
387            self.canvwidth = canvwidth
388        if canvheight:
389            self.canvheight = canvheight
390        if bg:
391            self.bg = bg
392        self._canvas.config(bg=bg,
393                        scrollregion=(-self.canvwidth//2, -self.canvheight//2,
394                                       self.canvwidth//2, self.canvheight//2))
395        self._canvas.xview_moveto(0.5*(self.canvwidth - self.width + 30) /
396                                                               self.canvwidth)
397        self._canvas.yview_moveto(0.5*(self.canvheight- self.height + 30) /
398                                                              self.canvheight)
399        self.adjustScrolls()
400
401
402    def adjustScrolls(self):
403        """ Adjust scrollbars according to window- and canvas-size.
404        """
405        cwidth = self._canvas.winfo_width()
406        cheight = self._canvas.winfo_height()
407        self._canvas.xview_moveto(0.5*(self.canvwidth-cwidth)/self.canvwidth)
408        self._canvas.yview_moveto(0.5*(self.canvheight-cheight)/self.canvheight)
409        if cwidth < self.canvwidth or cheight < self.canvheight:
410            self.hscroll.grid(padx=1, in_ = self, pady=1, row=1,
411                              column=0, rowspan=1, columnspan=1, sticky='news')
412            self.vscroll.grid(padx=1, in_ = self, pady=1, row=0,
413                              column=1, rowspan=1, columnspan=1, sticky='news')
414        else:
415            self.hscroll.grid_forget()
416            self.vscroll.grid_forget()
417
418    def onResize(self, event):
419        """self-explanatory"""
420        self.adjustScrolls()
421
422    def bbox(self, *args):
423        """ 'forward' method, which canvas itself has inherited...
424        """
425        return self._canvas.bbox(*args)
426
427    def cget(self, *args, **kwargs):
428        """ 'forward' method, which canvas itself has inherited...
429        """
430        return self._canvas.cget(*args, **kwargs)
431
432    def config(self, *args, **kwargs):
433        """ 'forward' method, which canvas itself has inherited...
434        """
435        self._canvas.config(*args, **kwargs)
436
437    def bind(self, *args, **kwargs):
438        """ 'forward' method, which canvas itself has inherited...
439        """
440        self._canvas.bind(*args, **kwargs)
441
442    def unbind(self, *args, **kwargs):
443        """ 'forward' method, which canvas itself has inherited...
444        """
445        self._canvas.unbind(*args, **kwargs)
446
447    def focus_force(self):
448        """ 'forward' method, which canvas itself has inherited...
449        """
450        self._canvas.focus_force()
451
452__forwardmethods(ScrolledCanvas, TK.Canvas, '_canvas')
453
454
455class _Root(TK.Tk):
456    """Root class for Screen based on Tkinter."""
457    def __init__(self):
458        TK.Tk.__init__(self)
459
460    def setupcanvas(self, width, height, cwidth, cheight):
461        self._canvas = ScrolledCanvas(self, width, height, cwidth, cheight)
462        self._canvas.pack(expand=1, fill="both")
463
464    def _getcanvas(self):
465        return self._canvas
466
467    def set_geometry(self, width, height, startx, starty):
468        self.geometry("%dx%d%+d%+d"%(width, height, startx, starty))
469
470    def ondestroy(self, destroy):
471        self.wm_protocol("WM_DELETE_WINDOW", destroy)
472
473    def win_width(self):
474        return self.winfo_screenwidth()
475
476    def win_height(self):
477        return self.winfo_screenheight()
478
479Canvas = TK.Canvas
480
481
482class TurtleScreenBase(object):
483    """Provide the basic graphics functionality.
484       Interface between Tkinter and turtle.py.
485
486       To port turtle.py to some different graphics toolkit
487       a corresponding TurtleScreenBase class has to be implemented.
488    """
489
490    @staticmethod
491    def _blankimage():
492        """return a blank image object
493        """
494        img = TK.PhotoImage(width=1, height=1)
495        img.blank()
496        return img
497
498    @staticmethod
499    def _image(filename):
500        """return an image object containing the
501        imagedata from a gif-file named filename.
502        """
503        return TK.PhotoImage(file=filename)
504
505    def __init__(self, cv):
506        self.cv = cv
507        if isinstance(cv, ScrolledCanvas):
508            w = self.cv.canvwidth
509            h = self.cv.canvheight
510        else:  # expected: ordinary TK.Canvas
511            w = int(self.cv.cget("width"))
512            h = int(self.cv.cget("height"))
513            self.cv.config(scrollregion = (-w//2, -h//2, w//2, h//2 ))
514        self.canvwidth = w
515        self.canvheight = h
516        self.xscale = self.yscale = 1.0
517
518    def _createpoly(self):
519        """Create an invisible polygon item on canvas self.cv)
520        """
521        return self.cv.create_polygon((0, 0, 0, 0, 0, 0), fill="", outline="")
522
523    def _drawpoly(self, polyitem, coordlist, fill=None,
524                  outline=None, width=None, top=False):
525        """Configure polygonitem polyitem according to provided
526        arguments:
527        coordlist is sequence of coordinates
528        fill is filling color
529        outline is outline color
530        top is a boolean value, which specifies if polyitem
531        will be put on top of the canvas' displaylist so it
532        will not be covered by other items.
533        """
534        cl = []
535        for x, y in coordlist:
536            cl.append(x * self.xscale)
537            cl.append(-y * self.yscale)
538        self.cv.coords(polyitem, *cl)
539        if fill is not None:
540            self.cv.itemconfigure(polyitem, fill=fill)
541        if outline is not None:
542            self.cv.itemconfigure(polyitem, outline=outline)
543        if width is not None:
544            self.cv.itemconfigure(polyitem, width=width)
545        if top:
546            self.cv.tag_raise(polyitem)
547
548    def _createline(self):
549        """Create an invisible line item on canvas self.cv)
550        """
551        return self.cv.create_line(0, 0, 0, 0, fill="", width=2,
552                                   capstyle = TK.ROUND)
553
554    def _drawline(self, lineitem, coordlist=None,
555                  fill=None, width=None, top=False):
556        """Configure lineitem according to provided arguments:
557        coordlist is sequence of coordinates
558        fill is drawing color
559        width is width of drawn line.
560        top is a boolean value, which specifies if polyitem
561        will be put on top of the canvas' displaylist so it
562        will not be covered by other items.
563        """
564        if coordlist is not None:
565            cl = []
566            for x, y in coordlist:
567                cl.append(x * self.xscale)
568                cl.append(-y * self.yscale)
569            self.cv.coords(lineitem, *cl)
570        if fill is not None:
571            self.cv.itemconfigure(lineitem, fill=fill)
572        if width is not None:
573            self.cv.itemconfigure(lineitem, width=width)
574        if top:
575            self.cv.tag_raise(lineitem)
576
577    def _delete(self, item):
578        """Delete graphics item from canvas.
579        If item is"all" delete all graphics items.
580        """
581        self.cv.delete(item)
582
583    def _update(self):
584        """Redraw graphics items on canvas
585        """
586        self.cv.update()
587
588    def _delay(self, delay):
589        """Delay subsequent canvas actions for delay ms."""
590        self.cv.after(delay)
591
592    def _iscolorstring(self, color):
593        """Check if the string color is a legal Tkinter color string.
594        """
595        try:
596            rgb = self.cv.winfo_rgb(color)
597            ok = True
598        except TK.TclError:
599            ok = False
600        return ok
601
602    def _bgcolor(self, color=None):
603        """Set canvas' backgroundcolor if color is not None,
604        else return backgroundcolor."""
605        if color is not None:
606            self.cv.config(bg = color)
607            self._update()
608        else:
609            return self.cv.cget("bg")
610
611    def _write(self, pos, txt, align, font, pencolor):
612        """Write txt at pos in canvas with specified font
613        and color.
614        Return text item and x-coord of right bottom corner
615        of text's bounding box."""
616        x, y = pos
617        x = x * self.xscale
618        y = y * self.yscale
619        anchor = {"left":"sw", "center":"s", "right":"se" }
620        item = self.cv.create_text(x-1, -y, text = txt, anchor = anchor[align],
621                                        fill = pencolor, font = font)
622        x0, y0, x1, y1 = self.cv.bbox(item)
623        self.cv.update()
624        return item, x1-1
625
626##    def _dot(self, pos, size, color):
627##        """may be implemented for some other graphics toolkit"""
628
629    def _onclick(self, item, fun, num=1, add=None):
630        """Bind fun to mouse-click event on turtle.
631        fun must be a function with two arguments, the coordinates
632        of the clicked point on the canvas.
633        num, the number of the mouse-button defaults to 1
634        """
635        if fun is None:
636            self.cv.tag_unbind(item, "<Button-%s>" % num)
637        else:
638            def eventfun(event):
639                x, y = (self.cv.canvasx(event.x)/self.xscale,
640                        -self.cv.canvasy(event.y)/self.yscale)
641                fun(x, y)
642            self.cv.tag_bind(item, "<Button-%s>" % num, eventfun, add)
643
644    def _onrelease(self, item, fun, num=1, add=None):
645        """Bind fun to mouse-button-release event on turtle.
646        fun must be a function with two arguments, the coordinates
647        of the point on the canvas where mouse button is released.
648        num, the number of the mouse-button defaults to 1
649
650        If a turtle is clicked, first _onclick-event will be performed,
651        then _onscreensclick-event.
652        """
653        if fun is None:
654            self.cv.tag_unbind(item, "<Button%s-ButtonRelease>" % num)
655        else:
656            def eventfun(event):
657                x, y = (self.cv.canvasx(event.x)/self.xscale,
658                        -self.cv.canvasy(event.y)/self.yscale)
659                fun(x, y)
660            self.cv.tag_bind(item, "<Button%s-ButtonRelease>" % num,
661                             eventfun, add)
662
663    def _ondrag(self, item, fun, num=1, add=None):
664        """Bind fun to mouse-move-event (with pressed mouse button) on turtle.
665        fun must be a function with two arguments, the coordinates of the
666        actual mouse position on the canvas.
667        num, the number of the mouse-button defaults to 1
668
669        Every sequence of mouse-move-events on a turtle is preceded by a
670        mouse-click event on that turtle.
671        """
672        if fun is None:
673            self.cv.tag_unbind(item, "<Button%s-Motion>" % num)
674        else:
675            def eventfun(event):
676                try:
677                    x, y = (self.cv.canvasx(event.x)/self.xscale,
678                           -self.cv.canvasy(event.y)/self.yscale)
679                    fun(x, y)
680                except BaseException:
681                    pass
682            self.cv.tag_bind(item, "<Button%s-Motion>" % num, eventfun, add)
683
684    def _onscreenclick(self, fun, num=1, add=None):
685        """Bind fun to mouse-click event on canvas.
686        fun must be a function with two arguments, the coordinates
687        of the clicked point on the canvas.
688        num, the number of the mouse-button defaults to 1
689
690        If a turtle is clicked, first _onclick-event will be performed,
691        then _onscreensclick-event.
692        """
693        if fun is None:
694            self.cv.unbind("<Button-%s>" % num)
695        else:
696            def eventfun(event):
697                x, y = (self.cv.canvasx(event.x)/self.xscale,
698                        -self.cv.canvasy(event.y)/self.yscale)
699                fun(x, y)
700            self.cv.bind("<Button-%s>" % num, eventfun, add)
701
702    def _onkey(self, fun, key):
703        """Bind fun to key-release event of key.
704        Canvas must have focus. See method listen
705        """
706        if fun is None:
707            self.cv.unbind("<KeyRelease-%s>" % key, None)
708        else:
709            def eventfun(event):
710                fun()
711            self.cv.bind("<KeyRelease-%s>" % key, eventfun)
712
713    def _listen(self):
714        """Set focus on canvas (in order to collect key-events)
715        """
716        self.cv.focus_force()
717
718    def _ontimer(self, fun, t):
719        """Install a timer, which calls fun after t milliseconds.
720        """
721        if t == 0:
722            self.cv.after_idle(fun)
723        else:
724            self.cv.after(t, fun)
725
726    def _createimage(self, image):
727        """Create and return image item on canvas.
728        """
729        return self.cv.create_image(0, 0, image=image)
730
731    def _drawimage(self, item, pos, image):
732        """Configure image item as to draw image object
733        at position (x,y) on canvas)
734        """
735        x, y = pos
736        self.cv.coords(item, (x * self.xscale, -y * self.yscale))
737        self.cv.itemconfig(item, image=image)
738
739    def _setbgpic(self, item, image):
740        """Configure image item as to draw image object
741        at center of canvas. Set item to the first item
742        in the displaylist, so it will be drawn below
743        any other item ."""
744        self.cv.itemconfig(item, image=image)
745        self.cv.tag_lower(item)
746
747    def _type(self, item):
748        """Return 'line' or 'polygon' or 'image' depending on
749        type of item.
750        """
751        return self.cv.type(item)
752
753    def _pointlist(self, item):
754        """returns list of coordinate-pairs of points of item
755        Example (for insiders):
756        >>> from turtle import *
757        >>> getscreen()._pointlist(getturtle().turtle._item)
758        [(0.0, 9.9999999999999982), (0.0, -9.9999999999999982),
759        (9.9999999999999982, 0.0)]
760        >>> """
761        cl = self.cv.coords(item)
762        pl = [(cl[i], -cl[i+1]) for i in range(0, len(cl), 2)]
763        return  pl
764
765    def _setscrollregion(self, srx1, sry1, srx2, sry2):
766        self.cv.config(scrollregion=(srx1, sry1, srx2, sry2))
767
768    def _rescale(self, xscalefactor, yscalefactor):
769        items = self.cv.find_all()
770        for item in items:
771            coordinates = self.cv.coords(item)
772            newcoordlist = []
773            while coordinates:
774                x, y = coordinates[:2]
775                newcoordlist.append(x * xscalefactor)
776                newcoordlist.append(y * yscalefactor)
777                coordinates = coordinates[2:]
778            self.cv.coords(item, *newcoordlist)
779
780    def _resize(self, canvwidth=None, canvheight=None, bg=None):
781        """Resize the canvas the turtles are drawing on. Does
782        not alter the drawing window.
783        """
784        # needs amendment
785        if not isinstance(self.cv, ScrolledCanvas):
786            return self.canvwidth, self.canvheight
787        if canvwidth is canvheight is bg is None:
788            return self.cv.canvwidth, self.cv.canvheight
789        if canvwidth is not None:
790            self.canvwidth = canvwidth
791        if canvheight is not None:
792            self.canvheight = canvheight
793        self.cv.reset(canvwidth, canvheight, bg)
794
795    def _window_size(self):
796        """ Return the width and height of the turtle window.
797        """
798        width = self.cv.winfo_width()
799        if width <= 1:  # the window isn't managed by a geometry manager
800            width = self.cv['width']
801        height = self.cv.winfo_height()
802        if height <= 1: # the window isn't managed by a geometry manager
803            height = self.cv['height']
804        return width, height
805
806
807##############################################################################
808###                  End of Tkinter - interface                            ###
809##############################################################################
810
811
812class Terminator (Exception):
813    """Will be raised in TurtleScreen.update, if _RUNNING becomes False.
814
815    This stops execution of a turtle graphics script.
816    Main purpose: use in the Demo-Viewer turtle.Demo.py.
817    """
818    pass
819
820
821class TurtleGraphicsError(Exception):
822    """Some TurtleGraphics Error
823    """
824
825
826class Shape(object):
827    """Data structure modeling shapes.
828
829    attribute _type is one of "polygon", "image", "compound"
830    attribute _data is - depending on _type a poygon-tuple,
831    an image or a list constructed using the addcomponent method.
832    """
833    def __init__(self, type_, data=None):
834        self._type = type_
835        if type_ == "polygon":
836            if isinstance(data, list):
837                data = tuple(data)
838        elif type_ == "image":
839            if isinstance(data, basestring):
840                if data.lower().endswith(".gif") and isfile(data):
841                    data = TurtleScreen._image(data)
842                # else data assumed to be Photoimage
843        elif type_ == "compound":
844            data = []
845        else:
846            raise TurtleGraphicsError("There is no shape type %s" % type_)
847        self._data = data
848
849    def addcomponent(self, poly, fill, outline=None):
850        """Add component to a shape of type compound.
851
852        Arguments: poly is a polygon, i. e. a tuple of number pairs.
853        fill is the fillcolor of the component,
854        outline is the outline color of the component.
855
856        call (for a Shapeobject namend s):
857        --   s.addcomponent(((0,0), (10,10), (-10,10)), "red", "blue")
858
859        Example:
860        >>> poly = ((0,0),(10,-5),(0,10),(-10,-5))
861        >>> s = Shape("compound")
862        >>> s.addcomponent(poly, "red", "blue")
863        >>> # .. add more components and then use register_shape()
864        """
865        if self._type != "compound":
866            raise TurtleGraphicsError("Cannot add component to %s Shape"
867                                                                % self._type)
868        if outline is None:
869            outline = fill
870        self._data.append([poly, fill, outline])
871
872
873class Tbuffer(object):
874    """Ring buffer used as undobuffer for RawTurtle objects."""
875    def __init__(self, bufsize=10):
876        self.bufsize = bufsize
877        self.buffer = [[None]] * bufsize
878        self.ptr = -1
879        self.cumulate = False
880    def reset(self, bufsize=None):
881        if bufsize is None:
882            for i in range(self.bufsize):
883                self.buffer[i] = [None]
884        else:
885            self.bufsize = bufsize
886            self.buffer = [[None]] * bufsize
887        self.ptr = -1
888    def push(self, item):
889        if self.bufsize > 0:
890            if not self.cumulate:
891                self.ptr = (self.ptr + 1) % self.bufsize
892                self.buffer[self.ptr] = item
893            else:
894                self.buffer[self.ptr].append(item)
895    def pop(self):
896        if self.bufsize > 0:
897            item = self.buffer[self.ptr]
898            if item is None:
899                return None
900            else:
901                self.buffer[self.ptr] = [None]
902                self.ptr = (self.ptr - 1) % self.bufsize
903                return (item)
904    def nr_of_items(self):
905        return self.bufsize - self.buffer.count([None])
906    def __repr__(self):
907        return str(self.buffer) + " " + str(self.ptr)
908
909
910
911class TurtleScreen(TurtleScreenBase):
912    """Provides screen oriented methods like setbg etc.
913
914    Only relies upon the methods of TurtleScreenBase and NOT
915    upon components of the underlying graphics toolkit -
916    which is Tkinter in this case.
917    """
918#    _STANDARD_DELAY = 5
919    _RUNNING = True
920
921    def __init__(self, cv, mode=_CFG["mode"],
922                 colormode=_CFG["colormode"], delay=_CFG["delay"]):
923        self._shapes = {
924                   "arrow" : Shape("polygon", ((-10,0), (10,0), (0,10))),
925                  "turtle" : Shape("polygon", ((0,16), (-2,14), (-1,10), (-4,7),
926                              (-7,9), (-9,8), (-6,5), (-7,1), (-5,-3), (-8,-6),
927                              (-6,-8), (-4,-5), (0,-7), (4,-5), (6,-8), (8,-6),
928                              (5,-3), (7,1), (6,5), (9,8), (7,9), (4,7), (1,10),
929                              (2,14))),
930                  "circle" : Shape("polygon", ((10,0), (9.51,3.09), (8.09,5.88),
931                              (5.88,8.09), (3.09,9.51), (0,10), (-3.09,9.51),
932                              (-5.88,8.09), (-8.09,5.88), (-9.51,3.09), (-10,0),
933                              (-9.51,-3.09), (-8.09,-5.88), (-5.88,-8.09),
934                              (-3.09,-9.51), (-0.00,-10.00), (3.09,-9.51),
935                              (5.88,-8.09), (8.09,-5.88), (9.51,-3.09))),
936                  "square" : Shape("polygon", ((10,-10), (10,10), (-10,10),
937                              (-10,-10))),
938                "triangle" : Shape("polygon", ((10,-5.77), (0,11.55),
939                              (-10,-5.77))),
940                  "classic": Shape("polygon", ((0,0),(-5,-9),(0,-7),(5,-9))),
941                   "blank" : Shape("image", self._blankimage())
942                  }
943
944        self._bgpics = {"nopic" : ""}
945
946        TurtleScreenBase.__init__(self, cv)
947        self._mode = mode
948        self._delayvalue = delay
949        self._colormode = _CFG["colormode"]
950        self._keys = []
951        self.clear()
952
953    def clear(self):
954        """Delete all drawings and all turtles from the TurtleScreen.
955
956        Reset empty TurtleScreen to its initial state: white background,
957        no backgroundimage, no eventbindings and tracing on.
958
959        No argument.
960
961        Example (for a TurtleScreen instance named screen):
962        >>> screen.clear()
963
964        Note: this method is not available as function.
965        """
966        self._delayvalue = _CFG["delay"]
967        self._colormode = _CFG["colormode"]
968        self._delete("all")
969        self._bgpic = self._createimage("")
970        self._bgpicname = "nopic"
971        self._tracing = 1
972        self._updatecounter = 0
973        self._turtles = []
974        self.bgcolor("white")
975        for btn in 1, 2, 3:
976            self.onclick(None, btn)
977        for key in self._keys[:]:
978            self.onkey(None, key)
979        Turtle._pen = None
980
981    def mode(self, mode=None):
982        """Set turtle-mode ('standard', 'logo' or 'world') and perform reset.
983
984        Optional argument:
985        mode -- one of the strings 'standard', 'logo' or 'world'
986
987        Mode 'standard' is compatible with turtle.py.
988        Mode 'logo' is compatible with most Logo-Turtle-Graphics.
989        Mode 'world' uses userdefined 'worldcoordinates'. *Attention*: in
990        this mode angles appear distorted if x/y unit-ratio doesn't equal 1.
991        If mode is not given, return the current mode.
992
993             Mode      Initial turtle heading     positive angles
994         ------------|-------------------------|-------------------
995          'standard'    to the right (east)       counterclockwise
996            'logo'        upward    (north)         clockwise
997
998        Examples:
999        >>> mode('logo')   # resets turtle heading to north
1000        >>> mode()
1001        'logo'
1002        """
1003        if mode is None:
1004            return self._mode
1005        mode = mode.lower()
1006        if mode not in ["standard", "logo", "world"]:
1007            raise TurtleGraphicsError("No turtle-graphics-mode %s" % mode)
1008        self._mode = mode
1009        if mode in ["standard", "logo"]:
1010            self._setscrollregion(-self.canvwidth//2, -self.canvheight//2,
1011                                       self.canvwidth//2, self.canvheight//2)
1012            self.xscale = self.yscale = 1.0
1013        self.reset()
1014
1015    def setworldcoordinates(self, llx, lly, urx, ury):
1016        """Set up a user defined coordinate-system.
1017
1018        Arguments:
1019        llx -- a number, x-coordinate of lower left corner of canvas
1020        lly -- a number, y-coordinate of lower left corner of canvas
1021        urx -- a number, x-coordinate of upper right corner of canvas
1022        ury -- a number, y-coordinate of upper right corner of canvas
1023
1024        Set up user coodinat-system and switch to mode 'world' if necessary.
1025        This performs a screen.reset. If mode 'world' is already active,
1026        all drawings are redrawn according to the new coordinates.
1027
1028        But ATTENTION: in user-defined coordinatesystems angles may appear
1029        distorted. (see Screen.mode())
1030
1031        Example (for a TurtleScreen instance named screen):
1032        >>> screen.setworldcoordinates(-10,-0.5,50,1.5)
1033        >>> for _ in range(36):
1034        ...     left(10)
1035        ...     forward(0.5)
1036        """
1037        if self.mode() != "world":
1038            self.mode("world")
1039        xspan = float(urx - llx)
1040        yspan = float(ury - lly)
1041        wx, wy = self._window_size()
1042        self.screensize(wx-20, wy-20)
1043        oldxscale, oldyscale = self.xscale, self.yscale
1044        self.xscale = self.canvwidth / xspan
1045        self.yscale = self.canvheight / yspan
1046        srx1 = llx * self.xscale
1047        sry1 = -ury * self.yscale
1048        srx2 = self.canvwidth + srx1
1049        sry2 = self.canvheight + sry1
1050        self._setscrollregion(srx1, sry1, srx2, sry2)
1051        self._rescale(self.xscale/oldxscale, self.yscale/oldyscale)
1052        self.update()
1053
1054    def register_shape(self, name, shape=None):
1055        """Adds a turtle shape to TurtleScreen's shapelist.
1056
1057        Arguments:
1058        (1) name is the name of a gif-file and shape is None.
1059            Installs the corresponding image shape.
1060            !! Image-shapes DO NOT rotate when turning the turtle,
1061            !! so they do not display the heading of the turtle!
1062        (2) name is an arbitrary string and shape is a tuple
1063            of pairs of coordinates. Installs the corresponding
1064            polygon shape
1065        (3) name is an arbitrary string and shape is a
1066            (compound) Shape object. Installs the corresponding
1067            compound shape.
1068        To use a shape, you have to issue the command shape(shapename).
1069
1070        call: register_shape("turtle.gif")
1071        --or: register_shape("tri", ((0,0), (10,10), (-10,10)))
1072
1073        Example (for a TurtleScreen instance named screen):
1074        >>> screen.register_shape("triangle", ((5,-3),(0,5),(-5,-3)))
1075
1076        """
1077        if shape is None:
1078            # image
1079            if name.lower().endswith(".gif"):
1080                shape = Shape("image", self._image(name))
1081            else:
1082                raise TurtleGraphicsError("Bad arguments for register_shape.\n"
1083                                          + "Use  help(register_shape)" )
1084        elif isinstance(shape, tuple):
1085            shape = Shape("polygon", shape)
1086        ## else shape assumed to be Shape-instance
1087        self._shapes[name] = shape
1088        # print "shape added:" , self._shapes
1089
1090    def _colorstr(self, color):
1091        """Return color string corresponding to args.
1092
1093        Argument may be a string or a tuple of three
1094        numbers corresponding to actual colormode,
1095        i.e. in the range 0<=n<=colormode.
1096
1097        If the argument doesn't represent a color,
1098        an error is raised.
1099        """
1100        if len(color) == 1:
1101            color = color[0]
1102        if isinstance(color, basestring):
1103            if self._iscolorstring(color) or color == "":
1104                return color
1105            else:
1106                raise TurtleGraphicsError("bad color string: %s" % str(color))
1107        try:
1108            r, g, b = color
1109        except (TypeError, ValueError):
1110            raise TurtleGraphicsError("bad color arguments: %s" % str(color))
1111        if self._colormode == 1.0:
1112            r, g, b = [round(255.0*x) for x in (r, g, b)]
1113        if not ((0 <= r <= 255) and (0 <= g <= 255) and (0 <= b <= 255)):
1114            raise TurtleGraphicsError("bad color sequence: %s" % str(color))
1115        return "#%02x%02x%02x" % (r, g, b)
1116
1117    def _color(self, cstr):
1118        if not cstr.startswith("#"):
1119            return cstr
1120        if len(cstr) == 7:
1121            cl = [int(cstr[i:i+2], 16) for i in (1, 3, 5)]
1122        elif len(cstr) == 4:
1123            cl = [16*int(cstr[h], 16) for h in cstr[1:]]
1124        else:
1125            raise TurtleGraphicsError("bad colorstring: %s" % cstr)
1126        return tuple([c * self._colormode/255 for c in cl])
1127
1128    def colormode(self, cmode=None):
1129        """Return the colormode or set it to 1.0 or 255.
1130
1131        Optional argument:
1132        cmode -- one of the values 1.0 or 255
1133
1134        r, g, b values of colortriples have to be in range 0..cmode.
1135
1136        Example (for a TurtleScreen instance named screen):
1137        >>> screen.colormode()
1138        1.0
1139        >>> screen.colormode(255)
1140        >>> pencolor(240,160,80)
1141        """
1142        if cmode is None:
1143            return self._colormode
1144        if cmode == 1.0:
1145            self._colormode = float(cmode)
1146        elif cmode == 255:
1147            self._colormode = int(cmode)
1148
1149    def reset(self):
1150        """Reset all Turtles on the Screen to their initial state.
1151
1152        No argument.
1153
1154        Example (for a TurtleScreen instance named screen):
1155        >>> screen.reset()
1156        """
1157        for turtle in self._turtles:
1158            turtle._setmode(self._mode)
1159            turtle.reset()
1160
1161    def turtles(self):
1162        """Return the list of turtles on the screen.
1163
1164        Example (for a TurtleScreen instance named screen):
1165        >>> screen.turtles()
1166        [<turtle.Turtle object at 0x00E11FB0>]
1167        """
1168        return self._turtles
1169
1170    def bgcolor(self, *args):
1171        """Set or return backgroundcolor of the TurtleScreen.
1172
1173        Arguments (if given): a color string or three numbers
1174        in the range 0..colormode or a 3-tuple of such numbers.
1175
1176        Example (for a TurtleScreen instance named screen):
1177        >>> screen.bgcolor("orange")
1178        >>> screen.bgcolor()
1179        'orange'
1180        >>> screen.bgcolor(0.5,0,0.5)
1181        >>> screen.bgcolor()
1182        '#800080'
1183        """
1184        if args:
1185            color = self._colorstr(args)
1186        else:
1187            color = None
1188        color = self._bgcolor(color)
1189        if color is not None:
1190            color = self._color(color)
1191        return color
1192
1193    def tracer(self, n=None, delay=None):
1194        """Turns turtle animation on/off and set delay for update drawings.
1195
1196        Optional arguments:
1197        n -- nonnegative  integer
1198        delay -- nonnegative  integer
1199
1200        If n is given, only each n-th regular screen update is really performed.
1201        (Can be used to accelerate the drawing of complex graphics.)
1202        Second arguments sets delay value (see RawTurtle.delay())
1203
1204        Example (for a TurtleScreen instance named screen):
1205        >>> screen.tracer(8, 25)
1206        >>> dist = 2
1207        >>> for i in range(200):
1208        ...     fd(dist)
1209        ...     rt(90)
1210        ...     dist += 2
1211        """
1212        if n is None:
1213            return self._tracing
1214        self._tracing = int(n)
1215        self._updatecounter = 0
1216        if delay is not None:
1217            self._delayvalue = int(delay)
1218        if self._tracing:
1219            self.update()
1220
1221    def delay(self, delay=None):
1222        """ Return or set the drawing delay in milliseconds.
1223
1224        Optional argument:
1225        delay -- positive integer
1226
1227        Example (for a TurtleScreen instance named screen):
1228        >>> screen.delay(15)
1229        >>> screen.delay()
1230        15
1231        """
1232        if delay is None:
1233            return self._delayvalue
1234        self._delayvalue = int(delay)
1235
1236    def _incrementudc(self):
1237        """Increment update counter."""
1238        if not TurtleScreen._RUNNING:
1239            TurtleScreen._RUNNING = True
1240            raise Terminator
1241        if self._tracing > 0:
1242            self._updatecounter += 1
1243            self._updatecounter %= self._tracing
1244
1245    def update(self):
1246        """Perform a TurtleScreen update.
1247        """
1248        tracing = self._tracing
1249        self._tracing = True
1250        for t in self.turtles():
1251            t._update_data()
1252            t._drawturtle()
1253        self._tracing = tracing
1254        self._update()
1255
1256    def window_width(self):
1257        """ Return the width of the turtle window.
1258
1259        Example (for a TurtleScreen instance named screen):
1260        >>> screen.window_width()
1261        640
1262        """
1263        return self._window_size()[0]
1264
1265    def window_height(self):
1266        """ Return the height of the turtle window.
1267
1268        Example (for a TurtleScreen instance named screen):
1269        >>> screen.window_height()
1270        480
1271        """
1272        return self._window_size()[1]
1273
1274    def getcanvas(self):
1275        """Return the Canvas of this TurtleScreen.
1276
1277        No argument.
1278
1279        Example (for a Screen instance named screen):
1280        >>> cv = screen.getcanvas()
1281        >>> cv
1282        <turtle.ScrolledCanvas instance at 0x010742D8>
1283        """
1284        return self.cv
1285
1286    def getshapes(self):
1287        """Return a list of names of all currently available turtle shapes.
1288
1289        No argument.
1290
1291        Example (for a TurtleScreen instance named screen):
1292        >>> screen.getshapes()
1293        ['arrow', 'blank', 'circle', ... , 'turtle']
1294        """
1295        return sorted(self._shapes.keys())
1296
1297    def onclick(self, fun, btn=1, add=None):
1298        """Bind fun to mouse-click event on canvas.
1299
1300        Arguments:
1301        fun -- a function with two arguments, the coordinates of the
1302               clicked point on the canvas.
1303        btn -- the number of the mouse-button, defaults to 1
1304
1305        Example (for a TurtleScreen instance named screen
1306        and a Turtle instance named turtle):
1307
1308        >>> screen.onclick(goto)
1309        >>> # Subsequently clicking into the TurtleScreen will
1310        >>> # make the turtle move to the clicked point.
1311        >>> screen.onclick(None)
1312        """
1313        self._onscreenclick(fun, btn, add)
1314
1315    def onkey(self, fun, key):
1316        """Bind fun to key-release event of key.
1317
1318        Arguments:
1319        fun -- a function with no arguments
1320        key -- a string: key (e.g. "a") or key-symbol (e.g. "space")
1321
1322        In order to be able to register key-events, TurtleScreen
1323        must have focus. (See method listen.)
1324
1325        Example (for a TurtleScreen instance named screen):
1326
1327        >>> def f():
1328        ...     fd(50)
1329        ...     lt(60)
1330        ...
1331        >>> screen.onkey(f, "Up")
1332        >>> screen.listen()
1333
1334        Subsequently the turtle can be moved by repeatedly pressing
1335        the up-arrow key, consequently drawing a hexagon
1336
1337        """
1338        if fun is None:
1339            if key in self._keys:
1340                self._keys.remove(key)
1341        elif key not in self._keys:
1342            self._keys.append(key)
1343        self._onkey(fun, key)
1344
1345    def listen(self, xdummy=None, ydummy=None):
1346        """Set focus on TurtleScreen (in order to collect key-events)
1347
1348        No arguments.
1349        Dummy arguments are provided in order
1350        to be able to pass listen to the onclick method.
1351
1352        Example (for a TurtleScreen instance named screen):
1353        >>> screen.listen()
1354        """
1355        self._listen()
1356
1357    def ontimer(self, fun, t=0):
1358        """Install a timer, which calls fun after t milliseconds.
1359
1360        Arguments:
1361        fun -- a function with no arguments.
1362        t -- a number >= 0
1363
1364        Example (for a TurtleScreen instance named screen):
1365
1366        >>> running = True
1367        >>> def f():
1368        ...     if running:
1369        ...             fd(50)
1370        ...             lt(60)
1371        ...             screen.ontimer(f, 250)
1372        ...
1373        >>> f()   # makes the turtle marching around
1374        >>> running = False
1375        """
1376        self._ontimer(fun, t)
1377
1378    def bgpic(self, picname=None):
1379        """Set background image or return name of current backgroundimage.
1380
1381        Optional argument:
1382        picname -- a string, name of a gif-file or "nopic".
1383
1384        If picname is a filename, set the corresponding image as background.
1385        If picname is "nopic", delete backgroundimage, if present.
1386        If picname is None, return the filename of the current backgroundimage.
1387
1388        Example (for a TurtleScreen instance named screen):
1389        >>> screen.bgpic()
1390        'nopic'
1391        >>> screen.bgpic("landscape.gif")
1392        >>> screen.bgpic()
1393        'landscape.gif'
1394        """
1395        if picname is None:
1396            return self._bgpicname
1397        if picname not in self._bgpics:
1398            self._bgpics[picname] = self._image(picname)
1399        self._setbgpic(self._bgpic, self._bgpics[picname])
1400        self._bgpicname = picname
1401
1402    def screensize(self, canvwidth=None, canvheight=None, bg=None):
1403        """Resize the canvas the turtles are drawing on.
1404
1405        Optional arguments:
1406        canvwidth -- positive integer, new width of canvas in pixels
1407        canvheight --  positive integer, new height of canvas in pixels
1408        bg -- colorstring or color-tuple, new backgroundcolor
1409        If no arguments are given, return current (canvaswidth, canvasheight)
1410
1411        Do not alter the drawing window. To observe hidden parts of
1412        the canvas use the scrollbars. (Can make visible those parts
1413        of a drawing, which were outside the canvas before!)
1414
1415        Example (for a Turtle instance named turtle):
1416        >>> turtle.screensize(2000,1500)
1417        >>> # e. g. to search for an erroneously escaped turtle ;-)
1418        """
1419        return self._resize(canvwidth, canvheight, bg)
1420
1421    onscreenclick = onclick
1422    resetscreen = reset
1423    clearscreen = clear
1424    addshape = register_shape
1425
1426class TNavigator(object):
1427    """Navigation part of the RawTurtle.
1428    Implements methods for turtle movement.
1429    """
1430    START_ORIENTATION = {
1431        "standard": Vec2D(1.0, 0.0),
1432        "world"   : Vec2D(1.0, 0.0),
1433        "logo"    : Vec2D(0.0, 1.0)  }
1434    DEFAULT_MODE = "standard"
1435    DEFAULT_ANGLEOFFSET = 0
1436    DEFAULT_ANGLEORIENT = 1
1437
1438    def __init__(self, mode=DEFAULT_MODE):
1439        self._angleOffset = self.DEFAULT_ANGLEOFFSET
1440        self._angleOrient = self.DEFAULT_ANGLEORIENT
1441        self._mode = mode
1442        self.undobuffer = None
1443        self.degrees()
1444        self._mode = None
1445        self._setmode(mode)
1446        TNavigator.reset(self)
1447
1448    def reset(self):
1449        """reset turtle to its initial values
1450
1451        Will be overwritten by parent class
1452        """
1453        self._position = Vec2D(0.0, 0.0)
1454        self._orient =  TNavigator.START_ORIENTATION[self._mode]
1455
1456    def _setmode(self, mode=None):
1457        """Set turtle-mode to 'standard', 'world' or 'logo'.
1458        """
1459        if mode is None:
1460            return self._mode
1461        if mode not in ["standard", "logo", "world"]:
1462            return
1463        self._mode = mode
1464        if mode in ["standard", "world"]:
1465            self._angleOffset = 0
1466            self._angleOrient = 1
1467        else: # mode == "logo":
1468            self._angleOffset = self._fullcircle/4.
1469            self._angleOrient = -1
1470
1471    def _setDegreesPerAU(self, fullcircle):
1472        """Helper function for degrees() and radians()"""
1473        self._fullcircle = fullcircle
1474        self._degreesPerAU = 360/fullcircle
1475        if self._mode == "standard":
1476            self._angleOffset = 0
1477        else:
1478            self._angleOffset = fullcircle/4.
1479
1480    def degrees(self, fullcircle=360.0):
1481        """ Set angle measurement units to degrees.
1482
1483        Optional argument:
1484        fullcircle -  a number
1485
1486        Set angle measurement units, i. e. set number
1487        of 'degrees' for a full circle. Dafault value is
1488        360 degrees.
1489
1490        Example (for a Turtle instance named turtle):
1491        >>> turtle.left(90)
1492        >>> turtle.heading()
1493        90
1494
1495        Change angle measurement unit to grad (also known as gon,
1496        grade, or gradian and equals 1/100-th of the right angle.)
1497        >>> turtle.degrees(400.0)
1498        >>> turtle.heading()
1499        100
1500
1501        """
1502        self._setDegreesPerAU(fullcircle)
1503
1504    def radians(self):
1505        """ Set the angle measurement units to radians.
1506
1507        No arguments.
1508
1509        Example (for a Turtle instance named turtle):
1510        >>> turtle.heading()
1511        90
1512        >>> turtle.radians()
1513        >>> turtle.heading()
1514        1.5707963267948966
1515        """
1516        self._setDegreesPerAU(2*math.pi)
1517
1518    def _go(self, distance):
1519        """move turtle forward by specified distance"""
1520        ende = self._position + self._orient * distance
1521        self._goto(ende)
1522
1523    def _rotate(self, angle):
1524        """Turn turtle counterclockwise by specified angle if angle > 0."""
1525        angle *= self._degreesPerAU
1526        self._orient = self._orient.rotate(angle)
1527
1528    def _goto(self, end):
1529        """move turtle to position end."""
1530        self._position = end
1531
1532    def forward(self, distance):
1533        """Move the turtle forward by the specified distance.
1534
1535        Aliases: forward | fd
1536
1537        Argument:
1538        distance -- a number (integer or float)
1539
1540        Move the turtle forward by the specified distance, in the direction
1541        the turtle is headed.
1542
1543        Example (for a Turtle instance named turtle):
1544        >>> turtle.position()
1545        (0.00, 0.00)
1546        >>> turtle.forward(25)
1547        >>> turtle.position()
1548        (25.00,0.00)
1549        >>> turtle.forward(-75)
1550        >>> turtle.position()
1551        (-50.00,0.00)
1552        """
1553        self._go(distance)
1554
1555    def back(self, distance):
1556        """Move the turtle backward by distance.
1557
1558        Aliases: back | backward | bk
1559
1560        Argument:
1561        distance -- a number
1562
1563        Move the turtle backward by distance ,opposite to the direction the
1564        turtle is headed. Do not change the turtle's heading.
1565
1566        Example (for a Turtle instance named turtle):
1567        >>> turtle.position()
1568        (0.00, 0.00)
1569        >>> turtle.backward(30)
1570        >>> turtle.position()
1571        (-30.00, 0.00)
1572        """
1573        self._go(-distance)
1574
1575    def right(self, angle):
1576        """Turn turtle right by angle units.
1577
1578        Aliases: right | rt
1579
1580        Argument:
1581        angle -- a number (integer or float)
1582
1583        Turn turtle right by angle units. (Units are by default degrees,
1584        but can be set via the degrees() and radians() functions.)
1585        Angle orientation depends on mode. (See this.)
1586
1587        Example (for a Turtle instance named turtle):
1588        >>> turtle.heading()
1589        22.0
1590        >>> turtle.right(45)
1591        >>> turtle.heading()
1592        337.0
1593        """
1594        self._rotate(-angle)
1595
1596    def left(self, angle):
1597        """Turn turtle left by angle units.
1598
1599        Aliases: left | lt
1600
1601        Argument:
1602        angle -- a number (integer or float)
1603
1604        Turn turtle left by angle units. (Units are by default degrees,
1605        but can be set via the degrees() and radians() functions.)
1606        Angle orientation depends on mode. (See this.)
1607
1608        Example (for a Turtle instance named turtle):
1609        >>> turtle.heading()
1610        22.0
1611        >>> turtle.left(45)
1612        >>> turtle.heading()
1613        67.0
1614        """
1615        self._rotate(angle)
1616
1617    def pos(self):
1618        """Return the turtle's current location (x,y), as a Vec2D-vector.
1619
1620        Aliases: pos | position
1621
1622        No arguments.
1623
1624        Example (for a Turtle instance named turtle):
1625        >>> turtle.pos()
1626        (0.00, 240.00)
1627        """
1628        return self._position
1629
1630    def xcor(self):
1631        """ Return the turtle's x coordinate.
1632
1633        No arguments.
1634
1635        Example (for a Turtle instance named turtle):
1636        >>> reset()
1637        >>> turtle.left(60)
1638        >>> turtle.forward(100)
1639        >>> print turtle.xcor()
1640        50.0
1641        """
1642        return self._position[0]
1643
1644    def ycor(self):
1645        """ Return the turtle's y coordinate
1646        ---
1647        No arguments.
1648
1649        Example (for a Turtle instance named turtle):
1650        >>> reset()
1651        >>> turtle.left(60)
1652        >>> turtle.forward(100)
1653        >>> print turtle.ycor()
1654        86.6025403784
1655        """
1656        return self._position[1]
1657
1658
1659    def goto(self, x, y=None):
1660        """Move turtle to an absolute position.
1661
1662        Aliases: setpos | setposition | goto:
1663
1664        Arguments:
1665        x -- a number      or     a pair/vector of numbers
1666        y -- a number             None
1667
1668        call: goto(x, y)         # two coordinates
1669        --or: goto((x, y))       # a pair (tuple) of coordinates
1670        --or: goto(vec)          # e.g. as returned by pos()
1671
1672        Move turtle to an absolute position. If the pen is down,
1673        a line will be drawn. The turtle's orientation does not change.
1674
1675        Example (for a Turtle instance named turtle):
1676        >>> tp = turtle.pos()
1677        >>> tp
1678        (0.00, 0.00)
1679        >>> turtle.setpos(60,30)
1680        >>> turtle.pos()
1681        (60.00,30.00)
1682        >>> turtle.setpos((20,80))
1683        >>> turtle.pos()
1684        (20.00,80.00)
1685        >>> turtle.setpos(tp)
1686        >>> turtle.pos()
1687        (0.00,0.00)
1688        """
1689        if y is None:
1690            self._goto(Vec2D(*x))
1691        else:
1692            self._goto(Vec2D(x, y))
1693
1694    def home(self):
1695        """Move turtle to the origin - coordinates (0,0).
1696
1697        No arguments.
1698
1699        Move turtle to the origin - coordinates (0,0) and set its
1700        heading to its start-orientation (which depends on mode).
1701
1702        Example (for a Turtle instance named turtle):
1703        >>> turtle.home()
1704        """
1705        self.goto(0, 0)
1706        self.setheading(0)
1707
1708    def setx(self, x):
1709        """Set the turtle's first coordinate to x
1710
1711        Argument:
1712        x -- a number (integer or float)
1713
1714        Set the turtle's first coordinate to x, leave second coordinate
1715        unchanged.
1716
1717        Example (for a Turtle instance named turtle):
1718        >>> turtle.position()
1719        (0.00, 240.00)
1720        >>> turtle.setx(10)
1721        >>> turtle.position()
1722        (10.00, 240.00)
1723        """
1724        self._goto(Vec2D(x, self._position[1]))
1725
1726    def sety(self, y):
1727        """Set the turtle's second coordinate to y
1728
1729        Argument:
1730        y -- a number (integer or float)
1731
1732        Set the turtle's first coordinate to x, second coordinate remains
1733        unchanged.
1734
1735        Example (for a Turtle instance named turtle):
1736        >>> turtle.position()
1737        (0.00, 40.00)
1738        >>> turtle.sety(-10)
1739        >>> turtle.position()
1740        (0.00, -10.00)
1741        """
1742        self._goto(Vec2D(self._position[0], y))
1743
1744    def distance(self, x, y=None):
1745        """Return the distance from the turtle to (x,y) in turtle step units.
1746
1747        Arguments:
1748        x -- a number   or  a pair/vector of numbers   or   a turtle instance
1749        y -- a number       None                            None
1750
1751        call: distance(x, y)         # two coordinates
1752        --or: distance((x, y))       # a pair (tuple) of coordinates
1753        --or: distance(vec)          # e.g. as returned by pos()
1754        --or: distance(mypen)        # where mypen is another turtle
1755
1756        Example (for a Turtle instance named turtle):
1757        >>> turtle.pos()
1758        (0.00, 0.00)
1759        >>> turtle.distance(30,40)
1760        50.0
1761        >>> pen = Turtle()
1762        >>> pen.forward(77)
1763        >>> turtle.distance(pen)
1764        77.0
1765        """
1766        if y is not None:
1767            pos = Vec2D(x, y)
1768        if isinstance(x, Vec2D):
1769            pos = x
1770        elif isinstance(x, tuple):
1771            pos = Vec2D(*x)
1772        elif isinstance(x, TNavigator):
1773            pos = x._position
1774        return abs(pos - self._position)
1775
1776    def towards(self, x, y=None):
1777        """Return the angle of the line from the turtle's position to (x, y).
1778
1779        Arguments:
1780        x -- a number   or  a pair/vector of numbers   or   a turtle instance
1781        y -- a number       None                            None
1782
1783        call: distance(x, y)         # two coordinates
1784        --or: distance((x, y))       # a pair (tuple) of coordinates
1785        --or: distance(vec)          # e.g. as returned by pos()
1786        --or: distance(mypen)        # where mypen is another turtle
1787
1788        Return the angle, between the line from turtle-position to position
1789        specified by x, y and the turtle's start orientation. (Depends on
1790        modes - "standard" or "logo")
1791
1792        Example (for a Turtle instance named turtle):
1793        >>> turtle.pos()
1794        (10.00, 10.00)
1795        >>> turtle.towards(0,0)
1796        225.0
1797        """
1798        if y is not None:
1799            pos = Vec2D(x, y)
1800        if isinstance(x, Vec2D):
1801            pos = x
1802        elif isinstance(x, tuple):
1803            pos = Vec2D(*x)
1804        elif isinstance(x, TNavigator):
1805            pos = x._position
1806        x, y = pos - self._position
1807        result = round(math.atan2(y, x)*180.0/math.pi, 10) % 360.0
1808        result /= self._degreesPerAU
1809        return (self._angleOffset + self._angleOrient*result) % self._fullcircle
1810
1811    def heading(self):
1812        """ Return the turtle's current heading.
1813
1814        No arguments.
1815
1816        Example (for a Turtle instance named turtle):
1817        >>> turtle.left(67)
1818        >>> turtle.heading()
1819        67.0
1820        """
1821        x, y = self._orient
1822        result = round(math.atan2(y, x)*180.0/math.pi, 10) % 360.0
1823        result /= self._degreesPerAU
1824        return (self._angleOffset + self._angleOrient*result) % self._fullcircle
1825
1826    def setheading(self, to_angle):
1827        """Set the orientation of the turtle to to_angle.
1828
1829        Aliases:  setheading | seth
1830
1831        Argument:
1832        to_angle -- a number (integer or float)
1833
1834        Set the orientation of the turtle to to_angle.
1835        Here are some common directions in degrees:
1836
1837         standard - mode:          logo-mode:
1838        -------------------|--------------------
1839           0 - east                0 - north
1840          90 - north              90 - east
1841         180 - west              180 - south
1842         270 - south             270 - west
1843
1844        Example (for a Turtle instance named turtle):
1845        >>> turtle.setheading(90)
1846        >>> turtle.heading()
1847        90
1848        """
1849        angle = (to_angle - self.heading())*self._angleOrient
1850        full = self._fullcircle
1851        angle = (angle+full/2.)%full - full/2.
1852        self._rotate(angle)
1853
1854    def circle(self, radius, extent = None, steps = None):
1855        """ Draw a circle with given radius.
1856
1857        Arguments:
1858        radius -- a number
1859        extent (optional) -- a number
1860        steps (optional) -- an integer
1861
1862        Draw a circle with given radius. The center is radius units left
1863        of the turtle; extent - an angle - determines which part of the
1864        circle is drawn. If extent is not given, draw the entire circle.
1865        If extent is not a full circle, one endpoint of the arc is the
1866        current pen position. Draw the arc in counterclockwise direction
1867        if radius is positive, otherwise in clockwise direction. Finally
1868        the direction of the turtle is changed by the amount of extent.
1869
1870        As the circle is approximated by an inscribed regular polygon,
1871        steps determines the number of steps to use. If not given,
1872        it will be calculated automatically. Maybe used to draw regular
1873        polygons.
1874
1875        call: circle(radius)                  # full circle
1876        --or: circle(radius, extent)          # arc
1877        --or: circle(radius, extent, steps)
1878        --or: circle(radius, steps=6)         # 6-sided polygon
1879
1880        Example (for a Turtle instance named turtle):
1881        >>> turtle.circle(50)
1882        >>> turtle.circle(120, 180)  # semicircle
1883        """
1884        if self.undobuffer:
1885            self.undobuffer.push(["seq"])
1886            self.undobuffer.cumulate = True
1887        speed = self.speed()
1888        if extent is None:
1889            extent = self._fullcircle
1890        if steps is None:
1891            frac = abs(extent)/self._fullcircle
1892            steps = 1+int(min(11+abs(radius)/6.0, 59.0)*frac)
1893        w = 1.0 * extent / steps
1894        w2 = 0.5 * w
1895        l = 2.0 * radius * math.sin(w2*math.pi/180.0*self._degreesPerAU)
1896        if radius < 0:
1897            l, w, w2 = -l, -w, -w2
1898        tr = self.tracer()
1899        dl = self._delay()
1900        if speed == 0:
1901            self.tracer(0, 0)
1902        else:
1903            self.speed(0)
1904        self._rotate(w2)
1905        for i in range(steps):
1906            self.speed(speed)
1907            self._go(l)
1908            self.speed(0)
1909            self._rotate(w)
1910        self._rotate(-w2)
1911        if speed == 0:
1912            self.tracer(tr, dl)
1913        self.speed(speed)
1914        if self.undobuffer:
1915            self.undobuffer.cumulate = False
1916
1917## three dummy methods to be implemented by child class:
1918
1919    def speed(self, s=0):
1920        """dummy method - to be overwritten by child class"""
1921    def tracer(self, a=None, b=None):
1922        """dummy method - to be overwritten by child class"""
1923    def _delay(self, n=None):
1924        """dummy method - to be overwritten by child class"""
1925
1926    fd = forward
1927    bk = back
1928    backward = back
1929    rt = right
1930    lt = left
1931    position = pos
1932    setpos = goto
1933    setposition = goto
1934    seth = setheading
1935
1936
1937class TPen(object):
1938    """Drawing part of the RawTurtle.
1939    Implements drawing properties.
1940    """
1941    def __init__(self, resizemode=_CFG["resizemode"]):
1942        self._resizemode = resizemode # or "user" or "noresize"
1943        self.undobuffer = None
1944        TPen._reset(self)
1945
1946    def _reset(self, pencolor=_CFG["pencolor"],
1947                     fillcolor=_CFG["fillcolor"]):
1948        self._pensize = 1
1949        self._shown = True
1950        self._pencolor = pencolor
1951        self._fillcolor = fillcolor
1952        self._drawing = True
1953        self._speed = 3
1954        self._stretchfactor = (1, 1)
1955        self._tilt = 0
1956        self._outlinewidth = 1
1957        ### self.screen = None  # to override by child class
1958
1959    def resizemode(self, rmode=None):
1960        """Set resizemode to one of the values: "auto", "user", "noresize".
1961
1962        (Optional) Argument:
1963        rmode -- one of the strings "auto", "user", "noresize"
1964
1965        Different resizemodes have the following effects:
1966          - "auto" adapts the appearance of the turtle
1967                   corresponding to the value of pensize.
1968          - "user" adapts the appearance of the turtle according to the
1969                   values of stretchfactor and outlinewidth (outline),
1970                   which are set by shapesize()
1971          - "noresize" no adaption of the turtle's appearance takes place.
1972        If no argument is given, return current resizemode.
1973        resizemode("user") is called by a call of shapesize with arguments.
1974
1975
1976        Examples (for a Turtle instance named turtle):
1977        >>> turtle.resizemode("noresize")
1978        >>> turtle.resizemode()
1979        'noresize'
1980        """
1981        if rmode is None:
1982            return self._resizemode
1983        rmode = rmode.lower()
1984        if rmode in ["auto", "user", "noresize"]:
1985            self.pen(resizemode=rmode)
1986
1987    def pensize(self, width=None):
1988        """Set or return the line thickness.
1989
1990        Aliases:  pensize | width
1991
1992        Argument:
1993        width -- positive number
1994
1995        Set the line thickness to width or return it. If resizemode is set
1996        to "auto" and turtleshape is a polygon, that polygon is drawn with
1997        the same line thickness. If no argument is given, current pensize
1998        is returned.
1999
2000        Example (for a Turtle instance named turtle):
2001        >>> turtle.pensize()
2002        1
2003        >>> turtle.pensize(10)   # from here on lines of width 10 are drawn
2004        """
2005        if width is None:
2006            return self._pensize
2007        self.pen(pensize=width)
2008
2009
2010    def penup(self):
2011        """Pull the pen up -- no drawing when moving.
2012
2013        Aliases: penup | pu | up
2014
2015        No argument
2016
2017        Example (for a Turtle instance named turtle):
2018        >>> turtle.penup()
2019        """
2020        if not self._drawing:
2021            return
2022        self.pen(pendown=False)
2023
2024    def pendown(self):
2025        """Pull the pen down -- drawing when moving.
2026
2027        Aliases: pendown | pd | down
2028
2029        No argument.
2030
2031        Example (for a Turtle instance named turtle):
2032        >>> turtle.pendown()
2033        """
2034        if self._drawing:
2035            return
2036        self.pen(pendown=True)
2037
2038    def isdown(self):
2039        """Return True if pen is down, False if it's up.
2040
2041        No argument.
2042
2043        Example (for a Turtle instance named turtle):
2044        >>> turtle.penup()
2045        >>> turtle.isdown()
2046        False
2047        >>> turtle.pendown()
2048        >>> turtle.isdown()
2049        True
2050        """
2051        return self._drawing
2052
2053    def speed(self, speed=None):
2054        """ Return or set the turtle's speed.
2055
2056        Optional argument:
2057        speed -- an integer in the range 0..10 or a speedstring (see below)
2058
2059        Set the turtle's speed to an integer value in the range 0 .. 10.
2060        If no argument is given: return current speed.
2061
2062        If input is a number greater than 10 or smaller than 0.5,
2063        speed is set to 0.
2064        Speedstrings  are mapped to speedvalues in the following way:
2065            'fastest' :  0
2066            'fast'    :  10
2067            'normal'  :  6
2068            'slow'    :  3
2069            'slowest' :  1
2070        speeds from 1 to 10 enforce increasingly faster animation of
2071        line drawing and turtle turning.
2072
2073        Attention:
2074        speed = 0 : *no* animation takes place. forward/back makes turtle jump
2075        and likewise left/right make the turtle turn instantly.
2076
2077        Example (for a Turtle instance named turtle):
2078        >>> turtle.speed(3)
2079        """
2080        speeds = {'fastest':0, 'fast':10, 'normal':6, 'slow':3, 'slowest':1 }
2081        if speed is None:
2082            return self._speed
2083        if speed in speeds:
2084            speed = speeds[speed]
2085        elif 0.5 < speed < 10.5:
2086            speed = int(round(speed))
2087        else:
2088            speed = 0
2089        self.pen(speed=speed)
2090
2091    def color(self, *args):
2092        """Return or set the pencolor and fillcolor.
2093
2094        Arguments:
2095        Several input formats are allowed.
2096        They use 0, 1, 2, or 3 arguments as follows:
2097
2098        color()
2099            Return the current pencolor and the current fillcolor
2100            as a pair of color specification strings as are returned
2101            by pencolor and fillcolor.
2102        color(colorstring), color((r,g,b)), color(r,g,b)
2103            inputs as in pencolor, set both, fillcolor and pencolor,
2104            to the given value.
2105        color(colorstring1, colorstring2),
2106        color((r1,g1,b1), (r2,g2,b2))
2107            equivalent to pencolor(colorstring1) and fillcolor(colorstring2)
2108            and analogously, if the other input format is used.
2109
2110        If turtleshape is a polygon, outline and interior of that polygon
2111        is drawn with the newly set colors.
2112        For mor info see: pencolor, fillcolor
2113
2114        Example (for a Turtle instance named turtle):
2115        >>> turtle.color('red', 'green')
2116        >>> turtle.color()
2117        ('red', 'green')
2118        >>> colormode(255)
2119        >>> color((40, 80, 120), (160, 200, 240))
2120        >>> color()
2121        ('#285078', '#a0c8f0')
2122        """
2123        if args:
2124            l = len(args)
2125            if l == 1:
2126                pcolor = fcolor = args[0]
2127            elif l == 2:
2128                pcolor, fcolor = args
2129            elif l == 3:
2130                pcolor = fcolor = args
2131            pcolor = self._colorstr(pcolor)
2132            fcolor = self._colorstr(fcolor)
2133            self.pen(pencolor=pcolor, fillcolor=fcolor)
2134        else:
2135            return self._color(self._pencolor), self._color(self._fillcolor)
2136
2137    def pencolor(self, *args):
2138        """ Return or set the pencolor.
2139
2140        Arguments:
2141        Four input formats are allowed:
2142          - pencolor()
2143            Return the current pencolor as color specification string,
2144            possibly in hex-number format (see example).
2145            May be used as input to another color/pencolor/fillcolor call.
2146          - pencolor(colorstring)
2147            s is a Tk color specification string, such as "red" or "yellow"
2148          - pencolor((r, g, b))
2149            *a tuple* of r, g, and b, which represent, an RGB color,
2150            and each of r, g, and b are in the range 0..colormode,
2151            where colormode is either 1.0 or 255
2152          - pencolor(r, g, b)
2153            r, g, and b represent an RGB color, and each of r, g, and b
2154            are in the range 0..colormode
2155
2156        If turtleshape is a polygon, the outline of that polygon is drawn
2157        with the newly set pencolor.
2158
2159        Example (for a Turtle instance named turtle):
2160        >>> turtle.pencolor('brown')
2161        >>> tup = (0.2, 0.8, 0.55)
2162        >>> turtle.pencolor(tup)
2163        >>> turtle.pencolor()
2164        '#33cc8c'
2165        """
2166        if args:
2167            color = self._colorstr(args)
2168            if color == self._pencolor:
2169                return
2170            self.pen(pencolor=color)
2171        else:
2172            return self._color(self._pencolor)
2173
2174    def fillcolor(self, *args):
2175        """ Return or set the fillcolor.
2176
2177        Arguments:
2178        Four input formats are allowed:
2179          - fillcolor()
2180            Return the current fillcolor as color specification string,
2181            possibly in hex-number format (see example).
2182            May be used as input to another color/pencolor/fillcolor call.
2183          - fillcolor(colorstring)
2184            s is a Tk color specification string, such as "red" or "yellow"
2185          - fillcolor((r, g, b))
2186            *a tuple* of r, g, and b, which represent, an RGB color,
2187            and each of r, g, and b are in the range 0..colormode,
2188            where colormode is either 1.0 or 255
2189          - fillcolor(r, g, b)
2190            r, g, and b represent an RGB color, and each of r, g, and b
2191            are in the range 0..colormode
2192
2193        If turtleshape is a polygon, the interior of that polygon is drawn
2194        with the newly set fillcolor.
2195
2196        Example (for a Turtle instance named turtle):
2197        >>> turtle.fillcolor('violet')
2198        >>> col = turtle.pencolor()
2199        >>> turtle.fillcolor(col)
2200        >>> turtle.fillcolor(0, .5, 0)
2201        """
2202        if args:
2203            color = self._colorstr(args)
2204            if color == self._fillcolor:
2205                return
2206            self.pen(fillcolor=color)
2207        else:
2208            return self._color(self._fillcolor)
2209
2210    def showturtle(self):
2211        """Makes the turtle visible.
2212
2213        Aliases: showturtle | st
2214
2215        No argument.
2216
2217        Example (for a Turtle instance named turtle):
2218        >>> turtle.hideturtle()
2219        >>> turtle.showturtle()
2220        """
2221        self.pen(shown=True)
2222
2223    def hideturtle(self):
2224        """Makes the turtle invisible.
2225
2226        Aliases: hideturtle | ht
2227
2228        No argument.
2229
2230        It's a good idea to do this while you're in the
2231        middle of a complicated drawing, because hiding
2232        the turtle speeds up the drawing observably.
2233
2234        Example (for a Turtle instance named turtle):
2235        >>> turtle.hideturtle()
2236        """
2237        self.pen(shown=False)
2238
2239    def isvisible(self):
2240        """Return True if the Turtle is shown, False if it's hidden.
2241
2242        No argument.
2243
2244        Example (for a Turtle instance named turtle):
2245        >>> turtle.hideturtle()
2246        >>> print turtle.isvisible():
2247        False
2248        """
2249        return self._shown
2250
2251    def pen(self, pen=None, **pendict):
2252        """Return or set the pen's attributes.
2253
2254        Arguments:
2255            pen -- a dictionary with some or all of the below listed keys.
2256            **pendict -- one or more keyword-arguments with the below
2257                         listed keys as keywords.
2258
2259        Return or set the pen's attributes in a 'pen-dictionary'
2260        with the following key/value pairs:
2261           "shown"      :   True/False
2262           "pendown"    :   True/False
2263           "pencolor"   :   color-string or color-tuple
2264           "fillcolor"  :   color-string or color-tuple
2265           "pensize"    :   positive number
2266           "speed"      :   number in range 0..10
2267           "resizemode" :   "auto" or "user" or "noresize"
2268           "stretchfactor": (positive number, positive number)
2269           "outline"    :   positive number
2270           "tilt"       :   number
2271
2272        This dictionary can be used as argument for a subsequent
2273        pen()-call to restore the former pen-state. Moreover one
2274        or more of these attributes can be provided as keyword-arguments.
2275        This can be used to set several pen attributes in one statement.
2276
2277
2278        Examples (for a Turtle instance named turtle):
2279        >>> turtle.pen(fillcolor="black", pencolor="red", pensize=10)
2280        >>> turtle.pen()
2281        {'pensize': 10, 'shown': True, 'resizemode': 'auto', 'outline': 1,
2282        'pencolor': 'red', 'pendown': True, 'fillcolor': 'black',
2283        'stretchfactor': (1,1), 'speed': 3}
2284        >>> penstate=turtle.pen()
2285        >>> turtle.color("yellow","")
2286        >>> turtle.penup()
2287        >>> turtle.pen()
2288        {'pensize': 10, 'shown': True, 'resizemode': 'auto', 'outline': 1,
2289        'pencolor': 'yellow', 'pendown': False, 'fillcolor': '',
2290        'stretchfactor': (1,1), 'speed': 3}
2291        >>> p.pen(penstate, fillcolor="green")
2292        >>> p.pen()
2293        {'pensize': 10, 'shown': True, 'resizemode': 'auto', 'outline': 1,
2294        'pencolor': 'red', 'pendown': True, 'fillcolor': 'green',
2295        'stretchfactor': (1,1), 'speed': 3}
2296        """
2297        _pd =  {"shown"         : self._shown,
2298                "pendown"       : self._drawing,
2299                "pencolor"      : self._pencolor,
2300                "fillcolor"     : self._fillcolor,
2301                "pensize"       : self._pensize,
2302                "speed"         : self._speed,
2303                "resizemode"    : self._resizemode,
2304                "stretchfactor" : self._stretchfactor,
2305                "outline"       : self._outlinewidth,
2306                "tilt"          : self._tilt
2307               }
2308
2309        if not (pen or pendict):
2310            return _pd
2311
2312        if isinstance(pen, dict):
2313            p = pen
2314        else:
2315            p = {}
2316        p.update(pendict)
2317
2318        _p_buf = {}
2319        for key in p:
2320            _p_buf[key] = _pd[key]
2321
2322        if self.undobuffer:
2323            self.undobuffer.push(("pen", _p_buf))
2324
2325        newLine = False
2326        if "pendown" in p:
2327            if self._drawing != p["pendown"]:
2328                newLine = True
2329        if "pencolor" in p:
2330            if isinstance(p["pencolor"], tuple):
2331                p["pencolor"] = self._colorstr((p["pencolor"],))
2332            if self._pencolor != p["pencolor"]:
2333                newLine = True
2334        if "pensize" in p:
2335            if self._pensize != p["pensize"]:
2336                newLine = True
2337        if newLine:
2338            self._newLine()
2339        if "pendown" in p:
2340            self._drawing = p["pendown"]
2341        if "pencolor" in p:
2342            self._pencolor = p["pencolor"]
2343        if "pensize" in p:
2344            self._pensize = p["pensize"]
2345        if "fillcolor" in p:
2346            if isinstance(p["fillcolor"], tuple):
2347                p["fillcolor"] = self._colorstr((p["fillcolor"],))
2348            self._fillcolor = p["fillcolor"]
2349        if "speed" in p:
2350            self._speed = p["speed"]
2351        if "resizemode" in p:
2352            self._resizemode = p["resizemode"]
2353        if "stretchfactor" in p:
2354            sf = p["stretchfactor"]
2355            if isinstance(sf, (int, long, float)):
2356                sf = (sf, sf)
2357            self._stretchfactor = sf
2358        if "outline" in p:
2359            self._outlinewidth = p["outline"]
2360        if "shown" in p:
2361            self._shown = p["shown"]
2362        if "tilt" in p:
2363            self._tilt = p["tilt"]
2364        self._update()
2365
2366## three dummy methods to be implemented by child class:
2367
2368    def _newLine(self, usePos = True):
2369        """dummy method - to be overwritten by child class"""
2370    def _update(self, count=True, forced=False):
2371        """dummy method - to be overwritten by child class"""
2372    def _color(self, args):
2373        """dummy method - to be overwritten by child class"""
2374    def _colorstr(self, args):
2375        """dummy method - to be overwritten by child class"""
2376
2377    width = pensize
2378    up = penup
2379    pu = penup
2380    pd = pendown
2381    down = pendown
2382    st = showturtle
2383    ht = hideturtle
2384
2385
2386class _TurtleImage(object):
2387    """Helper class: Datatype to store Turtle attributes
2388    """
2389
2390    def __init__(self, screen, shapeIndex):
2391        self.screen = screen
2392        self._type = None
2393        self._setshape(shapeIndex)
2394
2395    def _setshape(self, shapeIndex):
2396        screen = self.screen # RawTurtle.screens[self.screenIndex]
2397        self.shapeIndex = shapeIndex
2398        if self._type == "polygon" == screen._shapes[shapeIndex]._type:
2399            return
2400        if self._type == "image" == screen._shapes[shapeIndex]._type:
2401            return
2402        if self._type in ["image", "polygon"]:
2403            screen._delete(self._item)
2404        elif self._type == "compound":
2405            for item in self._item:
2406                screen._delete(item)
2407        self._type = screen._shapes[shapeIndex]._type
2408        if self._type == "polygon":
2409            self._item = screen._createpoly()
2410        elif self._type == "image":
2411            self._item = screen._createimage(screen._shapes["blank"]._data)
2412        elif self._type == "compound":
2413            self._item = [screen._createpoly() for item in
2414                                          screen._shapes[shapeIndex]._data]
2415
2416
2417class RawTurtle(TPen, TNavigator):
2418    """Animation part of the RawTurtle.
2419    Puts RawTurtle upon a TurtleScreen and provides tools for
2420    its animation.
2421    """
2422    screens = []
2423
2424    def __init__(self, canvas=None,
2425                 shape=_CFG["shape"],
2426                 undobuffersize=_CFG["undobuffersize"],
2427                 visible=_CFG["visible"]):
2428        if isinstance(canvas, _Screen):
2429            self.screen = canvas
2430        elif isinstance(canvas, TurtleScreen):
2431            if canvas not in RawTurtle.screens:
2432                RawTurtle.screens.append(canvas)
2433            self.screen = canvas
2434        elif isinstance(canvas, (ScrolledCanvas, Canvas)):
2435            for screen in RawTurtle.screens:
2436                if screen.cv == canvas:
2437                    self.screen = screen
2438                    break
2439            else:
2440                self.screen = TurtleScreen(canvas)
2441                RawTurtle.screens.append(self.screen)
2442        else:
2443            raise TurtleGraphicsError("bad canvas argument %s" % canvas)
2444
2445        screen = self.screen
2446        TNavigator.__init__(self, screen.mode())
2447        TPen.__init__(self)
2448        screen._turtles.append(self)
2449        self.drawingLineItem = screen._createline()
2450        self.turtle = _TurtleImage(screen, shape)
2451        self._poly = None
2452        self._creatingPoly = False
2453        self._fillitem = self._fillpath = None
2454        self._shown = visible
2455        self._hidden_from_screen = False
2456        self.currentLineItem = screen._createline()
2457        self.currentLine = [self._position]
2458        self.items = [self.currentLineItem]
2459        self.stampItems = []
2460        self._undobuffersize = undobuffersize
2461        self.undobuffer = Tbuffer(undobuffersize)
2462        self._update()
2463
2464    def reset(self):
2465        """Delete the turtle's drawings and restore its default values.
2466
2467        No argument.
2468,
2469        Delete the turtle's drawings from the screen, re-center the turtle
2470        and set variables to the default values.
2471
2472        Example (for a Turtle instance named turtle):
2473        >>> turtle.position()
2474        (0.00,-22.00)
2475        >>> turtle.heading()
2476        100.0
2477        >>> turtle.reset()
2478        >>> turtle.position()
2479        (0.00,0.00)
2480        >>> turtle.heading()
2481        0.0
2482        """
2483        TNavigator.reset(self)
2484        TPen._reset(self)
2485        self._clear()
2486        self._drawturtle()
2487        self._update()
2488
2489    def setundobuffer(self, size):
2490        """Set or disable undobuffer.
2491
2492        Argument:
2493        size -- an integer or None
2494
2495        If size is an integer an empty undobuffer of given size is installed.
2496        Size gives the maximum number of turtle-actions that can be undone
2497        by the undo() function.
2498        If size is None, no undobuffer is present.
2499
2500        Example (for a Turtle instance named turtle):
2501        >>> turtle.setundobuffer(42)
2502        """
2503        if size is None or size <= 0:
2504            self.undobuffer = None
2505        else:
2506            self.undobuffer = Tbuffer(size)
2507
2508    def undobufferentries(self):
2509        """Return count of entries in the undobuffer.
2510
2511        No argument.
2512
2513        Example (for a Turtle instance named turtle):
2514        >>> while undobufferentries():
2515        ...     undo()
2516        """
2517        if self.undobuffer is None:
2518            return 0
2519        return self.undobuffer.nr_of_items()
2520
2521    def _clear(self):
2522        """Delete all of pen's drawings"""
2523        self._fillitem = self._fillpath = None
2524        for item in self.items:
2525            self.screen._delete(item)
2526        self.currentLineItem = self.screen._createline()
2527        self.currentLine = []
2528        if self._drawing:
2529            self.currentLine.append(self._position)
2530        self.items = [self.currentLineItem]
2531        self.clearstamps()
2532        self.setundobuffer(self._undobuffersize)
2533
2534
2535    def clear(self):
2536        """Delete the turtle's drawings from the screen. Do not move turtle.
2537
2538        No arguments.
2539
2540        Delete the turtle's drawings from the screen. Do not move turtle.
2541        State and position of the turtle as well as drawings of other
2542        turtles are not affected.
2543
2544        Examples (for a Turtle instance named turtle):
2545        >>> turtle.clear()
2546        """
2547        self._clear()
2548        self._update()
2549
2550    def _update_data(self):
2551        self.screen._incrementudc()
2552        if self.screen._updatecounter != 0:
2553            return
2554        if len(self.currentLine)>1:
2555            self.screen._drawline(self.currentLineItem, self.currentLine,
2556                                  self._pencolor, self._pensize)
2557
2558    def _update(self):
2559        """Perform a Turtle-data update.
2560        """
2561        screen = self.screen
2562        if screen._tracing == 0:
2563            return
2564        elif screen._tracing == 1:
2565            self._update_data()
2566            self._drawturtle()
2567            screen._update()                  # TurtleScreenBase
2568            screen._delay(screen._delayvalue) # TurtleScreenBase
2569        else:
2570            self._update_data()
2571            if screen._updatecounter == 0:
2572                for t in screen.turtles():
2573                    t._drawturtle()
2574                screen._update()
2575
2576    def tracer(self, flag=None, delay=None):
2577        """Turns turtle animation on/off and set delay for update drawings.
2578
2579        Optional arguments:
2580        n -- nonnegative  integer
2581        delay -- nonnegative  integer
2582
2583        If n is given, only each n-th regular screen update is really performed.
2584        (Can be used to accelerate the drawing of complex graphics.)
2585        Second arguments sets delay value (see RawTurtle.delay())
2586
2587        Example (for a Turtle instance named turtle):
2588        >>> turtle.tracer(8, 25)
2589        >>> dist = 2
2590        >>> for i in range(200):
2591        ...     turtle.fd(dist)
2592        ...     turtle.rt(90)
2593        ...     dist += 2
2594        """
2595        return self.screen.tracer(flag, delay)
2596
2597    def _color(self, args):
2598        return self.screen._color(args)
2599
2600    def _colorstr(self, args):
2601        return self.screen._colorstr(args)
2602
2603    def _cc(self, args):
2604        """Convert colortriples to hexstrings.
2605        """
2606        if isinstance(args, basestring):
2607            return args
2608        try:
2609            r, g, b = args
2610        except (TypeError, ValueError):
2611            raise TurtleGraphicsError("bad color arguments: %s" % str(args))
2612        if self.screen._colormode == 1.0:
2613            r, g, b = [round(255.0*x) for x in (r, g, b)]
2614        if not ((0 <= r <= 255) and (0 <= g <= 255) and (0 <= b <= 255)):
2615            raise TurtleGraphicsError("bad color sequence: %s" % str(args))
2616        return "#%02x%02x%02x" % (r, g, b)
2617
2618    def clone(self):
2619        """Create and return a clone of the turtle.
2620
2621        No argument.
2622
2623        Create and return a clone of the turtle with same position, heading
2624        and turtle properties.
2625
2626        Example (for a Turtle instance named mick):
2627        mick = Turtle()
2628        joe = mick.clone()
2629        """
2630        screen = self.screen
2631        self._newLine(self._drawing)
2632
2633        turtle = self.turtle
2634        self.screen = None
2635        self.turtle = None  # too make self deepcopy-able
2636
2637        q = deepcopy(self)
2638
2639        self.screen = screen
2640        self.turtle = turtle
2641
2642        q.screen = screen
2643        q.turtle = _TurtleImage(screen, self.turtle.shapeIndex)
2644
2645        screen._turtles.append(q)
2646        ttype = screen._shapes[self.turtle.shapeIndex]._type
2647        if ttype == "polygon":
2648            q.turtle._item = screen._createpoly()
2649        elif ttype == "image":
2650            q.turtle._item = screen._createimage(screen._shapes["blank"]._data)
2651        elif ttype == "compound":
2652            q.turtle._item = [screen._createpoly() for item in
2653                              screen._shapes[self.turtle.shapeIndex]._data]
2654        q.currentLineItem = screen._createline()
2655        q._update()
2656        return q
2657
2658    def shape(self, name=None):
2659        """Set turtle shape to shape with given name / return current shapename.
2660
2661        Optional argument:
2662        name -- a string, which is a valid shapename
2663
2664        Set turtle shape to shape with given name or, if name is not given,
2665        return name of current shape.
2666        Shape with name must exist in the TurtleScreen's shape dictionary.
2667        Initially there are the following polygon shapes:
2668        'arrow', 'turtle', 'circle', 'square', 'triangle', 'classic'.
2669        To learn about how to deal with shapes see Screen-method register_shape.
2670
2671        Example (for a Turtle instance named turtle):
2672        >>> turtle.shape()
2673        'arrow'
2674        >>> turtle.shape("turtle")
2675        >>> turtle.shape()
2676        'turtle'
2677        """
2678        if name is None:
2679            return self.turtle.shapeIndex
2680        if not name in self.screen.getshapes():
2681            raise TurtleGraphicsError("There is no shape named %s" % name)
2682        self.turtle._setshape(name)
2683        self._update()
2684
2685    def shapesize(self, stretch_wid=None, stretch_len=None, outline=None):
2686        """Set/return turtle's stretchfactors/outline. Set resizemode to "user".
2687
2688        Optional arguments:
2689           stretch_wid : positive number
2690           stretch_len : positive number
2691           outline  : positive number
2692
2693        Return or set the pen's attributes x/y-stretchfactors and/or outline.
2694        Set resizemode to "user".
2695        If and only if resizemode is set to "user", the turtle will be displayed
2696        stretched according to its stretchfactors:
2697        stretch_wid is stretchfactor perpendicular to orientation
2698        stretch_len is stretchfactor in direction of turtles orientation.
2699        outline determines the width of the shapes's outline.
2700
2701        Examples (for a Turtle instance named turtle):
2702        >>> turtle.resizemode("user")
2703        >>> turtle.shapesize(5, 5, 12)
2704        >>> turtle.shapesize(outline=8)
2705        """
2706        if stretch_wid is stretch_len is outline is None:
2707            stretch_wid, stretch_len = self._stretchfactor
2708            return stretch_wid, stretch_len, self._outlinewidth
2709        if stretch_wid is not None:
2710            if stretch_len is None:
2711                stretchfactor = stretch_wid, stretch_wid
2712            else:
2713                stretchfactor = stretch_wid, stretch_len
2714        elif stretch_len is not None:
2715            stretchfactor = self._stretchfactor[0], stretch_len
2716        else:
2717            stretchfactor = self._stretchfactor
2718        if outline is None:
2719            outline = self._outlinewidth
2720        self.pen(resizemode="user",
2721                 stretchfactor=stretchfactor, outline=outline)
2722
2723    def settiltangle(self, angle):
2724        """Rotate the turtleshape to point in the specified direction
2725
2726        Optional argument:
2727        angle -- number
2728
2729        Rotate the turtleshape to point in the direction specified by angle,
2730        regardless of its current tilt-angle. DO NOT change the turtle's
2731        heading (direction of movement).
2732
2733
2734        Examples (for a Turtle instance named turtle):
2735        >>> turtle.shape("circle")
2736        >>> turtle.shapesize(5,2)
2737        >>> turtle.settiltangle(45)
2738        >>> stamp()
2739        >>> turtle.fd(50)
2740        >>> turtle.settiltangle(-45)
2741        >>> stamp()
2742        >>> turtle.fd(50)
2743        """
2744        tilt = -angle * self._degreesPerAU * self._angleOrient
2745        tilt = (tilt * math.pi / 180.0) % (2*math.pi)
2746        self.pen(resizemode="user", tilt=tilt)
2747
2748    def tiltangle(self):
2749        """Return the current tilt-angle.
2750
2751        No argument.
2752
2753        Return the current tilt-angle, i. e. the angle between the
2754        orientation of the turtleshape and the heading of the turtle
2755        (its direction of movement).
2756
2757        Examples (for a Turtle instance named turtle):
2758        >>> turtle.shape("circle")
2759        >>> turtle.shapesize(5,2)
2760        >>> turtle.tilt(45)
2761        >>> turtle.tiltangle()
2762        """
2763        tilt = -self._tilt * (180.0/math.pi) * self._angleOrient
2764        return (tilt / self._degreesPerAU) % self._fullcircle
2765
2766    def tilt(self, angle):
2767        """Rotate the turtleshape by angle.
2768
2769        Argument:
2770        angle - a number
2771
2772        Rotate the turtleshape by angle from its current tilt-angle,
2773        but do NOT change the turtle's heading (direction of movement).
2774
2775        Examples (for a Turtle instance named turtle):
2776        >>> turtle.shape("circle")
2777        >>> turtle.shapesize(5,2)
2778        >>> turtle.tilt(30)
2779        >>> turtle.fd(50)
2780        >>> turtle.tilt(30)
2781        >>> turtle.fd(50)
2782        """
2783        self.settiltangle(angle + self.tiltangle())
2784
2785    def _polytrafo(self, poly):
2786        """Computes transformed polygon shapes from a shape
2787        according to current position and heading.
2788        """
2789        screen = self.screen
2790        p0, p1 = self._position
2791        e0, e1 = self._orient
2792        e = Vec2D(e0, e1 * screen.yscale / screen.xscale)
2793        e0, e1 = (1.0 / abs(e)) * e
2794        return [(p0+(e1*x+e0*y)/screen.xscale, p1+(-e0*x+e1*y)/screen.yscale)
2795                                                           for (x, y) in poly]
2796
2797    def _drawturtle(self):
2798        """Manages the correct rendering of the turtle with respect to
2799        its shape, resizemode, stretch and tilt etc."""
2800        screen = self.screen
2801        shape = screen._shapes[self.turtle.shapeIndex]
2802        ttype = shape._type
2803        titem = self.turtle._item
2804        if self._shown and screen._updatecounter == 0 and screen._tracing > 0:
2805            self._hidden_from_screen = False
2806            tshape = shape._data
2807            if ttype == "polygon":
2808                if self._resizemode == "noresize":
2809                    w = 1
2810                    shape = tshape
2811                else:
2812                    if self._resizemode == "auto":
2813                        lx = ly = max(1, self._pensize/5.0)
2814                        w = self._pensize
2815                        tiltangle = 0
2816                    elif self._resizemode == "user":
2817                        lx, ly = self._stretchfactor
2818                        w = self._outlinewidth
2819                        tiltangle = self._tilt
2820                    shape = [(lx*x, ly*y) for (x, y) in tshape]
2821                    t0, t1 = math.sin(tiltangle), math.cos(tiltangle)
2822                    shape = [(t1*x+t0*y, -t0*x+t1*y) for (x, y) in shape]
2823                shape = self._polytrafo(shape)
2824                fc, oc = self._fillcolor, self._pencolor
2825                screen._drawpoly(titem, shape, fill=fc, outline=oc,
2826                                                      width=w, top=True)
2827            elif ttype == "image":
2828                screen._drawimage(titem, self._position, tshape)
2829            elif ttype == "compound":
2830                lx, ly = self._stretchfactor
2831                w = self._outlinewidth
2832                for item, (poly, fc, oc) in zip(titem, tshape):
2833                    poly = [(lx*x, ly*y) for (x, y) in poly]
2834                    poly = self._polytrafo(poly)
2835                    screen._drawpoly(item, poly, fill=self._cc(fc),
2836                                     outline=self._cc(oc), width=w, top=True)
2837        else:
2838            if self._hidden_from_screen:
2839                return
2840            if ttype == "polygon":
2841                screen._drawpoly(titem, ((0, 0), (0, 0), (0, 0)), "", "")
2842            elif ttype == "image":
2843                screen._drawimage(titem, self._position,
2844                                          screen._shapes["blank"]._data)
2845            elif ttype == "compound":
2846                for item in titem:
2847                    screen._drawpoly(item, ((0, 0), (0, 0), (0, 0)), "", "")
2848            self._hidden_from_screen = True
2849
2850##############################  stamp stuff  ###############################
2851
2852    def stamp(self):
2853        """Stamp a copy of the turtleshape onto the canvas and return its id.
2854
2855        No argument.
2856
2857        Stamp a copy of the turtle shape onto the canvas at the current
2858        turtle position. Return a stamp_id for that stamp, which can be
2859        used to delete it by calling clearstamp(stamp_id).
2860
2861        Example (for a Turtle instance named turtle):
2862        >>> turtle.color("blue")
2863        >>> turtle.stamp()
2864        13
2865        >>> turtle.fd(50)
2866        """
2867        screen = self.screen
2868        shape = screen._shapes[self.turtle.shapeIndex]
2869        ttype = shape._type
2870        tshape = shape._data
2871        if ttype == "polygon":
2872            stitem = screen._createpoly()
2873            if self._resizemode == "noresize":
2874                w = 1
2875                shape = tshape
2876            else:
2877                if self._resizemode == "auto":
2878                    lx = ly = max(1, self._pensize/5.0)
2879                    w = self._pensize
2880                    tiltangle = 0
2881                elif self._resizemode == "user":
2882                    lx, ly = self._stretchfactor
2883                    w = self._outlinewidth
2884                    tiltangle = self._tilt
2885                shape = [(lx*x, ly*y) for (x, y) in tshape]
2886                t0, t1 = math.sin(tiltangle), math.cos(tiltangle)
2887                shape = [(t1*x+t0*y, -t0*x+t1*y) for (x, y) in shape]
2888            shape = self._polytrafo(shape)
2889            fc, oc = self._fillcolor, self._pencolor
2890            screen._drawpoly(stitem, shape, fill=fc, outline=oc,
2891                                                  width=w, top=True)
2892        elif ttype == "image":
2893            stitem = screen._createimage("")
2894            screen._drawimage(stitem, self._position, tshape)
2895        elif ttype == "compound":
2896            stitem = []
2897            for element in tshape:
2898                item = screen._createpoly()
2899                stitem.append(item)
2900            stitem = tuple(stitem)
2901            lx, ly = self._stretchfactor
2902            w = self._outlinewidth
2903            for item, (poly, fc, oc) in zip(stitem, tshape):
2904                poly = [(lx*x, ly*y) for (x, y) in poly]
2905                poly = self._polytrafo(poly)
2906                screen._drawpoly(item, poly, fill=self._cc(fc),
2907                                 outline=self._cc(oc), width=w, top=True)
2908        self.stampItems.append(stitem)
2909        self.undobuffer.push(("stamp", stitem))
2910        return stitem
2911
2912    def _clearstamp(self, stampid):
2913        """does the work for clearstamp() and clearstamps()
2914        """
2915        if stampid in self.stampItems:
2916            if isinstance(stampid, tuple):
2917                for subitem in stampid:
2918                    self.screen._delete(subitem)
2919            else:
2920                self.screen._delete(stampid)
2921            self.stampItems.remove(stampid)
2922        # Delete stampitem from undobuffer if necessary
2923        # if clearstamp is called directly.
2924        item = ("stamp", stampid)
2925        buf = self.undobuffer
2926        if item not in buf.buffer:
2927            return
2928        index = buf.buffer.index(item)
2929        buf.buffer.remove(item)
2930        if index <= buf.ptr:
2931            buf.ptr = (buf.ptr - 1) % buf.bufsize
2932        buf.buffer.insert((buf.ptr+1)%buf.bufsize, [None])
2933
2934    def clearstamp(self, stampid):
2935        """Delete stamp with given stampid
2936
2937        Argument:
2938        stampid - an integer, must be return value of previous stamp() call.
2939
2940        Example (for a Turtle instance named turtle):
2941        >>> turtle.color("blue")
2942        >>> astamp = turtle.stamp()
2943        >>> turtle.fd(50)
2944        >>> turtle.clearstamp(astamp)
2945        """
2946        self._clearstamp(stampid)
2947        self._update()
2948
2949    def clearstamps(self, n=None):
2950        """Delete all or first/last n of turtle's stamps.
2951
2952        Optional argument:
2953        n -- an integer
2954
2955        If n is None, delete all of pen's stamps,
2956        else if n > 0 delete first n stamps
2957        else if n < 0 delete last n stamps.
2958
2959        Example (for a Turtle instance named turtle):
2960        >>> for i in range(8):
2961        ...     turtle.stamp(); turtle.fd(30)
2962        ...
2963        >>> turtle.clearstamps(2)
2964        >>> turtle.clearstamps(-2)
2965        >>> turtle.clearstamps()
2966        """
2967        if n is None:
2968            toDelete = self.stampItems[:]
2969        elif n >= 0:
2970            toDelete = self.stampItems[:n]
2971        else:
2972            toDelete = self.stampItems[n:]
2973        for item in toDelete:
2974            self._clearstamp(item)
2975        self._update()
2976
2977    def _goto(self, end):
2978        """Move the pen to the point end, thereby drawing a line
2979        if pen is down. All other methods for turtle movement depend
2980        on this one.
2981        """
2982        ## Version mit undo-stuff
2983        go_modes = ( self._drawing,
2984                     self._pencolor,
2985                     self._pensize,
2986                     isinstance(self._fillpath, list))
2987        screen = self.screen
2988        undo_entry = ("go", self._position, end, go_modes,
2989                      (self.currentLineItem,
2990                      self.currentLine[:],
2991                      screen._pointlist(self.currentLineItem),
2992                      self.items[:])
2993                      )
2994        if self.undobuffer:
2995            self.undobuffer.push(undo_entry)
2996        start = self._position
2997        if self._speed and screen._tracing == 1:
2998            diff = (end-start)
2999            diffsq = (diff[0]*screen.xscale)**2 + (diff[1]*screen.yscale)**2
3000            nhops = 1+int((diffsq**0.5)/(3*(1.1**self._speed)*self._speed))
3001            delta = diff * (1.0/nhops)
3002            for n in range(1, nhops):
3003                if n == 1:
3004                    top = True
3005                else:
3006                    top = False
3007                self._position = start + delta * n
3008                if self._drawing:
3009                    screen._drawline(self.drawingLineItem,
3010                                     (start, self._position),
3011                                     self._pencolor, self._pensize, top)
3012                self._update()
3013            if self._drawing:
3014                screen._drawline(self.drawingLineItem, ((0, 0), (0, 0)),
3015                                               fill="", width=self._pensize)
3016        # Turtle now at end,
3017        if self._drawing: # now update currentLine
3018            self.currentLine.append(end)
3019        if isinstance(self._fillpath, list):
3020            self._fillpath.append(end)
3021        ######    vererbung!!!!!!!!!!!!!!!!!!!!!!
3022        self._position = end
3023        if self._creatingPoly:
3024            self._poly.append(end)
3025        if len(self.currentLine) > 42: # 42! answer to the ultimate question
3026                                       # of life, the universe and everything
3027            self._newLine()
3028        self._update() #count=True)
3029
3030    def _undogoto(self, entry):
3031        """Reverse a _goto. Used for undo()
3032        """
3033        old, new, go_modes, coodata = entry
3034        drawing, pc, ps, filling = go_modes
3035        cLI, cL, pl, items = coodata
3036        screen = self.screen
3037        if abs(self._position - new) > 0.5:
3038            print "undogoto: HALLO-DA-STIMMT-WAS-NICHT!"
3039        # restore former situation
3040        self.currentLineItem = cLI
3041        self.currentLine = cL
3042
3043        if pl == [(0, 0), (0, 0)]:
3044            usepc = ""
3045        else:
3046            usepc = pc
3047        screen._drawline(cLI, pl, fill=usepc, width=ps)
3048
3049        todelete = [i for i in self.items if (i not in items) and
3050                                       (screen._type(i) == "line")]
3051        for i in todelete:
3052            screen._delete(i)
3053            self.items.remove(i)
3054
3055        start = old
3056        if self._speed and screen._tracing == 1:
3057            diff = old - new
3058            diffsq = (diff[0]*screen.xscale)**2 + (diff[1]*screen.yscale)**2
3059            nhops = 1+int((diffsq**0.5)/(3*(1.1**self._speed)*self._speed))
3060            delta = diff * (1.0/nhops)
3061            for n in range(1, nhops):
3062                if n == 1:
3063                    top = True
3064                else:
3065                    top = False
3066                self._position = new + delta * n
3067                if drawing:
3068                    screen._drawline(self.drawingLineItem,
3069                                     (start, self._position),
3070                                     pc, ps, top)
3071                self._update()
3072            if drawing:
3073                screen._drawline(self.drawingLineItem, ((0, 0), (0, 0)),
3074                                               fill="", width=ps)
3075        # Turtle now at position old,
3076        self._position = old
3077        ##  if undo is done during creating a polygon, the last vertex
3078        ##  will be deleted. if the polygon is entirely deleted,
3079        ##  creatingPoly will be set to False.
3080        ##  Polygons created before the last one will not be affected by undo()
3081        if self._creatingPoly:
3082            if len(self._poly) > 0:
3083                self._poly.pop()
3084            if self._poly == []:
3085                self._creatingPoly = False
3086                self._poly = None
3087        if filling:
3088            if self._fillpath == []:
3089                self._fillpath = None
3090                print "Unwahrscheinlich in _undogoto!"
3091            elif self._fillpath is not None:
3092                self._fillpath.pop()
3093        self._update() #count=True)
3094
3095    def _rotate(self, angle):
3096        """Turns pen clockwise by angle.
3097        """
3098        if self.undobuffer:
3099            self.undobuffer.push(("rot", angle, self._degreesPerAU))
3100        angle *= self._degreesPerAU
3101        neworient = self._orient.rotate(angle)
3102        tracing = self.screen._tracing
3103        if tracing == 1 and self._speed > 0:
3104            anglevel = 3.0 * self._speed
3105            steps = 1 + int(abs(angle)/anglevel)
3106            delta = 1.0*angle/steps
3107            for _ in range(steps):
3108                self._orient = self._orient.rotate(delta)
3109                self._update()
3110        self._orient = neworient
3111        self._update()
3112
3113    def _newLine(self, usePos=True):
3114        """Closes current line item and starts a new one.
3115           Remark: if current line became too long, animation
3116           performance (via _drawline) slowed down considerably.
3117        """
3118        if len(self.currentLine) > 1:
3119            self.screen._drawline(self.currentLineItem, self.currentLine,
3120                                      self._pencolor, self._pensize)
3121            self.currentLineItem = self.screen._createline()
3122            self.items.append(self.currentLineItem)
3123        else:
3124            self.screen._drawline(self.currentLineItem, top=True)
3125        self.currentLine = []
3126        if usePos:
3127            self.currentLine = [self._position]
3128
3129    def fill(self, flag=None):
3130        """Call fill(True) before drawing a shape to fill, fill(False) when done.
3131
3132        Optional argument:
3133        flag -- True/False (or 1/0 respectively)
3134
3135        Call fill(True) before drawing the shape you want to fill,
3136        and  fill(False) when done.
3137        When used without argument: return fillstate (True if filling,
3138        False else)
3139
3140        Example (for a Turtle instance named turtle):
3141        >>> turtle.fill(True)
3142        >>> turtle.forward(100)
3143        >>> turtle.left(90)
3144        >>> turtle.forward(100)
3145        >>> turtle.left(90)
3146        >>> turtle.forward(100)
3147        >>> turtle.left(90)
3148        >>> turtle.forward(100)
3149        >>> turtle.fill(False)
3150        """
3151        filling = isinstance(self._fillpath, list)
3152        if flag is None:
3153            return filling
3154        screen = self.screen
3155        entry1 = entry2 = ()
3156        if filling:
3157            if len(self._fillpath) > 2:
3158                self.screen._drawpoly(self._fillitem, self._fillpath,
3159                                      fill=self._fillcolor)
3160                entry1 = ("dofill", self._fillitem)
3161        if flag:
3162            self._fillitem = self.screen._createpoly()
3163            self.items.append(self._fillitem)
3164            self._fillpath = [self._position]
3165            entry2 = ("beginfill", self._fillitem) # , self._fillpath)
3166            self._newLine()
3167        else:
3168            self._fillitem = self._fillpath = None
3169        if self.undobuffer:
3170            if entry1 == ():
3171                if entry2 != ():
3172                    self.undobuffer.push(entry2)
3173            else:
3174                if entry2 == ():
3175                    self.undobuffer.push(entry1)
3176                else:
3177                    self.undobuffer.push(["seq", entry1, entry2])
3178        self._update()
3179
3180    def begin_fill(self):
3181        """Called just before drawing a shape to be filled.
3182
3183        No argument.
3184
3185        Example (for a Turtle instance named turtle):
3186        >>> turtle.begin_fill()
3187        >>> turtle.forward(100)
3188        >>> turtle.left(90)
3189        >>> turtle.forward(100)
3190        >>> turtle.left(90)
3191        >>> turtle.forward(100)
3192        >>> turtle.left(90)
3193        >>> turtle.forward(100)
3194        >>> turtle.end_fill()
3195        """
3196        self.fill(True)
3197
3198    def end_fill(self):
3199        """Fill the shape drawn after the call begin_fill().
3200
3201        No argument.
3202
3203        Example (for a Turtle instance named turtle):
3204        >>> turtle.begin_fill()
3205        >>> turtle.forward(100)
3206        >>> turtle.left(90)
3207        >>> turtle.forward(100)
3208        >>> turtle.left(90)
3209        >>> turtle.forward(100)
3210        >>> turtle.left(90)
3211        >>> turtle.forward(100)
3212        >>> turtle.end_fill()
3213        """
3214        self.fill(False)
3215
3216    def dot(self, size=None, *color):
3217        """Draw a dot with diameter size, using color.
3218
3219        Optional arguments:
3220        size -- an integer >= 1 (if given)
3221        color -- a colorstring or a numeric color tuple
3222
3223        Draw a circular dot with diameter size, using color.
3224        If size is not given, the maximum of pensize+4 and 2*pensize is used.
3225
3226        Example (for a Turtle instance named turtle):
3227        >>> turtle.dot()
3228        >>> turtle.fd(50); turtle.dot(20, "blue"); turtle.fd(50)
3229        """
3230        #print "dot-1:", size, color
3231        if not color:
3232            if isinstance(size, (basestring, tuple)):
3233                color = self._colorstr(size)
3234                size = self._pensize + max(self._pensize, 4)
3235            else:
3236                color = self._pencolor
3237                if not size:
3238                    size = self._pensize + max(self._pensize, 4)
3239        else:
3240            if size is None:
3241                size = self._pensize + max(self._pensize, 4)
3242            color = self._colorstr(color)
3243        #print "dot-2:", size, color
3244        if hasattr(self.screen, "_dot"):
3245            item = self.screen._dot(self._position, size, color)
3246            #print "dot:", size, color, "item:", item
3247            self.items.append(item)
3248            if self.undobuffer:
3249                self.undobuffer.push(("dot", item))
3250        else:
3251            pen = self.pen()
3252            if self.undobuffer:
3253                self.undobuffer.push(["seq"])
3254                self.undobuffer.cumulate = True
3255            try:
3256                if self.resizemode() == 'auto':
3257                    self.ht()
3258                self.pendown()
3259                self.pensize(size)
3260                self.pencolor(color)
3261                self.forward(0)
3262            finally:
3263                self.pen(pen)
3264            if self.undobuffer:
3265                self.undobuffer.cumulate = False
3266
3267    def _write(self, txt, align, font):
3268        """Performs the writing for write()
3269        """
3270        item, end = self.screen._write(self._position, txt, align, font,
3271                                                          self._pencolor)
3272        self.items.append(item)
3273        if self.undobuffer:
3274            self.undobuffer.push(("wri", item))
3275        return end
3276
3277    def write(self, arg, move=False, align="left", font=("Arial", 8, "normal")):
3278        """Write text at the current turtle position.
3279
3280        Arguments:
3281        arg -- info, which is to be written to the TurtleScreen
3282        move (optional) -- True/False
3283        align (optional) -- one of the strings "left", "center" or right"
3284        font (optional) -- a triple (fontname, fontsize, fonttype)
3285
3286        Write text - the string representation of arg - at the current
3287        turtle position according to align ("left", "center" or right")
3288        and with the given font.
3289        If move is True, the pen is moved to the bottom-right corner
3290        of the text. By default, move is False.
3291
3292        Example (for a Turtle instance named turtle):
3293        >>> turtle.write('Home = ', True, align="center")
3294        >>> turtle.write((0,0), True)
3295        """
3296        if self.undobuffer:
3297            self.undobuffer.push(["seq"])
3298            self.undobuffer.cumulate = True
3299        end = self._write(str(arg), align.lower(), font)
3300        if move:
3301            x, y = self.pos()
3302            self.setpos(end, y)
3303        if self.undobuffer:
3304            self.undobuffer.cumulate = False
3305
3306    def begin_poly(self):
3307        """Start recording the vertices of a polygon.
3308
3309        No argument.
3310
3311        Start recording the vertices of a polygon. Current turtle position
3312        is first point of polygon.
3313
3314        Example (for a Turtle instance named turtle):
3315        >>> turtle.begin_poly()
3316        """
3317        self._poly = [self._position]
3318        self._creatingPoly = True
3319
3320    def end_poly(self):
3321        """Stop recording the vertices of a polygon.
3322
3323        No argument.
3324
3325        Stop recording the vertices of a polygon. Current turtle position is
3326        last point of polygon. This will be connected with the first point.
3327
3328        Example (for a Turtle instance named turtle):
3329        >>> turtle.end_poly()
3330        """
3331        self._creatingPoly = False
3332
3333    def get_poly(self):
3334        """Return the lastly recorded polygon.
3335
3336        No argument.
3337
3338        Example (for a Turtle instance named turtle):
3339        >>> p = turtle.get_poly()
3340        >>> turtle.register_shape("myFavouriteShape", p)
3341        """
3342        ## check if there is any poly?  -- 1st solution:
3343        if self._poly is not None:
3344            return tuple(self._poly)
3345
3346    def getscreen(self):
3347        """Return the TurtleScreen object, the turtle is drawing  on.
3348
3349        No argument.
3350
3351        Return the TurtleScreen object, the turtle is drawing  on.
3352        So TurtleScreen-methods can be called for that object.
3353
3354        Example (for a Turtle instance named turtle):
3355        >>> ts = turtle.getscreen()
3356        >>> ts
3357        <turtle.TurtleScreen object at 0x0106B770>
3358        >>> ts.bgcolor("pink")
3359        """
3360        return self.screen
3361
3362    def getturtle(self):
3363        """Return the Turtleobject itself.
3364
3365        No argument.
3366
3367        Only reasonable use: as a function to return the 'anonymous turtle':
3368
3369        Example:
3370        >>> pet = getturtle()
3371        >>> pet.fd(50)
3372        >>> pet
3373        <turtle.Turtle object at 0x0187D810>
3374        >>> turtles()
3375        [<turtle.Turtle object at 0x0187D810>]
3376        """
3377        return self
3378
3379    getpen = getturtle
3380
3381
3382    ################################################################
3383    ### screen oriented methods recurring to methods of TurtleScreen
3384    ################################################################
3385
3386    def window_width(self):
3387        """ Returns the width of the turtle window.
3388
3389        No argument.
3390
3391        Example (for a TurtleScreen instance named screen):
3392        >>> screen.window_width()
3393        640
3394        """
3395        return self.screen._window_size()[0]
3396
3397    def window_height(self):
3398        """ Return the height of the turtle window.
3399
3400        No argument.
3401
3402        Example (for a TurtleScreen instance named screen):
3403        >>> screen.window_height()
3404        480
3405        """
3406        return self.screen._window_size()[1]
3407
3408    def _delay(self, delay=None):
3409        """Set delay value which determines speed of turtle animation.
3410        """
3411        return self.screen.delay(delay)
3412
3413    #####   event binding methods   #####
3414
3415    def onclick(self, fun, btn=1, add=None):
3416        """Bind fun to mouse-click event on this turtle on canvas.
3417
3418        Arguments:
3419        fun --  a function with two arguments, to which will be assigned
3420                the coordinates of the clicked point on the canvas.
3421        btn --  number of the mouse-button defaults to 1 (left mouse button).
3422        add --  True or False. If True, new binding will be added, otherwise
3423                it will replace a former binding.
3424
3425        Example for the anonymous turtle, i. e. the procedural way:
3426
3427        >>> def turn(x, y):
3428        ...     left(360)
3429        ...
3430        >>> onclick(turn)  # Now clicking into the turtle will turn it.
3431        >>> onclick(None)  # event-binding will be removed
3432        """
3433        self.screen._onclick(self.turtle._item, fun, btn, add)
3434        self._update()
3435
3436    def onrelease(self, fun, btn=1, add=None):
3437        """Bind fun to mouse-button-release event on this turtle on canvas.
3438
3439        Arguments:
3440        fun -- a function with two arguments, to which will be assigned
3441                the coordinates of the clicked point on the canvas.
3442        btn --  number of the mouse-button defaults to 1 (left mouse button).
3443
3444        Example (for a MyTurtle instance named joe):
3445        >>> class MyTurtle(Turtle):
3446        ...     def glow(self,x,y):
3447        ...             self.fillcolor("red")
3448        ...     def unglow(self,x,y):
3449        ...             self.fillcolor("")
3450        ...
3451        >>> joe = MyTurtle()
3452        >>> joe.onclick(joe.glow)
3453        >>> joe.onrelease(joe.unglow)
3454
3455        Clicking on joe turns fillcolor red, unclicking turns it to
3456        transparent.
3457        """
3458        self.screen._onrelease(self.turtle._item, fun, btn, add)
3459        self._update()
3460
3461    def ondrag(self, fun, btn=1, add=None):
3462        """Bind fun to mouse-move event on this turtle on canvas.
3463
3464        Arguments:
3465        fun -- a function with two arguments, to which will be assigned
3466               the coordinates of the clicked point on the canvas.
3467        btn -- number of the mouse-button defaults to 1 (left mouse button).
3468
3469        Every sequence of mouse-move-events on a turtle is preceded by a
3470        mouse-click event on that turtle.
3471
3472        Example (for a Turtle instance named turtle):
3473        >>> turtle.ondrag(turtle.goto)
3474
3475        Subsequently clicking and dragging a Turtle will move it
3476        across the screen thereby producing handdrawings (if pen is
3477        down).
3478        """
3479        self.screen._ondrag(self.turtle._item, fun, btn, add)
3480
3481
3482    def _undo(self, action, data):
3483        """Does the main part of the work for undo()
3484        """
3485        if self.undobuffer is None:
3486            return
3487        if action == "rot":
3488            angle, degPAU = data
3489            self._rotate(-angle*degPAU/self._degreesPerAU)
3490            dummy = self.undobuffer.pop()
3491        elif action == "stamp":
3492            stitem = data[0]
3493            self.clearstamp(stitem)
3494        elif action == "go":
3495            self._undogoto(data)
3496        elif action in ["wri", "dot"]:
3497            item = data[0]
3498            self.screen._delete(item)
3499            self.items.remove(item)
3500        elif action == "dofill":
3501            item = data[0]
3502            self.screen._drawpoly(item, ((0, 0),(0, 0),(0, 0)),
3503                                  fill="", outline="")
3504        elif action == "beginfill":
3505            item = data[0]
3506            self._fillitem = self._fillpath = None
3507            self.screen._delete(item)
3508            self.items.remove(item)
3509        elif action == "pen":
3510            TPen.pen(self, data[0])
3511            self.undobuffer.pop()
3512
3513    def undo(self):
3514        """undo (repeatedly) the last turtle action.
3515
3516        No argument.
3517
3518        undo (repeatedly) the last turtle action.
3519        Number of available undo actions is determined by the size of
3520        the undobuffer.
3521
3522        Example (for a Turtle instance named turtle):
3523        >>> for i in range(4):
3524        ...     turtle.fd(50); turtle.lt(80)
3525        ...
3526        >>> for i in range(8):
3527        ...     turtle.undo()
3528        ...
3529        """
3530        if self.undobuffer is None:
3531            return
3532        item = self.undobuffer.pop()
3533        action = item[0]
3534        data = item[1:]
3535        if action == "seq":
3536            while data:
3537                item = data.pop()
3538                self._undo(item[0], item[1:])
3539        else:
3540            self._undo(action, data)
3541
3542    turtlesize = shapesize
3543
3544RawPen = RawTurtle
3545
3546###  Screen - Singleton  ########################
3547
3548def Screen():
3549    """Return the singleton screen object.
3550    If none exists at the moment, create a new one and return it,
3551    else return the existing one."""
3552    if Turtle._screen is None:
3553        Turtle._screen = _Screen()
3554    return Turtle._screen
3555
3556class _Screen(TurtleScreen):
3557
3558    _root = None
3559    _canvas = None
3560    _title = _CFG["title"]
3561
3562    def __init__(self):
3563        # XXX there is no need for this code to be conditional,
3564        # as there will be only a single _Screen instance, anyway
3565        # XXX actually, the turtle demo is injecting root window,
3566        # so perhaps the conditional creation of a root should be
3567        # preserved (perhaps by passing it as an optional parameter)
3568        if _Screen._root is None:
3569            _Screen._root = self._root = _Root()
3570            self._root.title(_Screen._title)
3571            self._root.ondestroy(self._destroy)
3572        if _Screen._canvas is None:
3573            width = _CFG["width"]
3574            height = _CFG["height"]
3575            canvwidth = _CFG["canvwidth"]
3576            canvheight = _CFG["canvheight"]
3577            leftright = _CFG["leftright"]
3578            topbottom = _CFG["topbottom"]
3579            self._root.setupcanvas(width, height, canvwidth, canvheight)
3580            _Screen._canvas = self._root._getcanvas()
3581            TurtleScreen.__init__(self, _Screen._canvas)
3582            self.setup(width, height, leftright, topbottom)
3583
3584    def setup(self, width=_CFG["width"], height=_CFG["height"],
3585              startx=_CFG["leftright"], starty=_CFG["topbottom"]):
3586        """ Set the size and position of the main window.
3587
3588        Arguments:
3589        width: as integer a size in pixels, as float a fraction of the screen.
3590          Default is 50% of screen.
3591        height: as integer the height in pixels, as float a fraction of the
3592          screen. Default is 75% of screen.
3593        startx: if positive, starting position in pixels from the left
3594          edge of the screen, if negative from the right edge
3595          Default, startx=None is to center window horizontally.
3596        starty: if positive, starting position in pixels from the top
3597          edge of the screen, if negative from the bottom edge
3598          Default, starty=None is to center window vertically.
3599
3600        Examples (for a Screen instance named screen):
3601        >>> screen.setup (width=200, height=200, startx=0, starty=0)
3602
3603        sets window to 200x200 pixels, in upper left of screen
3604
3605        >>> screen.setup(width=.75, height=0.5, startx=None, starty=None)
3606
3607        sets window to 75% of screen by 50% of screen and centers
3608        """
3609        if not hasattr(self._root, "set_geometry"):
3610            return
3611        sw = self._root.win_width()
3612        sh = self._root.win_height()
3613        if isinstance(width, float) and 0 <= width <= 1:
3614            width = sw*width
3615        if startx is None:
3616            startx = (sw - width) / 2
3617        if isinstance(height, float) and 0 <= height <= 1:
3618            height = sh*height
3619        if starty is None:
3620            starty = (sh - height) / 2
3621        self._root.set_geometry(width, height, startx, starty)
3622        self.update()
3623
3624    def title(self, titlestring):
3625        """Set title of turtle-window
3626
3627        Argument:
3628        titlestring -- a string, to appear in the titlebar of the
3629                       turtle graphics window.
3630
3631        This is a method of Screen-class. Not available for TurtleScreen-
3632        objects.
3633
3634        Example (for a Screen instance named screen):
3635        >>> screen.title("Welcome to the turtle-zoo!")
3636        """
3637        if _Screen._root is not None:
3638            _Screen._root.title(titlestring)
3639        _Screen._title = titlestring
3640
3641    def _destroy(self):
3642        root = self._root
3643        if root is _Screen._root:
3644            Turtle._pen = None
3645            Turtle._screen = None
3646            _Screen._root = None
3647            _Screen._canvas = None
3648        TurtleScreen._RUNNING = False
3649        root.destroy()
3650
3651    def bye(self):
3652        """Shut the turtlegraphics window.
3653
3654        Example (for a TurtleScreen instance named screen):
3655        >>> screen.bye()
3656        """
3657        self._destroy()
3658
3659    def exitonclick(self):
3660        """Go into mainloop until the mouse is clicked.
3661
3662        No arguments.
3663
3664        Bind bye() method to mouseclick on TurtleScreen.
3665        If "using_IDLE" - value in configuration dictionary is False
3666        (default value), enter mainloop.
3667        If IDLE with -n switch (no subprocess) is used, this value should be
3668        set to True in turtle.cfg. In this case IDLE's mainloop
3669        is active also for the client script.
3670
3671        This is a method of the Screen-class and not available for
3672        TurtleScreen instances.
3673
3674        Example (for a Screen instance named screen):
3675        >>> screen.exitonclick()
3676
3677        """
3678        def exitGracefully(x, y):
3679            """Screen.bye() with two dummy-parameters"""
3680            self.bye()
3681        self.onclick(exitGracefully)
3682        if _CFG["using_IDLE"]:
3683            return
3684        try:
3685            mainloop()
3686        except AttributeError:
3687            exit(0)
3688
3689class Turtle(RawTurtle):
3690    """RawTurtle auto-creating (scrolled) canvas.
3691
3692    When a Turtle object is created or a function derived from some
3693    Turtle method is called a TurtleScreen object is automatically created.
3694    """
3695    _pen = None
3696    _screen = None
3697
3698    def __init__(self,
3699                 shape=_CFG["shape"],
3700                 undobuffersize=_CFG["undobuffersize"],
3701                 visible=_CFG["visible"]):
3702        if Turtle._screen is None:
3703            Turtle._screen = Screen()
3704        RawTurtle.__init__(self, Turtle._screen,
3705                           shape=shape,
3706                           undobuffersize=undobuffersize,
3707                           visible=visible)
3708
3709Pen = Turtle
3710
3711def write_docstringdict(filename="turtle_docstringdict"):
3712    """Create and write docstring-dictionary to file.
3713
3714    Optional argument:
3715    filename -- a string, used as filename
3716                default value is turtle_docstringdict
3717
3718    Has to be called explicitly, (not used by the turtle-graphics classes)
3719    The docstring dictionary will be written to the Python script <filname>.py
3720    It is intended to serve as a template for translation of the docstrings
3721    into different languages.
3722    """
3723    docsdict = {}
3724
3725    for methodname in _tg_screen_functions:
3726        key = "_Screen."+methodname
3727        docsdict[key] = eval(key).__doc__
3728    for methodname in _tg_turtle_functions:
3729        key = "Turtle."+methodname
3730        docsdict[key] = eval(key).__doc__
3731
3732    f = open("%s.py" % filename,"w")
3733    keys = sorted([x for x in docsdict.keys()
3734                        if x.split('.')[1] not in _alias_list])
3735    f.write('docsdict = {\n\n')
3736    for key in keys[:-1]:
3737        f.write('%s :\n' % repr(key))
3738        f.write('        """%s\n""",\n\n' % docsdict[key])
3739    key = keys[-1]
3740    f.write('%s :\n' % repr(key))
3741    f.write('        """%s\n"""\n\n' % docsdict[key])
3742    f.write("}\n")
3743    f.close()
3744
3745def read_docstrings(lang):
3746    """Read in docstrings from lang-specific docstring dictionary.
3747
3748    Transfer docstrings, translated to lang, from a dictionary-file
3749    to the methods of classes Screen and Turtle and - in revised form -
3750    to the corresponding functions.
3751    """
3752    modname = "turtle_docstringdict_%(language)s" % {'language':lang.lower()}
3753    module = __import__(modname)
3754    docsdict = module.docsdict
3755    for key in docsdict:
3756        #print key
3757        try:
3758            eval(key).im_func.__doc__ = docsdict[key]
3759        except BaseException:
3760            print "Bad docstring-entry: %s" % key
3761
3762_LANGUAGE = _CFG["language"]
3763
3764try:
3765    if _LANGUAGE != "english":
3766        read_docstrings(_LANGUAGE)
3767except ImportError:
3768    print "Cannot find docsdict for", _LANGUAGE
3769except BaseException:
3770    print ("Unknown Error when trying to import %s-docstring-dictionary" %
3771                                                                  _LANGUAGE)
3772
3773
3774def getmethparlist(ob):
3775    "Get strings describing the arguments for the given object"
3776    argText1 = argText2 = ""
3777    # bit of a hack for methods - turn it into a function
3778    # but we drop the "self" param.
3779    if type(ob)==types.MethodType:
3780        fob = ob.im_func
3781        argOffset = 1
3782    else:
3783        fob = ob
3784        argOffset = 0
3785    # Try and build one for Python defined functions
3786    if type(fob) in [types.FunctionType, types.LambdaType]:
3787        try:
3788            counter = fob.func_code.co_argcount
3789            items2 = list(fob.func_code.co_varnames[argOffset:counter])
3790            realArgs = fob.func_code.co_varnames[argOffset:counter]
3791            defaults = fob.func_defaults or []
3792            defaults = list(map(lambda name: "=%s" % repr(name), defaults))
3793            defaults = [""] * (len(realArgs)-len(defaults)) + defaults
3794            items1 = map(lambda arg, dflt: arg+dflt, realArgs, defaults)
3795            if fob.func_code.co_flags & 0x4:
3796                items1.append("*"+fob.func_code.co_varnames[counter])
3797                items2.append("*"+fob.func_code.co_varnames[counter])
3798                counter += 1
3799            if fob.func_code.co_flags & 0x8:
3800                items1.append("**"+fob.func_code.co_varnames[counter])
3801                items2.append("**"+fob.func_code.co_varnames[counter])
3802            argText1 = ", ".join(items1)
3803            argText1 = "(%s)" % argText1
3804            argText2 = ", ".join(items2)
3805            argText2 = "(%s)" % argText2
3806        except:
3807            pass
3808    return argText1, argText2
3809
3810def _turtle_docrevise(docstr):
3811    """To reduce docstrings from RawTurtle class for functions
3812    """
3813    import re
3814    if docstr is None:
3815        return None
3816    turtlename = _CFG["exampleturtle"]
3817    newdocstr = docstr.replace("%s." % turtlename,"")
3818    parexp = re.compile(r' \(.+ %s\):' % turtlename)
3819    newdocstr = parexp.sub(":", newdocstr)
3820    return newdocstr
3821
3822def _screen_docrevise(docstr):
3823    """To reduce docstrings from TurtleScreen class for functions
3824    """
3825    import re
3826    if docstr is None:
3827        return None
3828    screenname = _CFG["examplescreen"]
3829    newdocstr = docstr.replace("%s." % screenname,"")
3830    parexp = re.compile(r' \(.+ %s\):' % screenname)
3831    newdocstr = parexp.sub(":", newdocstr)
3832    return newdocstr
3833
3834## The following mechanism makes all methods of RawTurtle and Turtle available
3835## as functions. So we can enhance, change, add, delete methods to these
3836## classes and do not need to change anything here.
3837
3838__func_body = """\
3839def {name}{paramslist}:
3840    if {obj} is None:
3841        if not TurtleScreen._RUNNING:
3842            TurtleScreen._RUNNING = True
3843            raise Terminator
3844        {obj} = {init}
3845    try:
3846        return {obj}.{name}{argslist}
3847    except TK.TclError:
3848        if not TurtleScreen._RUNNING:
3849            TurtleScreen._RUNNING = True
3850            raise Terminator
3851        raise
3852"""
3853
3854def _make_global_funcs(functions, cls, obj, init, docrevise):
3855    for methodname in functions:
3856        method = getattr(cls, methodname)
3857        pl1, pl2 = getmethparlist(method)
3858        if pl1 == "":
3859            print ">>>>>>", pl1, pl2
3860            continue
3861        defstr = __func_body.format(obj=obj, init=init, name=methodname,
3862                                    paramslist=pl1, argslist=pl2)
3863        exec defstr in globals()
3864        globals()[methodname].__doc__ = docrevise(method.__doc__)
3865
3866_make_global_funcs(_tg_screen_functions, _Screen,
3867                   'Turtle._screen', 'Screen()', _screen_docrevise)
3868_make_global_funcs(_tg_turtle_functions, Turtle,
3869                   'Turtle._pen', 'Turtle()', _turtle_docrevise)
3870
3871
3872done = mainloop = TK.mainloop
3873
3874if __name__ == "__main__":
3875    def switchpen():
3876        if isdown():
3877            pu()
3878        else:
3879            pd()
3880
3881    def demo1():
3882        """Demo of old turtle.py - module"""
3883        reset()
3884        tracer(True)
3885        up()
3886        backward(100)
3887        down()
3888        # draw 3 squares; the last filled
3889        width(3)
3890        for i in range(3):
3891            if i == 2:
3892                fill(1)
3893            for _ in range(4):
3894                forward(20)
3895                left(90)
3896            if i == 2:
3897                color("maroon")
3898                fill(0)
3899            up()
3900            forward(30)
3901            down()
3902        width(1)
3903        color("black")
3904        # move out of the way
3905        tracer(False)
3906        up()
3907        right(90)
3908        forward(100)
3909        right(90)
3910        forward(100)
3911        right(180)
3912        down()
3913        # some text
3914        write("startstart", 1)
3915        write(u"start", 1)
3916        color("red")
3917        # staircase
3918        for i in range(5):
3919            forward(20)
3920            left(90)
3921            forward(20)
3922            right(90)
3923        # filled staircase
3924        tracer(True)
3925        fill(1)
3926        for i in range(5):
3927            forward(20)
3928            left(90)
3929            forward(20)
3930            right(90)
3931        fill(0)
3932        # more text
3933
3934    def demo2():
3935        """Demo of some new features."""
3936        speed(1)
3937        st()
3938        pensize(3)
3939        setheading(towards(0, 0))
3940        radius = distance(0, 0)/2.0
3941        rt(90)
3942        for _ in range(18):
3943            switchpen()
3944            circle(radius, 10)
3945        write("wait a moment...")
3946        while undobufferentries():
3947            undo()
3948        reset()
3949        lt(90)
3950        colormode(255)
3951        laenge = 10
3952        pencolor("green")
3953        pensize(3)
3954        lt(180)
3955        for i in range(-2, 16):
3956            if i > 0:
3957                begin_fill()
3958                fillcolor(255-15*i, 0, 15*i)
3959            for _ in range(3):
3960                fd(laenge)
3961                lt(120)
3962            laenge += 10
3963            lt(15)
3964            speed((speed()+1)%12)
3965        end_fill()
3966
3967        lt(120)
3968        pu()
3969        fd(70)
3970        rt(30)
3971        pd()
3972        color("red","yellow")
3973        speed(0)
3974        fill(1)
3975        for _ in range(4):
3976            circle(50, 90)
3977            rt(90)
3978            fd(30)
3979            rt(90)
3980        fill(0)
3981        lt(90)
3982        pu()
3983        fd(30)
3984        pd()
3985        shape("turtle")
3986
3987        tri = getturtle()
3988        tri.resizemode("auto")
3989        turtle = Turtle()
3990        turtle.resizemode(u"auto")
3991        turtle.shape("turtle")
3992        turtle.reset()
3993        turtle.left(90)
3994        turtle.speed(0)
3995        turtle.up()
3996        turtle.goto(280, 40)
3997        turtle.lt(30)
3998        turtle.down()
3999        turtle.speed(6)
4000        turtle.color("blue",u"orange")
4001        turtle.pensize(2)
4002        tri.speed(6)
4003        setheading(towards(turtle))
4004        count = 1
4005        while tri.distance(turtle) > 4:
4006            turtle.fd(3.5)
4007            turtle.lt(0.6)
4008            tri.setheading(tri.towards(turtle))
4009            tri.fd(4)
4010            if count % 20 == 0:
4011                turtle.stamp()
4012                tri.stamp()
4013                switchpen()
4014            count += 1
4015        tri.write("CAUGHT! ", font=("Arial", 16, "bold"), align=u"right")
4016        tri.pencolor("black")
4017        tri.pencolor(u"red")
4018
4019        def baba(xdummy, ydummy):
4020            clearscreen()
4021            bye()
4022
4023        time.sleep(2)
4024
4025        while undobufferentries():
4026            tri.undo()
4027            turtle.undo()
4028        tri.fd(50)
4029        tri.write("  Click me!", font = ("Courier", 12, "bold") )
4030        tri.onclick(baba, 1)
4031
4032    demo1()
4033    demo2()
4034    exitonclick()
4035