@@ -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
@@ -1642,6 +1651,20 @@ def _goto(self, end):
16421651 """Move the turtle to the end position."""
16431652 self ._position = end
16441653
1654+ @contextmanager
1655+ def _undo_sequence (self ):
1656+ """Record the enclosed actions as a single undo step."""
1657+ undobuffer = self .undobuffer
1658+ if not undobuffer or undobuffer .cumulate :
1659+ yield
1660+ return
1661+ undobuffer .push (["seq" ])
1662+ undobuffer .cumulate = True
1663+ try :
1664+ yield
1665+ finally :
1666+ undobuffer .cumulate = False
1667+
16451668 def teleport (self , x = None , y = None , * , fill_gap : bool = False ) -> None :
16461669 """To be overwritten by child class RawTurtle.
16471670 Includes no TPen references."""
@@ -1984,38 +2007,34 @@ def circle(self, radius, extent = None, steps = None):
19842007 >>> turtle.circle(50)
19852008 >>> turtle.circle(120, 180) # draw a semicircle
19862009 """
1987- if self .undobuffer :
1988- self .undobuffer .push (["seq" ])
1989- self .undobuffer .cumulate = True
19902010 speed = self .speed ()
19912011 if extent is None :
19922012 extent = self ._fullcircle
19932013 if steps is None :
19942014 frac = abs (extent )/ self ._fullcircle
1995- steps = 1 + int (min (11 + abs (radius )/ 6.0 , 59.0 )* frac )
2015+ steps = 1 + int (min (11 + abs (radius ) / 6.0 , 59.0 ) * frac )
19962016 w = 1.0 * extent / steps
19972017 w2 = 0.5 * w
19982018 l = 2.0 * radius * math .sin (math .radians (w2 )* self ._degreesPerAU )
19992019 if radius < 0 :
20002020 l , w , w2 = - l , - w , - w2
20012021 tr = self ._tracer ()
20022022 dl = self ._delay ()
2003- if speed == 0 :
2004- self ._tracer (0 , 0 )
2005- else :
2006- self .speed (0 )
2007- self ._rotate (w2 )
2008- for i in range (steps ):
2023+ with self ._undo_sequence ():
2024+ if speed == 0 :
2025+ self ._tracer (0 , 0 )
2026+ else :
2027+ self .speed (0 )
2028+ self ._rotate (w2 )
2029+ for i in range (steps ):
2030+ self .speed (speed )
2031+ self ._go (l )
2032+ self .speed (0 )
2033+ self ._rotate (w )
2034+ self ._rotate (- w2 )
2035+ if speed == 0 :
2036+ self ._tracer (tr , dl )
20092037 self .speed (speed )
2010- self ._go (l )
2011- self .speed (0 )
2012- self ._rotate (w )
2013- self ._rotate (- w2 )
2014- if speed == 0 :
2015- self ._tracer (tr , dl )
2016- self .speed (speed )
2017- if self .undobuffer :
2018- self .undobuffer .cumulate = False
20192038
20202039 # Three dummy methods to be implemented by the child class:
20212040
@@ -2786,16 +2805,19 @@ def teleport(self, x=None, y=None, *, fill_gap: bool = False) -> None:
27862805 """
27872806 pendown = self .isdown ()
27882807 was_filling = self .filling ()
2789- if pendown :
2790- self .pen (pendown = False )
2791- if was_filling and not fill_gap :
2792- self .end_fill ()
2793- new_x = x if x is not None else self ._position [0 ]
2794- new_y = y if y is not None else self ._position [1 ]
2795- self ._position = Vec2D (new_x , new_y )
2796- self .pen (pendown = pendown )
2797- if was_filling and not fill_gap :
2798- self .begin_fill ()
2808+ with self ._undo_sequence ():
2809+ if pendown :
2810+ self .pen (pendown = False )
2811+ if was_filling and not fill_gap :
2812+ self .end_fill ()
2813+ new_x = x if x is not None else self ._position [0 ]
2814+ new_y = y if y is not None else self ._position [1 ]
2815+ if self .undobuffer :
2816+ self .undobuffer .push (("teleport" , self ._position ))
2817+ self ._position = Vec2D (new_x , new_y )
2818+ self .pen (pendown = pendown )
2819+ if was_filling and not fill_gap :
2820+ self .begin_fill ()
27992821
28002822 def clone (self ):
28012823 """Create and return a clone of the turtle.
@@ -3146,7 +3168,8 @@ def stamp(self):
31463168 screen ._drawpoly (item , poly , fill = self ._cc (fc ),
31473169 outline = self ._cc (oc ), width = self ._outlinewidth , top = True )
31483170 self .stampItems .append (stitem )
3149- self .undobuffer .push (("stamp" , stitem ))
3171+ if self .undobuffer :
3172+ self .undobuffer .push (("stamp" , stitem ))
31503173 return stitem
31513174
31523175 def _clearstamp (self , stampid ):
@@ -3161,15 +3184,8 @@ def _clearstamp(self, stampid):
31613184 self .stampItems .remove (stampid )
31623185 # Delete stampitem from undobuffer if necessary
31633186 # if clearstamp is called directly.
3164- item = ("stamp" , stampid )
3165- buf = self .undobuffer
3166- if item not in buf .buffer :
3167- return
3168- index = buf .buffer .index (item )
3169- buf .buffer .remove (item )
3170- if index <= buf .ptr :
3171- buf .ptr = (buf .ptr - 1 ) % buf .bufsize
3172- buf .buffer .insert ((buf .ptr + 1 )% buf .bufsize , [None ])
3187+ if self .undobuffer :
3188+ self .undobuffer .remove (("stamp" , stampid ))
31733189
31743190 def clearstamp (self , stampid ):
31753191 """Delete stamp with given stampid
@@ -3467,20 +3483,16 @@ def dot(self, size=None, *color):
34673483 color = self ._colorstr (color )
34683484 # If screen were to gain a dot function, see GH #104218.
34693485 pen = self .pen ()
3470- if self .undobuffer :
3471- self .undobuffer .push (["seq" ])
3472- self .undobuffer .cumulate = True
3473- try :
3474- if self .resizemode () == 'auto' :
3475- self .ht ()
3476- self .pendown ()
3477- self .pensize (size )
3478- self .pencolor (color )
3479- self .forward (0 )
3480- finally :
3481- self .pen (pen )
3482- if self .undobuffer :
3483- self .undobuffer .cumulate = False
3486+ with self ._undo_sequence ():
3487+ try :
3488+ if self .resizemode () == 'auto' :
3489+ self .ht ()
3490+ self .pendown ()
3491+ self .pensize (size )
3492+ self .pencolor (color )
3493+ self .forward (0 )
3494+ finally :
3495+ self .pen (pen )
34843496
34853497 def _write (self , txt , align , font ):
34863498 """Performs the writing for write()
@@ -3512,15 +3524,11 @@ def write(self, arg, move=False, align="left", font=("Arial", 8, "normal")):
35123524 >>> turtle.write('Home = ', True, align="center")
35133525 >>> turtle.write((0,0), True)
35143526 """
3515- if self .undobuffer :
3516- self .undobuffer .push (["seq" ])
3517- self .undobuffer .cumulate = True
3518- end = self ._write (str (arg ), align .lower (), font )
3519- if move :
3520- x , y = self .pos ()
3521- self .setpos (end , y )
3522- if self .undobuffer :
3523- self .undobuffer .cumulate = False
3527+ with self ._undo_sequence ():
3528+ end = self ._write (str (arg ), align .lower (), font )
3529+ if move :
3530+ x , y = self .pos ()
3531+ self .setpos (end , y )
35243532
35253533 @contextmanager
35263534 def poly (self ):
@@ -3708,6 +3716,9 @@ def _undo(self, action, data):
37083716 self .clearstamp (stitem )
37093717 elif action == "go" :
37103718 self ._undogoto (data )
3719+ elif action == "teleport" :
3720+ self ._position = data [0 ]
3721+ self ._update ()
37113722 elif action in ["wri" , "dot" ]:
37123723 item = data [0 ]
37133724 self .screen ._delete (item )
0 commit comments