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