-
-
Notifications
You must be signed in to change notification settings - Fork 37.4k
gh-158563: Fix various bugs in turtle's undo handling
#158564
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
turtle's undo handling
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -897,6 +897,15 @@ def pop(self): | |
| self.ptr = (self.ptr - 1) % self.bufsize | ||
| return (item) | ||
|
|
||
| def remove(self, item): | ||
| if item not in self.buffer: | ||
| return | ||
| index = self.buffer.index(item) | ||
| self.buffer.remove(item) | ||
| if index <= self.ptr: | ||
| self.ptr = (self.ptr - 1) % self.bufsize | ||
| self.buffer.insert((self.ptr+1) % self.bufsize, [None]) | ||
|
|
||
| def nr_of_items(self): | ||
| return self.bufsize - self.buffer.count([None]) | ||
|
|
||
|
|
@@ -1643,6 +1652,19 @@ def _goto(self, end): | |
| """Move the turtle to the end position.""" | ||
| self._position = end | ||
|
|
||
| @contextmanager | ||
| def _undo_sequence(self): | ||
| """Record the enclosed actions as a single undo step.""" | ||
| undobuffer = self.undobuffer | ||
| if undobuffer: | ||
| undobuffer.push(["seq"]) | ||
| undobuffer.cumulate = True | ||
| try: | ||
| yield | ||
| finally: | ||
| if undobuffer: | ||
| undobuffer.cumulate = False | ||
|
|
||
| def teleport(self, x=None, y=None, *, fill_gap: bool = False) -> None: | ||
| """To be overwritten by child class RawTurtle. | ||
| Includes no TPen references.""" | ||
|
|
@@ -1985,38 +2007,34 @@ def circle(self, radius, extent = None, steps = None): | |
| >>> turtle.circle(50) | ||
| >>> turtle.circle(120, 180) # draw a semicircle | ||
| """ | ||
| if self.undobuffer: | ||
| self.undobuffer.push(["seq"]) | ||
| self.undobuffer.cumulate = True | ||
| speed = self.speed() | ||
| if extent is None: | ||
| extent = self._fullcircle | ||
| if steps is None: | ||
| frac = abs(extent)/self._fullcircle | ||
| steps = 1+int(min(11+abs(radius)/6.0, 59.0)*frac) | ||
| steps = 1 + int(min(11 + abs(radius) / 6.0, 59.0) * frac) | ||
| w = 1.0 * extent / steps | ||
| w2 = 0.5 * w | ||
| l = 2.0 * radius * math.sin(math.radians(w2)*self._degreesPerAU) | ||
| if radius < 0: | ||
| l, w, w2 = -l, -w, -w2 | ||
| tr = self._tracer() | ||
| dl = self._delay() | ||
| if speed == 0: | ||
| self._tracer(0, 0) | ||
| else: | ||
| self.speed(0) | ||
| self._rotate(w2) | ||
| for i in range(steps): | ||
| with self._undo_sequence(): | ||
| if speed == 0: | ||
| self._tracer(0, 0) | ||
| else: | ||
| self.speed(0) | ||
| self._rotate(w2) | ||
| for i in range(steps): | ||
| self.speed(speed) | ||
| self._go(l) | ||
| self.speed(0) | ||
| self._rotate(w) | ||
| self._rotate(-w2) | ||
| if speed == 0: | ||
| self._tracer(tr, dl) | ||
| self.speed(speed) | ||
| self._go(l) | ||
| self.speed(0) | ||
| self._rotate(w) | ||
| self._rotate(-w2) | ||
| if speed == 0: | ||
| self._tracer(tr, dl) | ||
| self.speed(speed) | ||
| if self.undobuffer: | ||
| self.undobuffer.cumulate = False | ||
|
|
||
| # Three dummy methods to be implemented by the child class: | ||
|
|
||
|
|
@@ -2787,16 +2805,19 @@ def teleport(self, x=None, y=None, *, fill_gap: bool = False) -> None: | |
| """ | ||
| pendown = self.isdown() | ||
| was_filling = self.filling() | ||
| if pendown: | ||
| self.pen(pendown=False) | ||
| if was_filling and not fill_gap: | ||
| self.end_fill() | ||
| new_x = x if x is not None else self._position[0] | ||
| new_y = y if y is not None else self._position[1] | ||
| self._position = Vec2D(new_x, new_y) | ||
| self.pen(pendown=pendown) | ||
| if was_filling and not fill_gap: | ||
| self.begin_fill() | ||
| with self._undo_sequence(): | ||
| if pendown: | ||
| self.pen(pendown=False) | ||
| if was_filling and not fill_gap: | ||
| self.end_fill() | ||
|
Comment on lines
+2812
to
+2813
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an existing bug, and it's not just for We can do this in a follow up to ease backporting. |
||
| new_x = x if x is not None else self._position[0] | ||
| new_y = y if y is not None else self._position[1] | ||
| if self.undobuffer: | ||
| self.undobuffer.push(("teleport", self._position)) | ||
| self._position = Vec2D(new_x, new_y) | ||
| self.pen(pendown=pendown) | ||
| if was_filling and not fill_gap: | ||
| self.begin_fill() | ||
|
|
||
| def clone(self): | ||
| """Create and return a clone of the turtle. | ||
|
|
@@ -3147,7 +3168,8 @@ def stamp(self): | |
| screen._drawpoly(item, poly, fill=self._cc(fc), | ||
| outline=self._cc(oc), width=self._outlinewidth, top=True) | ||
| self.stampItems.append(stitem) | ||
| self.undobuffer.push(("stamp", stitem)) | ||
| if self.undobuffer: | ||
| self.undobuffer.push(("stamp", stitem)) | ||
| return stitem | ||
|
|
||
| def _clearstamp(self, stampid): | ||
|
|
@@ -3162,15 +3184,8 @@ def _clearstamp(self, stampid): | |
| self.stampItems.remove(stampid) | ||
| # Delete stampitem from undobuffer if necessary | ||
| # if clearstamp is called directly. | ||
| item = ("stamp", stampid) | ||
| buf = self.undobuffer | ||
| if item not in buf.buffer: | ||
| return | ||
| index = buf.buffer.index(item) | ||
| buf.buffer.remove(item) | ||
| if index <= buf.ptr: | ||
| buf.ptr = (buf.ptr - 1) % buf.bufsize | ||
| buf.buffer.insert((buf.ptr+1)%buf.bufsize, [None]) | ||
| if self.undobuffer: | ||
| self.undobuffer.remove(("stamp", stampid)) | ||
|
|
||
| def clearstamp(self, stampid): | ||
| """Delete stamp with given stampid | ||
|
|
@@ -3468,20 +3483,16 @@ def dot(self, size=None, *color): | |
| color = self._colorstr(color) | ||
| # If screen were to gain a dot function, see GH #104218. | ||
| pen = self.pen() | ||
| if self.undobuffer: | ||
| self.undobuffer.push(["seq"]) | ||
| self.undobuffer.cumulate = True | ||
| try: | ||
| if self.resizemode() == 'auto': | ||
| self.ht() | ||
| self.pendown() | ||
| self.pensize(size) | ||
| self.pencolor(color) | ||
| self.forward(0) | ||
| finally: | ||
| self.pen(pen) | ||
| if self.undobuffer: | ||
| self.undobuffer.cumulate = False | ||
| with self._undo_sequence(): | ||
| try: | ||
| if self.resizemode() == 'auto': | ||
| self.ht() | ||
| self.pendown() | ||
| self.pensize(size) | ||
| self.pencolor(color) | ||
| self.forward(0) | ||
| finally: | ||
| self.pen(pen) | ||
|
|
||
| def _write(self, txt, align, font): | ||
| """Performs the writing for write() | ||
|
|
@@ -3513,15 +3524,11 @@ def write(self, arg, move=False, align="left", font=("Arial", 8, "normal")): | |
| >>> turtle.write('Home = ', True, align="center") | ||
| >>> turtle.write((0,0), True) | ||
| """ | ||
| if self.undobuffer: | ||
| self.undobuffer.push(["seq"]) | ||
| self.undobuffer.cumulate = True | ||
| end = self._write(str(arg), align.lower(), font) | ||
| if move: | ||
| x, y = self.pos() | ||
| self.setpos(end, y) | ||
| if self.undobuffer: | ||
| self.undobuffer.cumulate = False | ||
| with self._undo_sequence(): | ||
| end = self._write(str(arg), align.lower(), font) | ||
| if move: | ||
| x, y = self.pos() | ||
| self.setpos(end, y) | ||
|
|
||
| @contextmanager | ||
| def poly(self): | ||
|
|
@@ -3709,6 +3716,9 @@ def _undo(self, action, data): | |
| self.clearstamp(stitem) | ||
| elif action == "go": | ||
| self._undogoto(data) | ||
| elif action == "teleport": | ||
| self._position = data[0] | ||
| self._update() | ||
| elif action in ["wri", "dot"]: | ||
| item = data[0] | ||
| self.screen._delete(item) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Fix :func:`turtle.undo` after :func:`turtle.teleport` and after an exception | ||
| in :func:`turtle.circle`, :func:`turtle.dot` or :func:`turtle.write`. Fix | ||
| :func:`turtle.stamp`, :func:`turtle.clearstamp`, :func:`turtle.clear` and | ||
| :func:`turtle.reset` when the undo buffer is disabled. |
Uh oh!
There was an error while loading. Please reload this page.