@@ -896,6 +896,15 @@ def pop(self):
896896 self .ptr = (self .ptr - 1 ) % self .bufsize
897897 return (item )
898898
899+ def remove (self , item ):
900+ if item not in self .buffer :
901+ return
902+ index = self .buffer .index (item )
903+ self .buffer .remove (item )
904+ if index <= self .ptr :
905+ self .ptr = (self .ptr - 1 ) % self .bufsize
906+ self .buffer .insert ((self .ptr + 1 ) % self .bufsize , [None ])
907+
899908 def nr_of_items (self ):
900909 return self .bufsize - self .buffer .count ([None ])
901910
@@ -1639,6 +1648,20 @@ def _goto(self, end):
16391648 """Move the turtle to the end position."""
16401649 self ._position = end
16411650
1651+ @contextmanager
1652+ def _undo_sequence (self ):
1653+ """Record the enclosed actions as a single undo step."""
1654+ undobuffer = self .undobuffer
1655+ if not undobuffer or undobuffer .cumulate :
1656+ yield
1657+ return
1658+ undobuffer .push (["seq" ])
1659+ undobuffer .cumulate = True
1660+ try :
1661+ yield
1662+ finally :
1663+ undobuffer .cumulate = False
1664+
16421665 def teleport (self , x = None , y = None , * , fill_gap : bool = False ) -> None :
16431666 """To be overwritten by child class RawTurtle.
16441667 Includes no TPen references."""
@@ -1981,38 +2004,34 @@ def circle(self, radius, extent = None, steps = None):
19812004 >>> turtle.circle(50)
19822005 >>> turtle.circle(120, 180) # draw a semicircle
19832006 """
1984- if self .undobuffer :
1985- self .undobuffer .push (["seq" ])
1986- self .undobuffer .cumulate = True
19872007 speed = self .speed ()
19882008 if extent is None :
19892009 extent = self ._fullcircle
19902010 if steps is None :
19912011 frac = abs (extent )/ self ._fullcircle
1992- steps = 1 + int (min (11 + abs (radius )/ 6.0 , 59.0 )* frac )
2012+ steps = 1 + int (min (11 + abs (radius ) / 6.0 , 59.0 ) * frac )
19932013 w = 1.0 * extent / steps
19942014 w2 = 0.5 * w
19952015 l = 2.0 * radius * math .sin (math .radians (w2 )* self ._degreesPerAU )
19962016 if radius < 0 :
19972017 l , w , w2 = - l , - w , - w2
19982018 tr = self ._tracer ()
19992019 dl = self ._delay ()
2000- if speed == 0 :
2001- self ._tracer (0 , 0 )
2002- else :
2003- self .speed (0 )
2004- self ._rotate (w2 )
2005- for i in range (steps ):
2020+ with self ._undo_sequence ():
2021+ if speed == 0 :
2022+ self ._tracer (0 , 0 )
2023+ else :
2024+ self .speed (0 )
2025+ self ._rotate (w2 )
2026+ for i in range (steps ):
2027+ self .speed (speed )
2028+ self ._go (l )
2029+ self .speed (0 )
2030+ self ._rotate (w )
2031+ self ._rotate (- w2 )
2032+ if speed == 0 :
2033+ self ._tracer (tr , dl )
20062034 self .speed (speed )
2007- self ._go (l )
2008- self .speed (0 )
2009- self ._rotate (w )
2010- self ._rotate (- w2 )
2011- if speed == 0 :
2012- self ._tracer (tr , dl )
2013- self .speed (speed )
2014- if self .undobuffer :
2015- self .undobuffer .cumulate = False
20162035
20172036 # Three dummy methods to be implemented by the child class:
20182037
@@ -2783,16 +2802,19 @@ def teleport(self, x=None, y=None, *, fill_gap: bool = False) -> None:
27832802 """
27842803 pendown = self .isdown ()
27852804 was_filling = self .filling ()
2786- if pendown :
2787- self .pen (pendown = False )
2788- if was_filling and not fill_gap :
2789- self .end_fill ()
2790- new_x = x if x is not None else self ._position [0 ]
2791- new_y = y if y is not None else self ._position [1 ]
2792- self ._position = Vec2D (new_x , new_y )
2793- self .pen (pendown = pendown )
2794- if was_filling and not fill_gap :
2795- self .begin_fill ()
2805+ with self ._undo_sequence ():
2806+ if pendown :
2807+ self .pen (pendown = False )
2808+ if was_filling and not fill_gap :
2809+ self .end_fill ()
2810+ new_x = x if x is not None else self ._position [0 ]
2811+ new_y = y if y is not None else self ._position [1 ]
2812+ if self .undobuffer :
2813+ self .undobuffer .push (("teleport" , self ._position ))
2814+ self ._position = Vec2D (new_x , new_y )
2815+ self .pen (pendown = pendown )
2816+ if was_filling and not fill_gap :
2817+ self .begin_fill ()
27962818
27972819 def clone (self ):
27982820 """Create and return a clone of the turtle.
@@ -3143,7 +3165,8 @@ def stamp(self):
31433165 screen ._drawpoly (item , poly , fill = self ._cc (fc ),
31443166 outline = self ._cc (oc ), width = self ._outlinewidth , top = True )
31453167 self .stampItems .append (stitem )
3146- self .undobuffer .push (("stamp" , stitem ))
3168+ if self .undobuffer :
3169+ self .undobuffer .push (("stamp" , stitem ))
31473170 return stitem
31483171
31493172 def _clearstamp (self , stampid ):
@@ -3158,15 +3181,8 @@ def _clearstamp(self, stampid):
31583181 self .stampItems .remove (stampid )
31593182 # Delete stampitem from undobuffer if necessary
31603183 # if clearstamp is called directly.
3161- item = ("stamp" , stampid )
3162- buf = self .undobuffer
3163- if item not in buf .buffer :
3164- return
3165- index = buf .buffer .index (item )
3166- buf .buffer .remove (item )
3167- if index <= buf .ptr :
3168- buf .ptr = (buf .ptr - 1 ) % buf .bufsize
3169- buf .buffer .insert ((buf .ptr + 1 )% buf .bufsize , [None ])
3184+ if self .undobuffer :
3185+ self .undobuffer .remove (("stamp" , stampid ))
31703186
31713187 def clearstamp (self , stampid ):
31723188 """Delete stamp with given stampid
@@ -3464,20 +3480,16 @@ def dot(self, size=None, *color):
34643480 color = self ._colorstr (color )
34653481 # If screen were to gain a dot function, see GH #104218.
34663482 pen = self .pen ()
3467- if self .undobuffer :
3468- self .undobuffer .push (["seq" ])
3469- self .undobuffer .cumulate = True
3470- try :
3471- if self .resizemode () == 'auto' :
3472- self .ht ()
3473- self .pendown ()
3474- self .pensize (size )
3475- self .pencolor (color )
3476- self .forward (0 )
3477- finally :
3478- self .pen (pen )
3479- if self .undobuffer :
3480- self .undobuffer .cumulate = False
3483+ with self ._undo_sequence ():
3484+ try :
3485+ if self .resizemode () == 'auto' :
3486+ self .ht ()
3487+ self .pendown ()
3488+ self .pensize (size )
3489+ self .pencolor (color )
3490+ self .forward (0 )
3491+ finally :
3492+ self .pen (pen )
34813493
34823494 def _write (self , txt , align , font ):
34833495 """Performs the writing for write()
@@ -3509,15 +3521,11 @@ def write(self, arg, move=False, align="left", font=("Arial", 8, "normal")):
35093521 >>> turtle.write('Home = ', True, align="center")
35103522 >>> turtle.write((0,0), True)
35113523 """
3512- if self .undobuffer :
3513- self .undobuffer .push (["seq" ])
3514- self .undobuffer .cumulate = True
3515- end = self ._write (str (arg ), align .lower (), font )
3516- if move :
3517- x , y = self .pos ()
3518- self .setpos (end , y )
3519- if self .undobuffer :
3520- self .undobuffer .cumulate = False
3524+ with self ._undo_sequence ():
3525+ end = self ._write (str (arg ), align .lower (), font )
3526+ if move :
3527+ x , y = self .pos ()
3528+ self .setpos (end , y )
35213529
35223530 @contextmanager
35233531 def poly (self ):
@@ -3705,6 +3713,9 @@ def _undo(self, action, data):
37053713 self .clearstamp (stitem )
37063714 elif action == "go" :
37073715 self ._undogoto (data )
3716+ elif action == "teleport" :
3717+ self ._position = data [0 ]
3718+ self ._update ()
37083719 elif action in ["wri" , "dot" ]:
37093720 item = data [0 ]
37103721 self .screen ._delete (item )
0 commit comments