@@ -897,6 +897,15 @@ def pop(self):
897897 self .ptr = (self .ptr - 1 ) % self .bufsize
898898 return (item )
899899
900+ def remove (self , item ):
901+ if item not in self .buffer :
902+ return
903+ index = self .buffer .index (item )
904+ self .buffer .remove (item )
905+ if index <= self .ptr :
906+ self .ptr = (self .ptr - 1 ) % self .bufsize
907+ self .buffer .insert ((self .ptr + 1 ) % self .bufsize , [None ])
908+
900909 def nr_of_items (self ):
901910 return self .bufsize - self .buffer .count ([None ])
902911
@@ -1643,6 +1652,20 @@ def _goto(self, end):
16431652 """Move the turtle to the end position."""
16441653 self ._position = end
16451654
1655+ @contextmanager
1656+ def _undo_sequence (self ):
1657+ """Record the enclosed actions as a single undo step."""
1658+ undobuffer = self .undobuffer
1659+ if not undobuffer or undobuffer .cumulate :
1660+ yield
1661+ return
1662+ undobuffer .push (["seq" ])
1663+ undobuffer .cumulate = True
1664+ try :
1665+ yield
1666+ finally :
1667+ undobuffer .cumulate = False
1668+
16461669 def teleport (self , x = None , y = None , * , fill_gap : bool = False ) -> None :
16471670 """To be overwritten by child class RawTurtle.
16481671 Includes no TPen references."""
@@ -1985,38 +2008,34 @@ def circle(self, radius, extent = None, steps = None):
19852008 >>> turtle.circle(50)
19862009 >>> turtle.circle(120, 180) # draw a semicircle
19872010 """
1988- if self .undobuffer :
1989- self .undobuffer .push (["seq" ])
1990- self .undobuffer .cumulate = True
19912011 speed = self .speed ()
19922012 if extent is None :
19932013 extent = self ._fullcircle
19942014 if steps is None :
19952015 frac = abs (extent )/ self ._fullcircle
1996- steps = 1 + int (min (11 + abs (radius )/ 6.0 , 59.0 )* frac )
2016+ steps = 1 + int (min (11 + abs (radius ) / 6.0 , 59.0 ) * frac )
19972017 w = 1.0 * extent / steps
19982018 w2 = 0.5 * w
19992019 l = 2.0 * radius * math .sin (math .radians (w2 )* self ._degreesPerAU )
20002020 if radius < 0 :
20012021 l , w , w2 = - l , - w , - w2
20022022 tr = self ._tracer ()
20032023 dl = self ._delay ()
2004- if speed == 0 :
2005- self ._tracer (0 , 0 )
2006- else :
2007- self .speed (0 )
2008- self ._rotate (w2 )
2009- for i in range (steps ):
2024+ with self ._undo_sequence ():
2025+ if speed == 0 :
2026+ self ._tracer (0 , 0 )
2027+ else :
2028+ self .speed (0 )
2029+ self ._rotate (w2 )
2030+ for i in range (steps ):
2031+ self .speed (speed )
2032+ self ._go (l )
2033+ self .speed (0 )
2034+ self ._rotate (w )
2035+ self ._rotate (- w2 )
2036+ if speed == 0 :
2037+ self ._tracer (tr , dl )
20102038 self .speed (speed )
2011- self ._go (l )
2012- self .speed (0 )
2013- self ._rotate (w )
2014- self ._rotate (- w2 )
2015- if speed == 0 :
2016- self ._tracer (tr , dl )
2017- self .speed (speed )
2018- if self .undobuffer :
2019- self .undobuffer .cumulate = False
20202039
20212040 # Three dummy methods to be implemented by the child class:
20222041
@@ -2787,16 +2806,19 @@ def teleport(self, x=None, y=None, *, fill_gap: bool = False) -> None:
27872806 """
27882807 pendown = self .isdown ()
27892808 was_filling = self .filling ()
2790- if pendown :
2791- self .pen (pendown = False )
2792- if was_filling and not fill_gap :
2793- self .end_fill ()
2794- new_x = x if x is not None else self ._position [0 ]
2795- new_y = y if y is not None else self ._position [1 ]
2796- self ._position = Vec2D (new_x , new_y )
2797- self .pen (pendown = pendown )
2798- if was_filling and not fill_gap :
2799- self .begin_fill ()
2809+ with self ._undo_sequence ():
2810+ if pendown :
2811+ self .pen (pendown = False )
2812+ if was_filling and not fill_gap :
2813+ self .end_fill ()
2814+ new_x = x if x is not None else self ._position [0 ]
2815+ new_y = y if y is not None else self ._position [1 ]
2816+ if self .undobuffer :
2817+ self .undobuffer .push (("teleport" , self ._position ))
2818+ self ._position = Vec2D (new_x , new_y )
2819+ self .pen (pendown = pendown )
2820+ if was_filling and not fill_gap :
2821+ self .begin_fill ()
28002822
28012823 def clone (self ):
28022824 """Create and return a clone of the turtle.
@@ -3147,7 +3169,8 @@ def stamp(self):
31473169 screen ._drawpoly (item , poly , fill = self ._cc (fc ),
31483170 outline = self ._cc (oc ), width = self ._outlinewidth , top = True )
31493171 self .stampItems .append (stitem )
3150- self .undobuffer .push (("stamp" , stitem ))
3172+ if self .undobuffer :
3173+ self .undobuffer .push (("stamp" , stitem ))
31513174 return stitem
31523175
31533176 def _clearstamp (self , stampid ):
@@ -3162,15 +3185,8 @@ def _clearstamp(self, stampid):
31623185 self .stampItems .remove (stampid )
31633186 # Delete stampitem from undobuffer if necessary
31643187 # if clearstamp is called directly.
3165- item = ("stamp" , stampid )
3166- buf = self .undobuffer
3167- if item not in buf .buffer :
3168- return
3169- index = buf .buffer .index (item )
3170- buf .buffer .remove (item )
3171- if index <= buf .ptr :
3172- buf .ptr = (buf .ptr - 1 ) % buf .bufsize
3173- buf .buffer .insert ((buf .ptr + 1 )% buf .bufsize , [None ])
3188+ if self .undobuffer :
3189+ self .undobuffer .remove (("stamp" , stampid ))
31743190
31753191 def clearstamp (self , stampid ):
31763192 """Delete stamp with given stampid
@@ -3468,20 +3484,16 @@ def dot(self, size=None, *color):
34683484 color = self ._colorstr (color )
34693485 # If screen were to gain a dot function, see GH #104218.
34703486 pen = self .pen ()
3471- if self .undobuffer :
3472- self .undobuffer .push (["seq" ])
3473- self .undobuffer .cumulate = True
3474- try :
3475- if self .resizemode () == 'auto' :
3476- self .ht ()
3477- self .pendown ()
3478- self .pensize (size )
3479- self .pencolor (color )
3480- self .forward (0 )
3481- finally :
3482- self .pen (pen )
3483- if self .undobuffer :
3484- self .undobuffer .cumulate = False
3487+ with self ._undo_sequence ():
3488+ try :
3489+ if self .resizemode () == 'auto' :
3490+ self .ht ()
3491+ self .pendown ()
3492+ self .pensize (size )
3493+ self .pencolor (color )
3494+ self .forward (0 )
3495+ finally :
3496+ self .pen (pen )
34853497
34863498 def _write (self , txt , align , font ):
34873499 """Performs the writing for write()
@@ -3513,15 +3525,11 @@ def write(self, arg, move=False, align="left", font=("Arial", 8, "normal")):
35133525 >>> turtle.write('Home = ', True, align="center")
35143526 >>> turtle.write((0,0), True)
35153527 """
3516- if self .undobuffer :
3517- self .undobuffer .push (["seq" ])
3518- self .undobuffer .cumulate = True
3519- end = self ._write (str (arg ), align .lower (), font )
3520- if move :
3521- x , y = self .pos ()
3522- self .setpos (end , y )
3523- if self .undobuffer :
3524- self .undobuffer .cumulate = False
3528+ with self ._undo_sequence ():
3529+ end = self ._write (str (arg ), align .lower (), font )
3530+ if move :
3531+ x , y = self .pos ()
3532+ self .setpos (end , y )
35253533
35263534 @contextmanager
35273535 def poly (self ):
@@ -3709,6 +3717,9 @@ def _undo(self, action, data):
37093717 self .clearstamp (stitem )
37103718 elif action == "go" :
37113719 self ._undogoto (data )
3720+ elif action == "teleport" :
3721+ self ._position = data [0 ]
3722+ self ._update ()
37123723 elif action in ["wri" , "dot" ]:
37133724 item = data [0 ]
37143725 self .screen ._delete (item )
0 commit comments