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