@@ -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,19 @@ 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 undobuffer :
1660+ undobuffer .push (["seq" ])
1661+ undobuffer .cumulate = True
1662+ try :
1663+ yield
1664+ finally :
1665+ if undobuffer :
1666+ undobuffer .cumulate = False
1667+
16461668 def teleport (self , x = None , y = None , * , fill_gap : bool = False ) -> None :
16471669 """To be overwritten by child class RawTurtle.
16481670 Includes no TPen references."""
@@ -1985,38 +2007,34 @@ def circle(self, radius, extent = None, steps = None):
19852007 >>> turtle.circle(50)
19862008 >>> turtle.circle(120, 180) # draw a semicircle
19872009 """
1988- if self .undobuffer :
1989- self .undobuffer .push (["seq" ])
1990- self .undobuffer .cumulate = True
19912010 speed = self .speed ()
19922011 if extent is None :
19932012 extent = self ._fullcircle
19942013 if steps is None :
19952014 frac = abs (extent )/ self ._fullcircle
1996- 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 )
19972016 w = 1.0 * extent / steps
19982017 w2 = 0.5 * w
19992018 l = 2.0 * radius * math .sin (math .radians (w2 )* self ._degreesPerAU )
20002019 if radius < 0 :
20012020 l , w , w2 = - l , - w , - w2
20022021 tr = self ._tracer ()
20032022 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 ):
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 )
20102037 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
20202038
20212039 # Three dummy methods to be implemented by the child class:
20222040
@@ -2787,16 +2805,19 @@ def teleport(self, x=None, y=None, *, fill_gap: bool = False) -> None:
27872805 """
27882806 pendown = self .isdown ()
27892807 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 ()
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 ()
28002821
28012822 def clone (self ):
28022823 """Create and return a clone of the turtle.
@@ -3147,7 +3168,8 @@ def stamp(self):
31473168 screen ._drawpoly (item , poly , fill = self ._cc (fc ),
31483169 outline = self ._cc (oc ), width = self ._outlinewidth , top = True )
31493170 self .stampItems .append (stitem )
3150- self .undobuffer .push (("stamp" , stitem ))
3171+ if self .undobuffer :
3172+ self .undobuffer .push (("stamp" , stitem ))
31513173 return stitem
31523174
31533175 def _clearstamp (self , stampid ):
@@ -3162,15 +3184,8 @@ def _clearstamp(self, stampid):
31623184 self .stampItems .remove (stampid )
31633185 # Delete stampitem from undobuffer if necessary
31643186 # 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 ])
3187+ if self .undobuffer :
3188+ self .undobuffer .remove (("stamp" , stampid ))
31743189
31753190 def clearstamp (self , stampid ):
31763191 """Delete stamp with given stampid
@@ -3468,20 +3483,16 @@ def dot(self, size=None, *color):
34683483 color = self ._colorstr (color )
34693484 # If screen were to gain a dot function, see GH #104218.
34703485 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
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 )
34853496
34863497 def _write (self , txt , align , font ):
34873498 """Performs the writing for write()
@@ -3513,15 +3524,11 @@ def write(self, arg, move=False, align="left", font=("Arial", 8, "normal")):
35133524 >>> turtle.write('Home = ', True, align="center")
35143525 >>> turtle.write((0,0), True)
35153526 """
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
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 )
35253532
35263533 @contextmanager
35273534 def poly (self ):
@@ -3709,6 +3716,9 @@ def _undo(self, action, data):
37093716 self .clearstamp (stitem )
37103717 elif action == "go" :
37113718 self ._undogoto (data )
3719+ elif action == "teleport" :
3720+ self ._position = data [0 ]
3721+ self ._update ()
37123722 elif action in ["wri" , "dot" ]:
37133723 item = data [0 ]
37143724 self .screen ._delete (item )
0 commit comments