From d3f9133bb4071c8dbd875f5c62c928643843e5df Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Wed, 25 Nov 2020 14:59:28 -0700 Subject: [PATCH 01/14] copy Undo over from apptools --- pyface/undo/__init__.py | 13 + pyface/undo/abstract_command.py | 76 +++++ pyface/undo/action/__init__.py | 10 + .../action/abstract_command_stack_action.py | 83 +++++ pyface/undo/action/api.py | 17 + pyface/undo/action/command_action.py | 58 ++++ pyface/undo/action/redo_action.py | 49 +++ pyface/undo/action/undo_action.py | 47 +++ pyface/undo/api.py | 20 ++ pyface/undo/command_stack.py | 319 ++++++++++++++++++ pyface/undo/i_command.py | 65 ++++ pyface/undo/i_command_stack.py | 91 +++++ pyface/undo/i_undo_manager.py | 63 ++++ pyface/undo/tests/__init__.py | 0 pyface/undo/tests/test_command_stack.py | 168 +++++++++ pyface/undo/tests/testing_commands.py | 37 ++ pyface/undo/undo_manager.py | 122 +++++++ 17 files changed, 1238 insertions(+) create mode 100644 pyface/undo/__init__.py create mode 100644 pyface/undo/abstract_command.py create mode 100644 pyface/undo/action/__init__.py create mode 100644 pyface/undo/action/abstract_command_stack_action.py create mode 100644 pyface/undo/action/api.py create mode 100644 pyface/undo/action/command_action.py create mode 100644 pyface/undo/action/redo_action.py create mode 100644 pyface/undo/action/undo_action.py create mode 100644 pyface/undo/api.py create mode 100644 pyface/undo/command_stack.py create mode 100644 pyface/undo/i_command.py create mode 100644 pyface/undo/i_command_stack.py create mode 100644 pyface/undo/i_undo_manager.py create mode 100644 pyface/undo/tests/__init__.py create mode 100644 pyface/undo/tests/test_command_stack.py create mode 100644 pyface/undo/tests/testing_commands.py create mode 100644 pyface/undo/undo_manager.py diff --git a/pyface/undo/__init__.py b/pyface/undo/__init__.py new file mode 100644 index 000000000..78fc4a3a2 --- /dev/null +++ b/pyface/undo/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2005-2011, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. +""" Supports undoing and scripting application commands. + Part of the AppTools project of the Enthought Tool Suite. +""" diff --git a/pyface/undo/abstract_command.py b/pyface/undo/abstract_command.py new file mode 100644 index 000000000..c428ce42a --- /dev/null +++ b/pyface/undo/abstract_command.py @@ -0,0 +1,76 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +# Enthought library imports. +from traits.api import Any, HasTraits, Str, provides + +# Local imports. +from .i_command import ICommand + + +@provides(ICommand) +class AbstractCommand(HasTraits): + """The AbstractCommand class is an abstract base class that implements the + ICommand interface. + """ + + #### 'ICommand' interface ################################################# + + # This is the data on which the command operates. + data = Any + + # This is the name of the command as it will appear in any GUI element. It + # may include '&' which will be automatically removed whenever it is + # inappropriate. + name = Str + + ########################################################################### + # 'ICommand' interface. + ########################################################################### + + def do(self): + """This is called by the command stack to do the command and to return + any value. The command must save any state necessary for the 'redo()' + and 'undo()' methods to work. The class's __init__() must also ensure + that deep copies of any arguments are made if appropriate. It is + guaranteed that this will only ever be called once and that it will be + called before any call to 'redo()' or 'undo()'. + """ + + raise NotImplementedError + + def merge(self, other): + """This is called by the command stack to try and merge another + command with this one. True is returned if the commands were merged. + 'other' is the command that is about to be executed. If the commands + are merged then 'other' will discarded and not placed on the command + stack. A subsequent undo or redo of this modified command must have + the same effect as the two original commands. + """ + + # By default merges never happen. + return False + + def redo(self): + """This is called by the command stack to redo the command. Any + returned value will replace the value that the command stack references + from the original call to 'do()' or previous call to 'redo()'. + """ + + raise NotImplementedError + + def undo(self): + """ This is called by the command stack to undo the command. """ + + raise NotImplementedError diff --git a/pyface/undo/action/__init__.py b/pyface/undo/action/__init__.py new file mode 100644 index 000000000..bc54012db --- /dev/null +++ b/pyface/undo/action/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2005-2011, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Enthought, Inc. diff --git a/pyface/undo/action/abstract_command_stack_action.py b/pyface/undo/action/abstract_command_stack_action.py new file mode 100644 index 000000000..46f30e830 --- /dev/null +++ b/pyface/undo/action/abstract_command_stack_action.py @@ -0,0 +1,83 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2008, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +# Enthought library imports. +from pyface.action.api import Action +from traits.api import Instance + +# Local library imports +from ..i_undo_manager import IUndoManager + + +class AbstractCommandStackAction(Action): + """The abstract base class for all actions that operate on a command + stack. + """ + + #### 'AbstractCommandStackAction' interface ############################### + + # The undo manager. + undo_manager = Instance(IUndoManager) + + ########################################################################### + # 'object' interface. + ########################################################################### + + def __init__(self, **traits): + """ Initialise the instance. """ + + super(AbstractCommandStackAction, self).__init__(**traits) + + self.undo_manager.on_trait_event( + self._on_stack_updated, "stack_updated" + ) + + # Update the action to initialise it. + self._update_action() + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def destroy(self): + """Called when the action is no longer required. + + By default this method does nothing, but this would be a great place to + unhook trait listeners etc. + + """ + + self.undo_manager.on_trait_event( + self._on_stack_updated, "stack_updated", remove=True + ) + + ########################################################################### + # Protected interface. + ########################################################################### + + def _update_action(self): + """ Update the state of the action. """ + + raise NotImplementedError + + ########################################################################### + # Private interface. + ########################################################################### + + def _on_stack_updated(self, stack): + """ Handle changes to the state of a command stack. """ + + # Ignore unless it is the active stack. + if stack is self.undo_manager.active_stack: + self._update_action() diff --git a/pyface/undo/action/api.py b/pyface/undo/action/api.py new file mode 100644 index 000000000..48d52295c --- /dev/null +++ b/pyface/undo/action/api.py @@ -0,0 +1,17 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +from .command_action import CommandAction +from .redo_action import RedoAction +from .undo_action import UndoAction diff --git a/pyface/undo/action/command_action.py b/pyface/undo/action/command_action.py new file mode 100644 index 000000000..8684ca212 --- /dev/null +++ b/pyface/undo/action/command_action.py @@ -0,0 +1,58 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +# Enthought library imports. +from pyface.action.api import Action +from traits.api import Any, Callable, Instance +from ..i_command_stack import ICommandStack + + +class CommandAction(Action): + """The CommandAction class is an Action class that wraps undo/redo + commands. It is only useful for commands that do not take any arguments or + return any result. + """ + + #### 'CommandAction' interface ############################################ + + # The command to create when the action is performed. + command = Callable + + # The command stack onto which the command will be pushed when the action + # is performed. + command_stack = Instance(ICommandStack) + + # This is the data on which the command operates. + data = Any + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform(self, event): + """This is reimplemented to push a new command instance onto the + command stack. + """ + + self.command_stack.push(self.command(data=self.data)) + + def _name_default(self): + """ This gets the action name from the command. """ + + if self.command: + name = self.command().name + else: + name = "" + + return name diff --git a/pyface/undo/action/redo_action.py b/pyface/undo/action/redo_action.py new file mode 100644 index 000000000..4cb426e5e --- /dev/null +++ b/pyface/undo/action/redo_action.py @@ -0,0 +1,49 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +# Local imports. +from .abstract_command_stack_action import AbstractCommandStackAction + + +class RedoAction(AbstractCommandStackAction): + """An action that redos the last command undone of the active command + stack. + """ + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform(self, event): + """ Perform the action. """ + + self.undo_manager.redo() + + ########################################################################### + # 'AbstractUndoAction' interface. + ########################################################################### + + def _update_action(self): + """ Update the state of the action. """ + + name = self.undo_manager.redo_name + + if name: + name = "&Redo " + name + self.enabled = True + else: + name = "&Redo" + self.enabled = False + + self.name = name diff --git a/pyface/undo/action/undo_action.py b/pyface/undo/action/undo_action.py new file mode 100644 index 000000000..b6a273996 --- /dev/null +++ b/pyface/undo/action/undo_action.py @@ -0,0 +1,47 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +# Local imports. +from .abstract_command_stack_action import AbstractCommandStackAction + + +class UndoAction(AbstractCommandStackAction): + """ An action that undos the last command of the active command stack. """ + + ########################################################################### + # 'Action' interface. + ########################################################################### + + def perform(self, event): + """ Perform the action. """ + + self.undo_manager.undo() + + ########################################################################### + # 'AbstractUndoAction' interface. + ########################################################################### + + def _update_action(self): + """ Update the state of the action. """ + + name = self.undo_manager.undo_name + + if name: + name = "&Undo " + name + self.enabled = True + else: + name = "&Undo" + self.enabled = False + + self.name = name diff --git a/pyface/undo/api.py b/pyface/undo/api.py new file mode 100644 index 000000000..ce6fb87f4 --- /dev/null +++ b/pyface/undo/api.py @@ -0,0 +1,20 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2008, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +from .abstract_command import AbstractCommand +from .command_stack import CommandStack +from .i_command import ICommand +from .i_command_stack import ICommandStack +from .i_undo_manager import IUndoManager +from .undo_manager import UndoManager diff --git a/pyface/undo/command_stack.py b/pyface/undo/command_stack.py new file mode 100644 index 000000000..ca1cacc93 --- /dev/null +++ b/pyface/undo/command_stack.py @@ -0,0 +1,319 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2008, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +# Enthought library imports. +from traits.api import ( + Bool, + HasTraits, + Instance, + Int, + List, + Property, + Str, + provides, +) + +# Local imports. +from .abstract_command import AbstractCommand +from .i_command import ICommand +from .i_command_stack import ICommandStack +from .i_undo_manager import IUndoManager + + +class _StackEntry(HasTraits): + """ The _StackEntry class is a single entry on a command stack. """ + + #### '_StackEntry' interface ############################################## + + # Set if the entry corresponds to a clean point on the stack. + clean = Bool(False) + + # The command instance. + command = Instance(ICommand) + + # The sequence number of the entry. + sequence_nr = Int + + +class _MacroCommand(AbstractCommand): + """ The _MacroCommand class is an internal command that handles macros. """ + + #### '_MacroCommand' interface ############################################ + + # The commands that make up this macro. + macro_commands = List(Instance(ICommand)) + + ########################################################################### + # 'ICommand' interface. + ########################################################################### + + def do(self): + """ Invoke the command. """ + + # This is a dummy. + return None + + def merge(self, other): + """ Try and merge a command. """ + + if len(self.macro_commands) == 0: + merged = False + else: + merged = self.macro_commands[-1].merge(other) + + return merged + + def redo(self): + """ Redo the sub-commands. """ + + for cmd in self.macro_commands: + cmd.redo() + + # Macros cannot return values. + return None + + def undo(self): + """ Undo the sub-commands. """ + + for cmd in self.macro_commands: + cmd.undo() + + +@provides(ICommandStack) +class CommandStack(HasTraits): + """The CommandStack class is the default implementation of the + ICommandStack interface. + """ + + #### 'ICommandStack' interface ############################################ + + # This is the clean state of the stack. Its value changes as commands are + # undone and redone. It can also be explicity set to mark the current + # stack position as being clean (when the data is saved to disk for + # example). + clean = Property(Bool) + + # This is the name of the command that can be redone. It will be empty if + # there is no command that can be redone. It is maintained by the undo + # stack. + redo_name = Property(Str) + + # This is the undo manager that manages this stack. + undo_manager = Instance(IUndoManager) + + # This is the name of the command that can be undone. It will be empty if + # there is no command that can be undone. It is maintained by the undo + # stack. + undo_name = Property(Str) + + #### Private interface #################################################### + + # The current index into the stack (ie. the last command that was done). + _index = Int(-1) + + # The current macro stack. + _macro_stack = List(Instance(_MacroCommand)) + + # The stack itself. + _stack = List(Instance(_StackEntry)) + + ########################################################################### + # 'ICommandStack' interface. + ########################################################################### + + def begin_macro(self, name): + """This begins a macro by creating an empty command with the given + 'name'. All subsequent calls to 'push()' create commands that will be + children of the empty command until the next call to 'end_macro()'. + Macros may be nested. The stack is disabled (ie. nothing can be undone + or redone) while a macro is being created (ie. while there is an + outstanding 'end_macro()' call). + """ + + command = _MacroCommand(name=name) + self.push(command) + self._macro_stack.append(command) + + def clear(self): + """This clears the stack, without undoing or redoing any commands, and + leaves the stack in a clean state. It is typically used when all + changes to the data have been abandoned. + """ + + self._index = -1 + self._stack = [] + self._macro_stack = [] + + self.undo_manager.stack_updated = self + + def end_macro(self): + """ This ends a macro. """ + + try: + self._macro_stack.pop() + except IndexError: + pass + + def push(self, command): + """This executes a command and saves it on the command stack so that + it can be subsequently undone and redone. 'command' is an instance + that implements the ICommand interface. Its 'do()' method is called + to execute the command. If any value is returned by 'do()' then it is + returned by 'push()'. + """ + + # See if the command can be merged with the previous one. + if len(self._macro_stack) == 0: + if self._index >= 0: + merged = self._stack[self._index].command.merge(command) + else: + merged = False + else: + merged = self._macro_stack[-1].merge(command) + + # Increment the global sequence number. + if not merged: + self.undo_manager.sequence_nr += 1 + + # Execute the command. + result = command.do() + + # Do nothing more if the command was merged. + if merged: + return result + + # Only update the command stack if there is no current macro. + if len(self._macro_stack) == 0: + # Remove everything on the stack after the last command that was + # done. + self._index += 1 + del self._stack[self._index:] + + # Create a new stack entry and add it to the stack. + entry = _StackEntry( + command=command, sequence_nr=self.undo_manager.sequence_nr + ) + + self._stack.append(entry) + self.undo_manager.stack_updated = self + else: + # Add the command to the parent macro command. + self._macro_stack[-1].macro_commands.append(command) + + return result + + def redo(self, sequence_nr=0): + """If 'sequence_nr' is 0 then the last command that was undone is + redone and any result returned. Otherwise commands are redone up to + and including the given 'sequence_nr' and any result of the last of + these is returned. + """ + + # Make sure a redo is valid in the current context. + if self.redo_name == "": + return None + + if sequence_nr == 0: + result = self._redo_one() + else: + result = None + + while self._index + 1 < len(self._stack): + if self._stack[self._index + 1].sequence_nr > sequence_nr: + break + + result = self._redo_one() + + self.undo_manager.stack_updated = self + + return result + + def undo(self, sequence_nr=0): + """If 'sequence_nr' is 0 then the last command is undone. Otherwise + commands are undone up to and including the given 'sequence_nr'. + """ + + # Make sure an undo is valid in the current context. + if self.undo_name == "": + return + + if sequence_nr == 0: + self._undo_one() + else: + while self._index >= 0: + if self._stack[self._index].sequence_nr <= sequence_nr: + break + + self._undo_one() + + self.undo_manager.stack_updated = self + + ########################################################################### + # Private interface. + ########################################################################### + + def _redo_one(self): + """ Redo the command at the current index and return the result. """ + + self._index += 1 + entry = self._stack[self._index] + + return entry.command.redo() + + def _undo_one(self): + """ Undo the command at the current index. """ + + entry = self._stack[self._index] + self._index -= 1 + + entry.command.undo() + + def _get_clean(self): + """ Get the clean state of the stack. """ + + if self._index >= 0: + clean = self._stack[self._index].clean + else: + clean = True + + return clean + + def _set_clean(self, clean): + """ Set the clean state of the stack. """ + + if self._index >= 0: + self._stack[self._index].clean = clean + + def _get_redo_name(self): + """ Get the name of the redo command, if any. """ + + redo_name = "" + + if len(self._macro_stack) == 0 and self._index + 1 < len(self._stack): + redo_name = self._stack[self._index + 1].command.name.replace( + "&", "" + ) + + return redo_name + + def _get_undo_name(self): + """ Get the name of the undo command, if any. """ + + undo_name = "" + + if len(self._macro_stack) == 0 and self._index >= 0: + command = self._stack[self._index].command + undo_name = command.name.replace("&", "") + + return undo_name diff --git a/pyface/undo/i_command.py b/pyface/undo/i_command.py new file mode 100644 index 000000000..ca7c2217b --- /dev/null +++ b/pyface/undo/i_command.py @@ -0,0 +1,65 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + + +# Enthought library imports. +from traits.api import Any, Interface, Str + + +class ICommand(Interface): + """The command interface. The state of the data can be changed by passing + an instance that implements this interface to the 'push()' method of a + command stack along with any arguments. + """ + + #### 'ICommand' interface ################################################# + + # This is the data on which the command operates. + data = Any + + # This is the name of the command as it will appear in any GUI element. It + # may include '&' which will be automatically removed whenever it is + # inappropriate. + name = Str + + ########################################################################### + # 'ICommand' interface. + ########################################################################### + + def do(self): + """This is called by the command stack to do the command and to return + any value. The command must save any state necessary for the 'redo()' + and 'undo()' methods to work. The class's __init__() must also ensure + that deep copies of any arguments are made if appropriate. It is + guaranteed that this will only ever be called once and that it will be + called before any call to 'redo()' or 'undo()'. + """ + + def merge(self, other): + """This is called by the command stack to try and merge another + command with this one. True is returned if the commands were merged. + 'other' is the command that is about to be executed. If the commands + are merged then 'other' will discarded and not placed on the command + stack. A subsequent undo or redo of this modified command must have + the same effect as the two original commands. + """ + + def redo(self): + """This is called by the command stack to redo the command. Any + returned value will replace the value that the command stack references + from the original call to 'do()' or previous call to 'redo()'. + """ + + def undo(self): + """ This is called by the command stack to undo the command. """ diff --git a/pyface/undo/i_command_stack.py b/pyface/undo/i_command_stack.py new file mode 100644 index 000000000..2c923fc5f --- /dev/null +++ b/pyface/undo/i_command_stack.py @@ -0,0 +1,91 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +# Enthought library imports. +from traits.api import Bool, Instance, Interface, Str + +# Local imports. +from .i_undo_manager import IUndoManager + + +class ICommandStack(Interface): + """The command stack interface. A command stack is responsible for + managing the changes to a data model and recording those changes so that + they can be undone or redone. + """ + + #### 'ICommandStack' interface ############################################ + + # This is the clean state of the stack. Its value changes as commands are + # undone and redone. It can also be explicity set to mark the current + # stack position as being clean (when the data is saved to disk for + # example). + clean = Bool + + # This is the name of the command that can be redone. It will be empty if + # there is no command that can be redone. It is maintained by the undo + # stack. + redo_name = Str + + # This is the undo manager that manages this stack. + undo_manager = Instance(IUndoManager) + + # This is the name of the command that can be undone. It will be empty if + # there is no command that can be undone. It is maintained by the undo + # stack. + undo_name = Str + + ########################################################################### + # 'ICommandStack' interface. + ########################################################################### + + def begin_macro(self, name): + """This begins a macro by creating an empty command with the given + 'name'. The commands passed to all subsequent calls to 'push()' will + be contained in the macro until the next call to 'end_macro()'. Macros + may be nested. The stack is disabled (ie. nothing can be undone or + redone) while a macro is being created (ie. while there is an + outstanding 'end_macro()' call). + """ + + def clear(self): + """This clears the stack, without undoing or redoing any commands, and + leaves the stack in a clean state. It is typically used when all + changes to the data have been abandoned. + """ + + def end_macro(self): + """ This ends a macro. """ + + def push(self, command): + """This executes a command and saves it on the command stack so that + it can be subsequently undone and redone. 'command' is an instance + that implements the ICommand interface. Its 'do()' method is called + to execute the command. If any value is returned by 'do()' then it is + returned by 'push()'. The command stack will keep a reference to the + result so that it can recognise it as an argument to a subsequent + command (which allows a script to properly save a result needed later). + """ + + def redo(self, sequence_nr=0): + """If 'sequence_nr' is 0 then the last command that was undone is + redone and any result returned. Otherwise commands are redone up to + and including the given 'sequence_nr' and any result of the last of + these is returned. + """ + + def undo(self, sequence_nr=0): + """If 'sequence_nr' is 0 then the last command is undone. Otherwise + commands are undone up to and including the given 'sequence_nr'. + """ diff --git a/pyface/undo/i_undo_manager.py b/pyface/undo/i_undo_manager.py new file mode 100644 index 000000000..ff15e7052 --- /dev/null +++ b/pyface/undo/i_undo_manager.py @@ -0,0 +1,63 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2008, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +# Enthought library imports. +from traits.api import Bool, Event, Instance, Int, Interface, Str + + +class IUndoManager(Interface): + """The undo manager interface. An undo manager is responsible for one or + more command stacks. Typically an application would have a single undo + manager. + """ + + #### 'IUndoManager' interface ############################################# + + # This is the currently active command stack and may be None. Typically it + # is set when some sort of editor becomes active. + active_stack = Instance("apptools.undo.api.ICommandStack") + + # This reflects the clean state of the currently active command stack. It + # is intended to support a "document modified" indicator in the GUI. It is + # maintained by the undo manager. + active_stack_clean = Bool + + # This is the name of the command that can be redone. It will be empty if + # there is no command that can be redone. It is maintained by the undo + # manager. + redo_name = Str + + # This is the sequence number of the next command to be performed. It is + # incremented immediately before a command is invoked (by its 'do()' + # method). + sequence_nr = Int + + # This event is fired when the index of a command stack changes. Note that + # it may not be the active stack. + stack_updated = Event(Instance("apptools.undo.api.ICommandStack")) + + # This is the name of the command that can be undone. It will be empty if + # there is no command that can be undone. It is maintained by the undo + # manager. + undo_name = Str + + ########################################################################### + # 'IUndoManager' interface. + ########################################################################### + + def redo(self): + """ Redo the last undone command of the active command stack. """ + + def undo(self): + """ Undo the last command of the active command stack. """ diff --git a/pyface/undo/tests/__init__.py b/pyface/undo/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pyface/undo/tests/test_command_stack.py b/pyface/undo/tests/test_command_stack.py new file mode 100644 index 000000000..c564f3b04 --- /dev/null +++ b/pyface/undo/tests/test_command_stack.py @@ -0,0 +1,168 @@ +# ----------------------------------------------------------------------------- +# +# Copyright (c) 2015, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# +# ----------------------------------------------------------------------------- + +from contextlib import contextmanager +import unittest + +from pyface.undo.api import CommandStack, UndoManager +from pyface.undo.tests.testing_commands import SimpleCommand, UnnamedCommand + + +class TestCommandStack(unittest.TestCase): + def setUp(self): + self.stack = CommandStack() + undo_manager = UndoManager() + self.stack.undo_manager = undo_manager + + self.command = SimpleCommand() + + # Command pushing tests --------------------------------------------------- + + def test_empty_command_stack(self): + with self.assert_n_commands_pushed(self.stack, 0): + pass + + def test_1_command_pushed(self): + with self.assert_n_commands_pushed(self.stack, 1): + self.stack.push(self.command) + + def test_n_command_pushed(self): + n = 4 + with self.assert_n_commands_pushed(self.stack, n): + for i in range(n): + self.stack.push(self.command) + + # Undo/Redo tests --------------------------------------------------------- + + def test_undo_1_command(self): + with self.assert_n_commands_pushed_and_undone(self.stack, 1): + self.stack.push(self.command) + self.assertEqual(self.stack.undo_name, self.command.name) + self.stack.undo() + + def test_undo_n_command(self): + n = 4 + with self.assert_n_commands_pushed_and_undone(self.stack, n): + for i in range(n): + self.stack.push(self.command) + + for i in range(n): + self.stack.undo() + + def test_undo_unnamed_command(self): + unnamed_command = UnnamedCommand() + with self.assert_n_commands_pushed(self.stack, 1): + self.stack.push(unnamed_command) + + # But the command cannot be undone because it has no name + self.assertEqual(self.stack.undo_name, "") + # This is a no-op + self.stack.undo() + + def test_undo_redo_1_command(self): + with self.assert_n_commands_pushed(self.stack, 1): + self.stack.push(self.command) + self.stack.undo() + self.stack.redo() + + # Macro tests ------------------------------------------------------------- + + def test_define_macro(self): + with self.assert_n_commands_pushed(self.stack, 1): + add_macro(self.stack, num_commands=2) + + def test_undo_macro(self): + with self.assert_n_commands_pushed_and_undone(self.stack, 1): + # The 2 pushes are viewed as 1 command + add_macro(self.stack, num_commands=2) + self.stack.undo() + + # Cleanliness tests ------------------------------------------------------- + + def test_empty_stack_is_clean(self): + self.assertTrue(self.stack.clean) + + def test_non_empty_stack_is_dirty(self): + self.stack.push(self.command) + self.assertFalse(self.stack.clean) + + def test_make_clean(self): + # This makes it dirty by default + self.stack.push(self.command) + # Make the current tip of the stack clean + self.stack.clean = True + self.assertTrue(self.stack.clean) + + def test_make_dirty(self): + # Start from a clean state: + self.stack.push(self.command) + self.stack.clean = True + + self.stack.clean = False + self.assertFalse(self.stack.clean) + + def test_save_push_undo_is_clean(self): + self.stack.push(self.command) + + self.stack.clean = True + self.stack.push(self.command) + self.stack.undo() + self.assertTrue(self.stack.clean) + + def test_save_push_save_undo_is_clean(self): + self.stack.push(self.command) + + self.stack.clean = True + self.stack.push(self.command) + self.stack.clean = True + self.stack.undo() + self.assertTrue(self.stack.clean) + + def test_push_undo_save_redo_is_dirty(self): + self.stack.push(self.command) + self.stack.undo() + self.stack.clean = True + self.stack.redo() + self.assertFalse(self.stack.clean) + + # Assertion helpers ------------------------------------------------------- + + @contextmanager + def assert_n_commands_pushed(self, stack, n): + current_length = len(stack._stack) + yield + # N commands have been pushed... + self.assertEqual(len(stack._stack), current_length + n) + # ... and the state is at the tip of the stack... + self.assertEqual(stack._index, current_length + n - 1) + + @contextmanager + def assert_n_commands_pushed_and_undone(self, stack, n): + current_length = len(stack._stack) + yield + # N commands have been pushed and then reverted. The stack still + # contains the commands... + self.assertEqual(len(stack._stack), n) + # ... but we are back to the initial (clean) state + self.assertEqual(stack._index, current_length - 1) + + +def add_macro(stack, num_commands=2): + command = SimpleCommand() + stack.begin_macro("Increment n times") + try: + for i in range(num_commands): + stack.push(command) + finally: + stack.end_macro() diff --git a/pyface/undo/tests/testing_commands.py b/pyface/undo/tests/testing_commands.py new file mode 100644 index 000000000..88517ed9c --- /dev/null +++ b/pyface/undo/tests/testing_commands.py @@ -0,0 +1,37 @@ +# ----------------------------------------------------------------------------- +# +# Copyright (c) 2015, Enthought, Inc. +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! +# +# ----------------------------------------------------------------------------- + +from traits.api import Int +from pyface.undo.api import AbstractCommand + + +class SimpleCommand(AbstractCommand): + """ Simplest command possible operating on an integer. """ + + name = "Increment by 1" + + data = Int + + def do(self): + self.redo() + + def redo(self): + self.data += 1 + + def undo(self): + self.data -= 1 + + +class UnnamedCommand(SimpleCommand): + name = "" diff --git a/pyface/undo/undo_manager.py b/pyface/undo/undo_manager.py new file mode 100644 index 000000000..ed6aa5740 --- /dev/null +++ b/pyface/undo/undo_manager.py @@ -0,0 +1,122 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) 2008, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ------------------------------------------------------------------------------ + +# Enthought library imports. +from traits.api import ( + Bool, + Event, + HasTraits, + Instance, + Int, + Property, + Str, + provides, +) + +# Local imports. +from .i_undo_manager import IUndoManager + + +@provides(IUndoManager) +class UndoManager(HasTraits): + """The UndoManager class is the default implementation of the + IUndoManager interface. + """ + + #### 'IUndoManager' interface ############################################# + + # This is the currently active command stack and may be None. Typically it + # is set when some sort of editor becomes active. + active_stack = Instance("apptools.undo.api.ICommandStack") + + # This reflects the clean state of the currently active command stack. It + # is intended to support a "document modified" indicator in the GUI. It is + # maintained by the undo manager. + active_stack_clean = Property(Bool) + + # This is the name of the command that can be redone. It will be empty if + # there is no command that can be redone. It is maintained by the undo + # manager. + redo_name = Property(Str) + + # This is the sequence number of the next command to be performed. It is + # incremented immediately before a command is invoked (by its 'do()' + # method). + sequence_nr = Int + + # This event is fired when the index of a command stack changes. The value + # of the event is the stack that has changed. Note that it may not be the + # active stack. + stack_updated = Event + + # This is the name of the command that can be undone. It will be empty if + # there is no command that can be undone. It is maintained by the undo + # manager. + undo_name = Property(Str) + + ########################################################################### + # 'IUndoManager' interface. + ########################################################################### + + def redo(self): + """ Redo the last undone command of the active command stack. """ + + if self.active_stack is not None: + self.active_stack.redo() + + def undo(self): + """ Undo the last command of the active command stack. """ + + if self.active_stack is not None: + self.active_stack.undo() + + ########################################################################### + # Private interface. + ########################################################################### + + def _active_stack_changed(self, new): + """ Handle a different stack becoming active. """ + + # Pretend that the stack contents have changed. + self.stack_updated = new + + def _get_active_stack_clean(self): + """ Get the current clean state. """ + + if self.active_stack is None: + active_stack_clean = True + else: + active_stack_clean = self.active_stack.clean + + return active_stack_clean + + def _get_redo_name(self): + """ Get the current redo name. """ + + if self.active_stack is None: + redo_name = "" + else: + redo_name = self.active_stack.redo_name + + return redo_name + + def _get_undo_name(self): + """ Get the current undo name. """ + + if self.active_stack is None: + undo_name = "" + else: + undo_name = self.active_stack.undo_name + + return undo_name From 024e55afb43ac63ba57598c77a281b642f244377 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 08:30:14 -0600 Subject: [PATCH 02/14] copy over undo examples from apptools --- examples/undo/commands.py | 203 ++++++++++++++++++++++++ examples/undo/example.py | 85 ++++++++++ examples/undo/example_editor_manager.py | 156 ++++++++++++++++++ examples/undo/example_undo_window.py | 141 ++++++++++++++++ examples/undo/model.py | 46 ++++++ 5 files changed, 631 insertions(+) create mode 100644 examples/undo/commands.py create mode 100644 examples/undo/example.py create mode 100644 examples/undo/example_editor_manager.py create mode 100644 examples/undo/example_undo_window.py create mode 100644 examples/undo/model.py diff --git a/examples/undo/commands.py b/examples/undo/commands.py new file mode 100644 index 000000000..51c36ea18 --- /dev/null +++ b/examples/undo/commands.py @@ -0,0 +1,203 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ----------------------------------------------------------------------------- + + +# Enthought library imports. +from traits.api import Instance, Int, Str +from pyface.undo.api import AbstractCommand + +# Local imports. +from model import Label + + +class LabelIncrementSizeCommand(AbstractCommand): + """ The LabelIncrementSizeCommand class is a command that increases the + size of a label's text. This command will merge multiple increments + togther. + """ + + #### 'ICommand' interface ################################################# + + # The data being operated on. + data = Instance(Label) + + # The name of the command. + name = Str("&Increment size") + + #### Private interface #################################################### + + _incremented_by = Int + + ########################################################################### + # 'ICommand' interface. + ########################################################################### + + def do(self): + self.data.increment_size(1) + self._incremented_by = 1 + + def merge(self, other): + # We can merge if the other command is the same type (or a sub-type). + if isinstance(other, type(self)): + self._incremented_by += 1 + merged = True + else: + merged = False + + return merged + + def redo(self): + self.data.increment_size(self._incremented_by) + + def undo(self): + self.data.decrement_size(self._incremented_by) + + +class LabelDecrementSizeCommand(AbstractCommand): + """ The LabelDecrementSizeCommand class is a command that decreases the + size of a label's text. This command will merge multiple decrements + togther. + """ + + #### 'ICommand' interface ################################################# + + # The data being operated on. + data = Instance(Label) + + # The name of the command. + name = Str("&Decrement size") + + #### Private interface #################################################### + + _decremented_by = Int + + ########################################################################### + # 'ICommand' interface. + ########################################################################### + + def do(self): + self.data.decrement_size(1) + self._decremented_by = 1 + + def merge(self, other): + # We can merge if the other command is the same type (or a sub-type). + if isinstance(other, type(self)): + self._decremented_by += 1 + merged = True + else: + merged = False + + return merged + + def redo(self): + self.data.decrement_size(self._decremented_by) + + def undo(self): + self.data.increment_size(self._decremented_by) + + +class LabelNormalFontCommand(AbstractCommand): + """ The LabelNormalFontCommand class is a command that sets a normal font + for a label's text. + """ + + #### 'ICommand' interface ################################################# + + # The data being operated on. + data = Instance(Label) + + # The name of the command. + name = Str("&Normal font") + + ########################################################################### + # 'ICommand' interface. + ########################################################################### + + def do(self): + # Save the old value. + self._saved = self.data.style + + # Calling redo() is a convenient way to update the model now that the + # old value is saved. + self.redo() + + def redo(self): + self.data.style = 'normal' + + def undo(self): + self.data.style = self._saved + + +class LabelBoldFontCommand(AbstractCommand): + """ The LabelNormalFontCommand class is a command that sets a bold font for + a label's text. + """ + + #### 'ICommand' interface ############################################# + + # The data being operated on. + data = Instance(Label) + + # The name of the command. + name = Str("&Bold font") + + ########################################################################### + # 'ICommand' interface. + ########################################################################### + + def do(self): + # Save the old value. + self._saved = self.data.style + + # Calling redo() is a convenient way to update the model now that the + # old value is saved. + self.redo() + + def redo(self): + self.data.style = 'bold' + + def undo(self): + self.data.style = self._saved + + +class LabelItalicFontCommand(AbstractCommand): + """ The LabelNormalFontCommand class is a command that sets an italic font + for a label's text. + """ + + #### 'ICommand' interface ################################################# + + # The data being operated on. + data = Instance(Label) + + # The name of the command. + name = Str("&Italic font") + + ########################################################################### + # 'ICommand' interface. + ########################################################################### + + def do(self): + # Save the old value. + self._saved = self.data.style + + # Calling redo() is a convenient way to update the model now that the + # old value is saved. + self.redo() + + def redo(self): + self.data.style = 'italic' + + def undo(self): + self.data.style = self._saved diff --git a/examples/undo/example.py b/examples/undo/example.py new file mode 100644 index 000000000..6e3bc790b --- /dev/null +++ b/examples/undo/example.py @@ -0,0 +1,85 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2008, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ----------------------------------------------------------------------------- + + +# Standard library imports. +import logging + +# Enthought library imports. +from pyface.api import GUI, YES +from pyface.workbench.api import Workbench + +# Local imports. +from example_undo_window import ExampleUndoWindow +from model import Label + + +# Log to stderr. +logging.getLogger().addHandler(logging.StreamHandler()) +logging.getLogger().setLevel(logging.DEBUG) + + +class ExampleUndo(Workbench): + """ The ExampleUndo class is a workbench that creates ExampleUndoWindow + windows. + """ + + #### 'Workbench' interface ################################################ + + # The factory (in this case simply a class) that is used to create + # workbench windows. + window_factory = ExampleUndoWindow + + ########################################################################### + # Private interface. + ########################################################################### + + def _exiting_changed(self, event): + """ Called when the workbench is exiting. """ + + if self.active_window.confirm('Ok to exit?') != YES: + event.veto = True + + return + + +def main(argv): + """ A simple example of using the the undo framework in a workbench. """ + + # Create the GUI. + gui = GUI() + + # Create the workbench. + workbench = ExampleUndo(state_location=gui.state_location) + + window = workbench.create_window(position=(300, 300), size=(400, 300)) + window.open() + + # Create some objects to edit. + label = Label(text="Label") + label2 = Label(text="Label2") + + # Edit the objects. + window.edit(label) + window.edit(label2) + + # Start the GUI event loop. + gui.start_event_loop() + + return + + +if __name__ == '__main__': + import sys + main(sys.argv) diff --git a/examples/undo/example_editor_manager.py b/examples/undo/example_editor_manager.py new file mode 100644 index 000000000..078d996b6 --- /dev/null +++ b/examples/undo/example_editor_manager.py @@ -0,0 +1,156 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2007, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ----------------------------------------------------------------------------- + + +# Enthought library imports. +from traits.etsconfig.api import ETSConfig +from pyface.workbench.api import Editor, EditorManager + + +class _wxLabelEditor(Editor): + """ _wxLabelEditor is the wx implementation of a label editor. """ + + def create_control(self, parent): + import wx + + w = wx.TextCtrl(parent, style=wx.TE_RICH2) + style = w.GetDefaultStyle() + style.SetAlignment(wx.TEXT_ALIGNMENT_CENTER) + w.SetDefaultStyle(style) + + self._set_text(w) + self._set_size_and_style(w) + + self.obj.on_trait_change(self._update_text, 'text') + self.obj.on_trait_change(self._update_size, 'size') + self.obj.on_trait_change(self._update_style, 'style') + + return w + + def _name_default(self): + return self.obj.text + + def _update_text(self): + self._set_text(self.control) + + def _set_text(self, w): + w.SetValue("") + w.WriteText( + "%s(%d points, %s)" % ( + self.obj.text, + self.obj.size, + self.obj.style + ) + ) + + def _update_size(self): + self._set_size_and_style(self.control) + + def _update_style(self): + self._set_size_and_style(self.control) + + def _set_size_and_style(self, w): + import wx + if self.obj.style == 'normal': + style, weight = wx.NORMAL, wx.NORMAL + elif self.obj.style == 'italic': + style, weight = wx.ITALIC, wx.NORMAL + elif self.obj.style == 'bold': + style, weight = wx.NORMAL, wx.BOLD + else: + raise NotImplementedError( + "style '%s' not supported" % self.obj.style + ) + + f = wx.Font(self.obj.size, wx.ROMAN, style, weight, False) + style = wx.TextAttr("BLACK", wx.NullColour, f) + w.SetDefaultStyle(style) + self._set_text(w) + + +class _PyQt4LabelEditor(Editor): + """ _PyQt4LabelEditor is the PyQt implementation of a label editor. """ + + def create_control(self, parent): + + from pyface.qt import QtCore, QtGui + + w = QtGui.QLabel(parent) + w.setAlignment(QtCore.Qt.AlignCenter) + + self._set_text(w) + self._set_size(w) + self._set_style(w) + + self.obj.on_trait_change(self._update_text, 'text') + self.obj.on_trait_change(self._update_size, 'size') + self.obj.on_trait_change(self._update_style, 'style') + + return w + + def _name_default(self): + return self.obj.text + + def _update_text(self): + self._set_text(self.control) + + def _set_text(self, w): + w.setText( + "%s\n(%d points, %s)" % ( + self.obj.text, + self.obj.size, + self.obj.style + ) + ) + + def _update_size(self): + self._set_size(self.control) + + def _set_size(self, w): + f = w.font() + f.setPointSize(self.obj.size) + w.setFont(f) + + self._set_text(w) + + def _update_style(self): + self._set_style(self.control) + + def _set_style(self, w): + f = w.font() + f.setBold(self.obj.style == 'bold') + f.setItalic(self.obj.style == 'italic') + w.setFont(f) + + self._set_text(w) + + +class ExampleEditorManager(EditorManager): + """ The ExampleEditorManager class creates the example editors. """ + + def create_editor(self, window, obj, kind): + + # Create the toolkit specific editor. + tk_name = ETSConfig.toolkit + + if tk_name == 'wx': + ed = _wxLabelEditor(window=window, obj=obj) + elif tk_name == 'qt4' or tk_name == 'qt': + ed = _PyQt4LabelEditor(window=window, obj=obj) + else: + raise NotImplementedError( + "unsupported toolkit: %s" % tk_name + ) + + return ed diff --git a/examples/undo/example_undo_window.py b/examples/undo/example_undo_window.py new file mode 100644 index 000000000..3478829f2 --- /dev/null +++ b/examples/undo/example_undo_window.py @@ -0,0 +1,141 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2008, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ----------------------------------------------------------------------------- + + +# Enthought library imports. +from pyface.action.api import Action, Group, MenuManager +from pyface.workbench.api import WorkbenchWindow +from pyface.workbench.action.api import MenuBarManager, ToolBarManager +from traits.api import Instance +from pyface.undo.action.api import CommandAction, RedoAction, UndoAction + +# Local imports. +from example_editor_manager import ExampleEditorManager +from commands import LabelIncrementSizeCommand, LabelDecrementSizeCommand, \ + LabelNormalFontCommand, LabelBoldFontCommand, LabelItalicFontCommand + + +class ExampleUndoWindow(WorkbenchWindow): + """ The ExampleUndoWindow class is a workbench window that contains example + editors that demonstrate the use of the undo framework. + """ + + #### Private interface #################################################### + + # The action that exits the application. + _exit_action = Instance(Action) + + # The File menu. + _file_menu = Instance(MenuManager) + + # The Label menu. + _label_menu = Instance(MenuManager) + + # The Undo menu. + _undo_menu = Instance(MenuManager) + + ########################################################################### + # Private interface. + ########################################################################### + + #### Trait initialisers ################################################### + + def __file_menu_default(self): + """ Trait initialiser. """ + + return MenuManager(self._exit_action, name="&File") + + def __undo_menu_default(self): + """ Trait initialiser. """ + + undo_manager = self.workbench.undo_manager + + undo_action = UndoAction(undo_manager=undo_manager) + redo_action = RedoAction(undo_manager=undo_manager) + + return MenuManager(undo_action, redo_action, name="&Undo") + + def __label_menu_default(self): + """ Trait initialiser. """ + + size_group = Group(CommandAction(command=LabelIncrementSizeCommand), + CommandAction(command=LabelDecrementSizeCommand)) + + normal = CommandAction(id='normal', command=LabelNormalFontCommand, + style='radio', checked=True) + bold = CommandAction(id='bold', command=LabelBoldFontCommand, + style='radio') + italic = CommandAction(id='italic', command=LabelItalicFontCommand, + style='radio') + + style_group = Group(normal, bold, italic, id='style') + + return MenuManager(size_group, style_group, name="&Label") + + def __exit_action_default(self): + """ Trait initialiser. """ + + return Action(name="E&xit", on_perform=self.workbench.exit) + + def _editor_manager_default(self): + """ Trait initialiser. """ + + return ExampleEditorManager() + + def _menu_bar_manager_default(self): + """ Trait initialiser. """ + + return MenuBarManager( + self._file_menu, + self._label_menu, + self._undo_menu, + window=self + ) + + def _tool_bar_manager_default(self): + """ Trait initialiser. """ + + return ToolBarManager(self._exit_action, show_tool_names=False) + + def _active_editor_changed(self, old, new): + """ Trait handler. """ + + # Tell the undo manager about the new command stack. + if old is not None: + old.command_stack.undo_manager.active_stack = None + + if new is not None: + new.command_stack.undo_manager.active_stack = new.command_stack + + # Walk the label editor menu. + for grp in self._label_menu.groups: + for itm in grp.items: + action = itm.action + + # Enable the action and set the command stack and data if there + # is a new editor. + if new is not None: + action.enabled = True + action.command_stack = new.command_stack + action.data = new.obj + + # FIXME v3: We should just be able to check the menu option + # corresponding to the style trait - but that doesn't seem + # to uncheck the other options in the group. Even then the + # first switch to another editor doesn't update the menus + # (though subsequent ones do). + if grp.id == 'style': + action.checked = (action.data.style == action.id) + else: + action.enabled = False diff --git a/examples/undo/model.py b/examples/undo/model.py new file mode 100644 index 000000000..8bd21a1c5 --- /dev/null +++ b/examples/undo/model.py @@ -0,0 +1,46 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2008, Riverbank Computing Limited +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in enthought/LICENSE.txt and may be redistributed only +# under the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# Thanks for using Enthought open source! +# +# Author: Riverbank Computing Limited +# Description: +# ----------------------------------------------------------------------------- + + +# Enthought library imports. +from traits.api import Enum, HasTraits, Int, Str + + +class Label(HasTraits): + """The Label class implements the data model for a label.""" + + #### 'Label' interface #################################################### + + # The name. + name = Str + + # The size in points. + size = Int(18) + + # The style. + style = Enum('normal', 'bold', 'italic') + + ########################################################################### + # 'Label' interface. + ########################################################################### + + def increment_size(self, by): + """Increment the current font size.""" + + self.size += by + + def decrement_size(self, by): + """Decrement the current font size.""" + + self.size -= by From e6199b32170915fa560f9a671945b9a98ca8be38 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 08:37:09 -0600 Subject: [PATCH 03/14] copy over undo documentation from apptools --- docs/source/submodules.rst | 1 + docs/source/undo.rst | 275 +++++++++++++++++++++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 docs/source/undo.rst diff --git a/docs/source/submodules.rst b/docs/source/submodules.rst index b9e698805..4418f9aa6 100644 --- a/docs/source/submodules.rst +++ b/docs/source/submodules.rst @@ -8,3 +8,4 @@ Submodules Data View Fields Timers + Undo diff --git a/docs/source/undo.rst b/docs/source/undo.rst new file mode 100644 index 000000000..89dee28d6 --- /dev/null +++ b/docs/source/undo.rst @@ -0,0 +1,275 @@ +Undo Framework +============== + +The Undo Framework is a component of the Enthought Tool Suite that provides +developers with an API that implements the standard pattern for do/undo/redo +commands. + +The framework is completely configurable. Alternate implementations of all +major components can be provided if necessary. + + +Framework Concepts +------------------ + +The following are the concepts supported by the framework. + +- Command + + A command is an application defined operation that can be done (i.e. + executed), undone (i.e. reverted) and redone (i.e. repeated). + + A command operates on some data and maintains sufficient state to allow it to + revert or repeat a change to the data. + + Commands may be merged so that potentially long sequences of similar + commands (e.g. to add a character to some text) can be collapsed into a + single command (e.g. to add a word to some text). + +- Macro + + A macro is a sequence of commands that is treated as a single command when + being undone or redone. + +- Command Stack + + A command is done by pushing it onto a command stack. The last command can + be undone and redone by calling appropriate command stack methods. It is + also possible to move the stack's position to any point and the command stack + will ensure that commands are undone or redone as required. + + A command stack maintains a *clean* state which is updated as commands are + done and undone. It may be explicitly set, for example when the data being + manipulated by the commands is saved to disk. + + Canned PyFace actions are provided as wrappers around command stack methods + to implement common menu items. + +- Undo Manager + + An undo manager is responsible for one or more command stacks and maintains + a reference to the currently active stack. It provides convenience undo and + redo methods that operate on the currently active stack. + + An undo manager ensures that each command execution is allocated a unique + sequence number, irrespective of which command stack it is pushed to. Using + this it is possible to synchronise multiple command stacks and restore them + to a particular point in time. + + An undo manager will generate an event whenever the clean state of the active + stack changes. This can be used to maintain some sort of GUI status + indicator to tell the user that their data has been modified since it was + last saved. + +Typically an application will have one undo manager and one undo stack for +each data type that can be edited. However this is not a requirement: how the +command stack's in particular are organised and linked (with the user +manager's sequence number) can need careful thought so as not to confuse the +user - particularly in a plugin based application that may have many editors. + +To support this typical usage the PyFace ``Workbench`` class has an +``undo_manager`` trait and the PyFace ``Editor`` class has a ``command_stack`` +trait. Both are lazy loaded so can be completely ignored if they are not used. + + +API Overview +------------ + +This section gives a brief overview of the various classes implemented in the +framework. The complete API_ documentation is available as endo generated +HTML. + +The example_ application demonstrates all the major features of the framework. + + +UndoManager +........... + +The ``UndoManager`` class is the default implementation of the ``IUndoManager`` +interface. + +``active_stack`` + This trait is a reference to the currently active command stack and may be + None. Typically it is set when some sort of editor becomes active. + +``active_stack_clean`` + This boolean trait reflects the clean state of the currently active + command stack. It is intended to support a "document modified" indicator + in the GUI. It is maintained by the undo manager. + +``stack_updated`` + This event is fired when the index of a command stack is changed. A + reference to the stack is passed as an argument to the event and may not + be the currently active stack. + +``undo_name`` + This Str trait is the name of the command that can be undone, and will + be empty if there is no such command. It is maintained by the undo + manager. + +``redo_name`` + This Str trait is the name of the command that can be redone, and will + be empty if there is no such command. It is maintained by the undo + manager. + +``sequence_nr`` + This integer trait is the sequence number of the next command to be + executed. It is incremented immediately before a command's ``do()`` + method is called. A particular sequence number identifies the state of + all command stacks handled by the undo manager and allows those stacks to + be set to the point they were at at a particular point in time. In other + words, the sequence number allows otherwise independent command stacks to + be synchronised. + +``undo()`` + This method calls the ``undo()`` method of the last command on the active + command stack. + +``redo()`` + This method calls the ``redo()`` method of the last undone command on the + active command stack. + + +CommandStack +............ + +The ``CommandStack`` class is the default implementation of the +``ICommandStack`` interface. + +``clean`` + This boolean traits reflects the clean state of the command stack. Its + value changes as commands are executed, undone and redone. It may also be + explicitly set to mark the current stack position as being clean (when + data is saved to disk for example). + +``undo_name`` + This Str trait is the name of the command that can be undone, and will + be empty if there is no such command. It is maintained by the command + stack. + +``redo_name`` + This Str trait is the name of the command that can be redone, and will + be empty if there is no such command. It is maintained by the command + stack. + +``undo_manager`` + This trait is a reference to the undo manager that manages the command + stack. + +``push(command)`` + This method executes the given command by calling its ``do()`` method. + Any value returned by ``do()`` is returned by ``push()``. If the command + couldn't be merged with the previous one then it is saved on the command + stack. + +``undo(sequence_nr=0)`` + This method undoes the last command. If a sequence number is given then + all commands are undone up to an including the sequence number. + +``redo(sequence_nr=0)`` + This method redoes the last command and returns any result. If a sequence + number is given then all commands are redone up to an including the + sequence number and any result of the last of these is returned. + +``clear()`` + This method clears the command stack, without undoing or redoing any + commands, and leaves the stack in a clean state. It is typically used + when all changes to the data have been abandoned. + +``begin_macro(name)`` + This method begins a macro by creating an empty command with the given + name. The commands passed to all subsequent calls to ``push()`` will be + contained in the macro until the next call to ``end_macro()``. Macros may + be nested. The command stack is disabled (ie. nothing can be undone or + redone) while a macro is being created (ie. while there is an outstanding + ``end_macro()`` call). + +``end_macro()`` + This method ends the current macro. + + +ICommand +........ + +The ``ICommand`` interface defines the interface that must be implemented by +any undoable/redoable command. + +``data`` + This optional trait is a reference to the data object that the command + operates on. It is not used by the framework itself. + +``name`` + This Str trait is the name of the command as it will appear in any GUI + element (e.g. in the text of an undo and redo menu entry). It may include + ``&`` to indicate a keyboard shortcut which will be automatically removed + whenever it is inappropriate. + +``__init__(*args)`` + If the command takes arguments then the command must ensure that deep + copies should be made if appropriate. + +``do()`` + This method is called by a command stack to execute the command and to + return any result. The command must save any state necessary for the + ``undo()`` and ``redo()`` methods to work. It is guaranteed that this + will only ever be called once and that it will be called before any call + to ``undo()`` or ``redo()``. + +``undo()`` + This method is called by a command stack to undo the command. + +``redo()`` + This method is called by a command stack to redo the command and to return + any result. + +``merge(other)`` + This method is called by the command stack to try and merge the ``other`` + command with this one. True should be returned if the commands were + merged. If the commands are merged then ``other`` will not be placed on + the command stack. A subsequent undo or redo of this modified command + must have the same effect as the two original commands. + + +AbstractCommand +............... + +``AbstractCommand`` is an abstract base class that implements the ``ICommand`` +interface. It provides a default implementation of the ``merge()`` method. + + +CommandAction +............. + +The ``CommandAction`` class is a sub-class of the PyFace ``Action`` class that +is used to wrap commands. + +``command`` + This callable trait must be set to a factory that will return an object + that implements ``ICommand``. It will be called when the action is invoked + and the object created pushed onto the command stack. + +``command_stack`` + This instance trait must be set to the command stack that commands invoked + by the action are pushed to. + +``data`` + This optional trait is a reference to the data object that will be passed + to the ``command`` factory when it is called. + + +UndoAction +.......... + +The ``UndoAction`` class is a canned PyFace action that undoes the last +command of the active command stack. + + +RedoAction +.......... + +The ``RedoAction`` class is a canned PyFace action that redoes the last +command undone of the active command stack. + + +.. _API: api/index.html +.. _example: https://svn.enthought.com/enthought/browser/AppTools/trunk/examples/undo/ From 11e3f4b2cbbe5bc92a8557983c2a4714fb2cfe19 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 08:48:54 -0600 Subject: [PATCH 04/14] update copyright headers --- examples/undo/commands.py | 10 ++++++++++ examples/undo/example.py | 10 ++++++++++ examples/undo/example_editor_manager.py | 10 ++++++++++ examples/undo/example_undo_window.py | 10 ++++++++++ examples/undo/model.py | 10 ++++++++++ pyface/undo/__init__.py | 9 ++++----- pyface/undo/abstract_command.py | 10 ++++++++++ pyface/undo/action/__init__.py | 9 ++++----- .../action/abstract_command_stack_action.py | 10 ++++++++++ pyface/undo/action/api.py | 10 ++++++++++ pyface/undo/action/command_action.py | 10 ++++++++++ pyface/undo/action/redo_action.py | 10 ++++++++++ pyface/undo/action/undo_action.py | 10 ++++++++++ pyface/undo/command_stack.py | 10 ++++++++++ pyface/undo/i_command.py | 10 ++++++++++ pyface/undo/i_command_stack.py | 10 ++++++++++ pyface/undo/i_undo_manager.py | 10 ++++++++++ pyface/undo/tests/__init__.py | 9 +++++++++ pyface/undo/tests/test_command_stack.py | 18 +++++++----------- pyface/undo/tests/testing_commands.py | 18 +++++++----------- pyface/undo/undo_manager.py | 10 ++++++++++ 21 files changed, 191 insertions(+), 32 deletions(-) diff --git a/examples/undo/commands.py b/examples/undo/commands.py index 51c36ea18..630daadcb 100644 --- a/examples/undo/commands.py +++ b/examples/undo/commands.py @@ -1,3 +1,13 @@ +# (C) Copyright 2007-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ----------------------------------------------------------------------------- # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. diff --git a/examples/undo/example.py b/examples/undo/example.py index 6e3bc790b..bdc1e5513 100644 --- a/examples/undo/example.py +++ b/examples/undo/example.py @@ -1,3 +1,13 @@ +# (C) Copyright 2008-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ----------------------------------------------------------------------------- # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. diff --git a/examples/undo/example_editor_manager.py b/examples/undo/example_editor_manager.py index 078d996b6..8af9e0718 100644 --- a/examples/undo/example_editor_manager.py +++ b/examples/undo/example_editor_manager.py @@ -1,3 +1,13 @@ +# (C) Copyright 2007-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ----------------------------------------------------------------------------- # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. diff --git a/examples/undo/example_undo_window.py b/examples/undo/example_undo_window.py index 3478829f2..ebcfd74be 100644 --- a/examples/undo/example_undo_window.py +++ b/examples/undo/example_undo_window.py @@ -1,3 +1,13 @@ +# (C) Copyright 2008-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ----------------------------------------------------------------------------- # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. diff --git a/examples/undo/model.py b/examples/undo/model.py index 8bd21a1c5..c60176f12 100644 --- a/examples/undo/model.py +++ b/examples/undo/model.py @@ -1,3 +1,13 @@ +# (C) Copyright 2008-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ----------------------------------------------------------------------------- # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/__init__.py b/pyface/undo/__init__.py index 78fc4a3a2..b48c90bcb 100644 --- a/pyface/undo/__init__.py +++ b/pyface/undo/__init__.py @@ -1,13 +1,12 @@ -# Copyright (c) 2005-2011, Enthought, Inc. +# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD -# license included in LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! # -# Author: Enthought, Inc. +# Thanks for using Enthought open source! """ Supports undoing and scripting application commands. Part of the AppTools project of the Enthought Tool Suite. """ diff --git a/pyface/undo/abstract_command.py b/pyface/undo/abstract_command.py index c428ce42a..6744ce0f3 100644 --- a/pyface/undo/abstract_command.py +++ b/pyface/undo/abstract_command.py @@ -1,3 +1,13 @@ +# (C) Copyright 2007-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/action/__init__.py b/pyface/undo/action/__init__.py index bc54012db..6b94a8b23 100644 --- a/pyface/undo/action/__init__.py +++ b/pyface/undo/action/__init__.py @@ -1,10 +1,9 @@ -# Copyright (c) 2005-2011, Enthought, Inc. +# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX # All rights reserved. # # This software is provided without warranty under the terms of the BSD -# license included in LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt -# Thanks for using Enthought open source! # -# Author: Enthought, Inc. +# Thanks for using Enthought open source! diff --git a/pyface/undo/action/abstract_command_stack_action.py b/pyface/undo/action/abstract_command_stack_action.py index 46f30e830..fd5a7d68f 100644 --- a/pyface/undo/action/abstract_command_stack_action.py +++ b/pyface/undo/action/abstract_command_stack_action.py @@ -1,3 +1,13 @@ +# (C) Copyright 2008-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/action/api.py b/pyface/undo/action/api.py index 48d52295c..80e76269b 100644 --- a/pyface/undo/action/api.py +++ b/pyface/undo/action/api.py @@ -1,3 +1,13 @@ +# (C) Copyright 2007-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/action/command_action.py b/pyface/undo/action/command_action.py index 8684ca212..b77947875 100644 --- a/pyface/undo/action/command_action.py +++ b/pyface/undo/action/command_action.py @@ -1,3 +1,13 @@ +# (C) Copyright 2007-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/action/redo_action.py b/pyface/undo/action/redo_action.py index 4cb426e5e..3d5018141 100644 --- a/pyface/undo/action/redo_action.py +++ b/pyface/undo/action/redo_action.py @@ -1,3 +1,13 @@ +# (C) Copyright 2007-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/action/undo_action.py b/pyface/undo/action/undo_action.py index b6a273996..d76670703 100644 --- a/pyface/undo/action/undo_action.py +++ b/pyface/undo/action/undo_action.py @@ -1,3 +1,13 @@ +# (C) Copyright 2007-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/command_stack.py b/pyface/undo/command_stack.py index ca1cacc93..1b7210aa9 100644 --- a/pyface/undo/command_stack.py +++ b/pyface/undo/command_stack.py @@ -1,3 +1,13 @@ +# (C) Copyright 2008-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/i_command.py b/pyface/undo/i_command.py index ca7c2217b..f4d83f0ef 100644 --- a/pyface/undo/i_command.py +++ b/pyface/undo/i_command.py @@ -1,3 +1,13 @@ +# (C) Copyright 2007-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/i_command_stack.py b/pyface/undo/i_command_stack.py index 2c923fc5f..c3e7504ca 100644 --- a/pyface/undo/i_command_stack.py +++ b/pyface/undo/i_command_stack.py @@ -1,3 +1,13 @@ +# (C) Copyright 2007-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/i_undo_manager.py b/pyface/undo/i_undo_manager.py index ff15e7052..6ff4aaea5 100644 --- a/pyface/undo/i_undo_manager.py +++ b/pyface/undo/i_undo_manager.py @@ -1,3 +1,13 @@ +# (C) Copyright 2007-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. diff --git a/pyface/undo/tests/__init__.py b/pyface/undo/tests/__init__.py index e69de29bb..6b94a8b23 100644 --- a/pyface/undo/tests/__init__.py +++ b/pyface/undo/tests/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! diff --git a/pyface/undo/tests/test_command_stack.py b/pyface/undo/tests/test_command_stack.py index c564f3b04..4d1c1476c 100644 --- a/pyface/undo/tests/test_command_stack.py +++ b/pyface/undo/tests/test_command_stack.py @@ -1,16 +1,12 @@ -# ----------------------------------------------------------------------------- +# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX +# All rights reserved. # -# Copyright (c) 2015, Enthought, Inc. -# All rights reserved. +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt # -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# -# Thanks for using Enthought open source! -# -# ----------------------------------------------------------------------------- +# Thanks for using Enthought open source! from contextlib import contextmanager import unittest diff --git a/pyface/undo/tests/testing_commands.py b/pyface/undo/tests/testing_commands.py index 88517ed9c..7833f508a 100644 --- a/pyface/undo/tests/testing_commands.py +++ b/pyface/undo/tests/testing_commands.py @@ -1,16 +1,12 @@ -# ----------------------------------------------------------------------------- +# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX +# All rights reserved. # -# Copyright (c) 2015, Enthought, Inc. -# All rights reserved. +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt # -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# -# Thanks for using Enthought open source! -# -# ----------------------------------------------------------------------------- +# Thanks for using Enthought open source! from traits.api import Int from pyface.undo.api import AbstractCommand diff --git a/pyface/undo/undo_manager.py b/pyface/undo/undo_manager.py index ed6aa5740..885bc7ef1 100644 --- a/pyface/undo/undo_manager.py +++ b/pyface/undo/undo_manager.py @@ -1,3 +1,13 @@ +# (C) Copyright 2008-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + # ------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. From 025fbc5226ff5caeff62fc35b30e64aed12b32d7 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 08:51:53 -0600 Subject: [PATCH 05/14] follow convention of instantiating traits for trait definitions --- pyface/undo/abstract_command.py | 4 ++-- pyface/undo/action/command_action.py | 4 ++-- pyface/undo/command_stack.py | 2 +- pyface/undo/i_command.py | 4 ++-- pyface/undo/i_command_stack.py | 6 +++--- pyface/undo/i_undo_manager.py | 8 ++++---- pyface/undo/tests/testing_commands.py | 2 +- pyface/undo/undo_manager.py | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pyface/undo/abstract_command.py b/pyface/undo/abstract_command.py index 6744ce0f3..165ec333a 100644 --- a/pyface/undo/abstract_command.py +++ b/pyface/undo/abstract_command.py @@ -38,12 +38,12 @@ class AbstractCommand(HasTraits): #### 'ICommand' interface ################################################# # This is the data on which the command operates. - data = Any + data = Any() # This is the name of the command as it will appear in any GUI element. It # may include '&' which will be automatically removed whenever it is # inappropriate. - name = Str + name = Str() ########################################################################### # 'ICommand' interface. diff --git a/pyface/undo/action/command_action.py b/pyface/undo/action/command_action.py index b77947875..eac4c2cea 100644 --- a/pyface/undo/action/command_action.py +++ b/pyface/undo/action/command_action.py @@ -37,14 +37,14 @@ class CommandAction(Action): #### 'CommandAction' interface ############################################ # The command to create when the action is performed. - command = Callable + command = Callable() # The command stack onto which the command will be pushed when the action # is performed. command_stack = Instance(ICommandStack) # This is the data on which the command operates. - data = Any + data = Any() ########################################################################### # 'Action' interface. diff --git a/pyface/undo/command_stack.py b/pyface/undo/command_stack.py index 1b7210aa9..5f6f1abb6 100644 --- a/pyface/undo/command_stack.py +++ b/pyface/undo/command_stack.py @@ -53,7 +53,7 @@ class _StackEntry(HasTraits): command = Instance(ICommand) # The sequence number of the entry. - sequence_nr = Int + sequence_nr = Int() class _MacroCommand(AbstractCommand): diff --git a/pyface/undo/i_command.py b/pyface/undo/i_command.py index f4d83f0ef..8dfb7623d 100644 --- a/pyface/undo/i_command.py +++ b/pyface/undo/i_command.py @@ -36,12 +36,12 @@ class ICommand(Interface): #### 'ICommand' interface ################################################# # This is the data on which the command operates. - data = Any + data = Any() # This is the name of the command as it will appear in any GUI element. It # may include '&' which will be automatically removed whenever it is # inappropriate. - name = Str + name = Str() ########################################################################### # 'ICommand' interface. diff --git a/pyface/undo/i_command_stack.py b/pyface/undo/i_command_stack.py index c3e7504ca..ca0117678 100644 --- a/pyface/undo/i_command_stack.py +++ b/pyface/undo/i_command_stack.py @@ -41,12 +41,12 @@ class ICommandStack(Interface): # undone and redone. It can also be explicity set to mark the current # stack position as being clean (when the data is saved to disk for # example). - clean = Bool + clean = Bool() # This is the name of the command that can be redone. It will be empty if # there is no command that can be redone. It is maintained by the undo # stack. - redo_name = Str + redo_name = Str() # This is the undo manager that manages this stack. undo_manager = Instance(IUndoManager) @@ -54,7 +54,7 @@ class ICommandStack(Interface): # This is the name of the command that can be undone. It will be empty if # there is no command that can be undone. It is maintained by the undo # stack. - undo_name = Str + undo_name = Str() ########################################################################### # 'ICommandStack' interface. diff --git a/pyface/undo/i_undo_manager.py b/pyface/undo/i_undo_manager.py index 6ff4aaea5..048516fd1 100644 --- a/pyface/undo/i_undo_manager.py +++ b/pyface/undo/i_undo_manager.py @@ -41,17 +41,17 @@ class IUndoManager(Interface): # This reflects the clean state of the currently active command stack. It # is intended to support a "document modified" indicator in the GUI. It is # maintained by the undo manager. - active_stack_clean = Bool + active_stack_clean = Bool() # This is the name of the command that can be redone. It will be empty if # there is no command that can be redone. It is maintained by the undo # manager. - redo_name = Str + redo_name = Str() # This is the sequence number of the next command to be performed. It is # incremented immediately before a command is invoked (by its 'do()' # method). - sequence_nr = Int + sequence_nr = Int() # This event is fired when the index of a command stack changes. Note that # it may not be the active stack. @@ -60,7 +60,7 @@ class IUndoManager(Interface): # This is the name of the command that can be undone. It will be empty if # there is no command that can be undone. It is maintained by the undo # manager. - undo_name = Str + undo_name = Str() ########################################################################### # 'IUndoManager' interface. diff --git a/pyface/undo/tests/testing_commands.py b/pyface/undo/tests/testing_commands.py index 7833f508a..f1f9b2c81 100644 --- a/pyface/undo/tests/testing_commands.py +++ b/pyface/undo/tests/testing_commands.py @@ -17,7 +17,7 @@ class SimpleCommand(AbstractCommand): name = "Increment by 1" - data = Int + data = Int() def do(self): self.redo() diff --git a/pyface/undo/undo_manager.py b/pyface/undo/undo_manager.py index 885bc7ef1..8c9d1f116 100644 --- a/pyface/undo/undo_manager.py +++ b/pyface/undo/undo_manager.py @@ -63,12 +63,12 @@ class UndoManager(HasTraits): # This is the sequence number of the next command to be performed. It is # incremented immediately before a command is invoked (by its 'do()' # method). - sequence_nr = Int + sequence_nr = Int() # This event is fired when the index of a command stack changes. The value # of the event is the stack that has changed. Note that it may not be the # active stack. - stack_updated = Event + stack_updated = Event() # This is the name of the command that can be undone. It will be empty if # there is no command that can be undone. It is maintained by the undo From 61ffe418a8173355e5cbe07bad037cab8c879700 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 08:59:06 -0600 Subject: [PATCH 06/14] add comment about circular dependency workaround --- pyface/undo/i_undo_manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyface/undo/i_undo_manager.py b/pyface/undo/i_undo_manager.py index 048516fd1..2b8ab3ee9 100644 --- a/pyface/undo/i_undo_manager.py +++ b/pyface/undo/i_undo_manager.py @@ -36,6 +36,8 @@ class IUndoManager(Interface): # This is the currently active command stack and may be None. Typically it # is set when some sort of editor becomes active. + # IUndoManager and ICommandStack depend on one another, hence we can't + # directly import ICommandStack and use it here. active_stack = Instance("apptools.undo.api.ICommandStack") # This reflects the clean state of the currently active command stack. It From eba2f19e946a4a8f3193f5c06929c7cc77fd524f Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 11:04:57 -0600 Subject: [PATCH 07/14] add #: to trait definitions comments so traits come up with autogenerated api docs --- pyface/undo/abstract_command.py | 8 ++--- .../action/abstract_command_stack_action.py | 2 +- pyface/undo/action/command_action.py | 8 ++--- pyface/undo/command_stack.py | 30 ++++++++-------- pyface/undo/i_command.py | 8 ++--- pyface/undo/i_command_stack.py | 22 ++++++------ pyface/undo/i_undo_manager.py | 36 +++++++++---------- pyface/undo/undo_manager.py | 34 +++++++++--------- 8 files changed, 74 insertions(+), 74 deletions(-) diff --git a/pyface/undo/abstract_command.py b/pyface/undo/abstract_command.py index 165ec333a..8e18da045 100644 --- a/pyface/undo/abstract_command.py +++ b/pyface/undo/abstract_command.py @@ -37,12 +37,12 @@ class AbstractCommand(HasTraits): #### 'ICommand' interface ################################################# - # This is the data on which the command operates. + #: This is the data on which the command operates. data = Any() - # This is the name of the command as it will appear in any GUI element. It - # may include '&' which will be automatically removed whenever it is - # inappropriate. + #: This is the name of the command as it will appear in any GUI element. It + #: may include '&' which will be automatically removed whenever it is + #: inappropriate. name = Str() ########################################################################### diff --git a/pyface/undo/action/abstract_command_stack_action.py b/pyface/undo/action/abstract_command_stack_action.py index fd5a7d68f..cefbd57fe 100644 --- a/pyface/undo/action/abstract_command_stack_action.py +++ b/pyface/undo/action/abstract_command_stack_action.py @@ -37,7 +37,7 @@ class AbstractCommandStackAction(Action): #### 'AbstractCommandStackAction' interface ############################### - # The undo manager. + #: The undo manager. undo_manager = Instance(IUndoManager) ########################################################################### diff --git a/pyface/undo/action/command_action.py b/pyface/undo/action/command_action.py index eac4c2cea..17eb6d19e 100644 --- a/pyface/undo/action/command_action.py +++ b/pyface/undo/action/command_action.py @@ -36,14 +36,14 @@ class CommandAction(Action): #### 'CommandAction' interface ############################################ - # The command to create when the action is performed. + #: The command to create when the action is performed. command = Callable() - # The command stack onto which the command will be pushed when the action - # is performed. + #: The command stack onto which the command will be pushed when the action + #: is performed. command_stack = Instance(ICommandStack) - # This is the data on which the command operates. + #: This is the data on which the command operates. data = Any() ########################################################################### diff --git a/pyface/undo/command_stack.py b/pyface/undo/command_stack.py index 5f6f1abb6..78abd2612 100644 --- a/pyface/undo/command_stack.py +++ b/pyface/undo/command_stack.py @@ -46,13 +46,13 @@ class _StackEntry(HasTraits): #### '_StackEntry' interface ############################################## - # Set if the entry corresponds to a clean point on the stack. + #: Set if the entry corresponds to a clean point on the stack. clean = Bool(False) - # The command instance. + #: The command instance. command = Instance(ICommand) - # The sequence number of the entry. + #: The sequence number of the entry. sequence_nr = Int() @@ -61,7 +61,7 @@ class _MacroCommand(AbstractCommand): #### '_MacroCommand' interface ############################################ - # The commands that make up this macro. + #: The commands that make up this macro. macro_commands = List(Instance(ICommand)) ########################################################################### @@ -108,23 +108,23 @@ class CommandStack(HasTraits): #### 'ICommandStack' interface ############################################ - # This is the clean state of the stack. Its value changes as commands are - # undone and redone. It can also be explicity set to mark the current - # stack position as being clean (when the data is saved to disk for - # example). + #: This is the clean state of the stack. Its value changes as commands are + #: undone and redone. It can also be explicity set to mark the current + #: stack position as being clean (when the data is saved to disk for + #: example). clean = Property(Bool) - # This is the name of the command that can be redone. It will be empty if - # there is no command that can be redone. It is maintained by the undo - # stack. + #: This is the name of the command that can be redone. It will be empty if + #: there is no command that can be redone. It is maintained by the undo + #: stack. redo_name = Property(Str) - # This is the undo manager that manages this stack. + #: This is the undo manager that manages this stack. undo_manager = Instance(IUndoManager) - # This is the name of the command that can be undone. It will be empty if - # there is no command that can be undone. It is maintained by the undo - # stack. + #: This is the name of the command that can be undone. It will be empty if + #: there is no command that can be undone. It is maintained by the undo + #: stack. undo_name = Property(Str) #### Private interface #################################################### diff --git a/pyface/undo/i_command.py b/pyface/undo/i_command.py index 8dfb7623d..3dbe9c684 100644 --- a/pyface/undo/i_command.py +++ b/pyface/undo/i_command.py @@ -35,12 +35,12 @@ class ICommand(Interface): #### 'ICommand' interface ################################################# - # This is the data on which the command operates. + #: This is the data on which the command operates. data = Any() - # This is the name of the command as it will appear in any GUI element. It - # may include '&' which will be automatically removed whenever it is - # inappropriate. + #: This is the name of the command as it will appear in any GUI element. It + #: may include '&' which will be automatically removed whenever it is + #: inappropriate. name = Str() ########################################################################### diff --git a/pyface/undo/i_command_stack.py b/pyface/undo/i_command_stack.py index ca0117678..d5aa3ed17 100644 --- a/pyface/undo/i_command_stack.py +++ b/pyface/undo/i_command_stack.py @@ -37,23 +37,23 @@ class ICommandStack(Interface): #### 'ICommandStack' interface ############################################ - # This is the clean state of the stack. Its value changes as commands are - # undone and redone. It can also be explicity set to mark the current - # stack position as being clean (when the data is saved to disk for - # example). + #: This is the clean state of the stack. Its value changes as commands are + #: undone and redone. It can also be explicity set to mark the current + #: stack position as being clean (when the data is saved to disk for + #: example). clean = Bool() - # This is the name of the command that can be redone. It will be empty if - # there is no command that can be redone. It is maintained by the undo - # stack. + #: This is the name of the command that can be redone. It will be empty if + #: there is no command that can be redone. It is maintained by the undo + #: stack. redo_name = Str() - # This is the undo manager that manages this stack. + #: This is the undo manager that manages this stack. undo_manager = Instance(IUndoManager) - # This is the name of the command that can be undone. It will be empty if - # there is no command that can be undone. It is maintained by the undo - # stack. + #: This is the name of the command that can be undone. It will be empty if + #: there is no command that can be undone. It is maintained by the undo + #: stack. undo_name = Str() ########################################################################### diff --git a/pyface/undo/i_undo_manager.py b/pyface/undo/i_undo_manager.py index 2b8ab3ee9..70e1b7fdd 100644 --- a/pyface/undo/i_undo_manager.py +++ b/pyface/undo/i_undo_manager.py @@ -34,34 +34,34 @@ class IUndoManager(Interface): #### 'IUndoManager' interface ############################################# - # This is the currently active command stack and may be None. Typically it - # is set when some sort of editor becomes active. - # IUndoManager and ICommandStack depend on one another, hence we can't - # directly import ICommandStack and use it here. + #: This is the currently active command stack and may be None. Typically it + #: is set when some sort of editor becomes active. + #: IUndoManager and ICommandStack depend on one another, hence we can't + #: directly import ICommandStack and use it here. active_stack = Instance("apptools.undo.api.ICommandStack") - # This reflects the clean state of the currently active command stack. It - # is intended to support a "document modified" indicator in the GUI. It is - # maintained by the undo manager. + #: This reflects the clean state of the currently active command stack. It + #: is intended to support a "document modified" indicator in the GUI. It is + #: maintained by the undo manager. active_stack_clean = Bool() - # This is the name of the command that can be redone. It will be empty if - # there is no command that can be redone. It is maintained by the undo - # manager. + #: This is the name of the command that can be redone. It will be empty if + #: there is no command that can be redone. It is maintained by the undo + #: manager. redo_name = Str() - # This is the sequence number of the next command to be performed. It is - # incremented immediately before a command is invoked (by its 'do()' - # method). + #: This is the sequence number of the next command to be performed. It is + #: incremented immediately before a command is invoked (by its 'do()' + #: method). sequence_nr = Int() - # This event is fired when the index of a command stack changes. Note that - # it may not be the active stack. + #: This event is fired when the index of a command stack changes. Note that + #: it may not be the active stack. stack_updated = Event(Instance("apptools.undo.api.ICommandStack")) - # This is the name of the command that can be undone. It will be empty if - # there is no command that can be undone. It is maintained by the undo - # manager. + #: This is the name of the command that can be undone. It will be empty if + #: there is no command that can be undone. It is maintained by the undo + #: manager. undo_name = Str() ########################################################################### diff --git a/pyface/undo/undo_manager.py b/pyface/undo/undo_manager.py index 8c9d1f116..8a47e8bd7 100644 --- a/pyface/undo/undo_manager.py +++ b/pyface/undo/undo_manager.py @@ -46,33 +46,33 @@ class UndoManager(HasTraits): #### 'IUndoManager' interface ############################################# - # This is the currently active command stack and may be None. Typically it - # is set when some sort of editor becomes active. + #: This is the currently active command stack and may be None. Typically it + #: is set when some sort of editor becomes active. active_stack = Instance("apptools.undo.api.ICommandStack") - # This reflects the clean state of the currently active command stack. It - # is intended to support a "document modified" indicator in the GUI. It is - # maintained by the undo manager. + #: This reflects the clean state of the currently active command stack. It + #: is intended to support a "document modified" indicator in the GUI. It is + #: maintained by the undo manager. active_stack_clean = Property(Bool) - # This is the name of the command that can be redone. It will be empty if - # there is no command that can be redone. It is maintained by the undo - # manager. + #: This is the name of the command that can be redone. It will be empty if + #: there is no command that can be redone. It is maintained by the undo + #: manager. redo_name = Property(Str) - # This is the sequence number of the next command to be performed. It is - # incremented immediately before a command is invoked (by its 'do()' - # method). + #: This is the sequence number of the next command to be performed. It is + #: incremented immediately before a command is invoked (by its 'do()' + #: method). sequence_nr = Int() - # This event is fired when the index of a command stack changes. The value - # of the event is the stack that has changed. Note that it may not be the - # active stack. + #: This event is fired when the index of a command stack changes. The value + #: of the event is the stack that has changed. Note that it may not be the + #: active stack. stack_updated = Event() - # This is the name of the command that can be undone. It will be empty if - # there is no command that can be undone. It is maintained by the undo - # manager. + #: This is the name of the command that can be undone. It will be empty if + #: there is no command that can be undone. It is maintained by the undo + #: manager. undo_name = Property(Str) ########################################################################### From 244c6be1c4cdbf09f327772c5537b177619fad0b Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 11:07:55 -0600 Subject: [PATCH 08/14] remove unneeded redundant portion of docs mentioning API overview (this is all available in auto generated api docs). Also remove broken links --- docs/source/undo.rst | 203 ------------------------------------------- 1 file changed, 203 deletions(-) diff --git a/docs/source/undo.rst b/docs/source/undo.rst index 89dee28d6..e28a9d2a3 100644 --- a/docs/source/undo.rst +++ b/docs/source/undo.rst @@ -70,206 +70,3 @@ user - particularly in a plugin based application that may have many editors. To support this typical usage the PyFace ``Workbench`` class has an ``undo_manager`` trait and the PyFace ``Editor`` class has a ``command_stack`` trait. Both are lazy loaded so can be completely ignored if they are not used. - - -API Overview ------------- - -This section gives a brief overview of the various classes implemented in the -framework. The complete API_ documentation is available as endo generated -HTML. - -The example_ application demonstrates all the major features of the framework. - - -UndoManager -........... - -The ``UndoManager`` class is the default implementation of the ``IUndoManager`` -interface. - -``active_stack`` - This trait is a reference to the currently active command stack and may be - None. Typically it is set when some sort of editor becomes active. - -``active_stack_clean`` - This boolean trait reflects the clean state of the currently active - command stack. It is intended to support a "document modified" indicator - in the GUI. It is maintained by the undo manager. - -``stack_updated`` - This event is fired when the index of a command stack is changed. A - reference to the stack is passed as an argument to the event and may not - be the currently active stack. - -``undo_name`` - This Str trait is the name of the command that can be undone, and will - be empty if there is no such command. It is maintained by the undo - manager. - -``redo_name`` - This Str trait is the name of the command that can be redone, and will - be empty if there is no such command. It is maintained by the undo - manager. - -``sequence_nr`` - This integer trait is the sequence number of the next command to be - executed. It is incremented immediately before a command's ``do()`` - method is called. A particular sequence number identifies the state of - all command stacks handled by the undo manager and allows those stacks to - be set to the point they were at at a particular point in time. In other - words, the sequence number allows otherwise independent command stacks to - be synchronised. - -``undo()`` - This method calls the ``undo()`` method of the last command on the active - command stack. - -``redo()`` - This method calls the ``redo()`` method of the last undone command on the - active command stack. - - -CommandStack -............ - -The ``CommandStack`` class is the default implementation of the -``ICommandStack`` interface. - -``clean`` - This boolean traits reflects the clean state of the command stack. Its - value changes as commands are executed, undone and redone. It may also be - explicitly set to mark the current stack position as being clean (when - data is saved to disk for example). - -``undo_name`` - This Str trait is the name of the command that can be undone, and will - be empty if there is no such command. It is maintained by the command - stack. - -``redo_name`` - This Str trait is the name of the command that can be redone, and will - be empty if there is no such command. It is maintained by the command - stack. - -``undo_manager`` - This trait is a reference to the undo manager that manages the command - stack. - -``push(command)`` - This method executes the given command by calling its ``do()`` method. - Any value returned by ``do()`` is returned by ``push()``. If the command - couldn't be merged with the previous one then it is saved on the command - stack. - -``undo(sequence_nr=0)`` - This method undoes the last command. If a sequence number is given then - all commands are undone up to an including the sequence number. - -``redo(sequence_nr=0)`` - This method redoes the last command and returns any result. If a sequence - number is given then all commands are redone up to an including the - sequence number and any result of the last of these is returned. - -``clear()`` - This method clears the command stack, without undoing or redoing any - commands, and leaves the stack in a clean state. It is typically used - when all changes to the data have been abandoned. - -``begin_macro(name)`` - This method begins a macro by creating an empty command with the given - name. The commands passed to all subsequent calls to ``push()`` will be - contained in the macro until the next call to ``end_macro()``. Macros may - be nested. The command stack is disabled (ie. nothing can be undone or - redone) while a macro is being created (ie. while there is an outstanding - ``end_macro()`` call). - -``end_macro()`` - This method ends the current macro. - - -ICommand -........ - -The ``ICommand`` interface defines the interface that must be implemented by -any undoable/redoable command. - -``data`` - This optional trait is a reference to the data object that the command - operates on. It is not used by the framework itself. - -``name`` - This Str trait is the name of the command as it will appear in any GUI - element (e.g. in the text of an undo and redo menu entry). It may include - ``&`` to indicate a keyboard shortcut which will be automatically removed - whenever it is inappropriate. - -``__init__(*args)`` - If the command takes arguments then the command must ensure that deep - copies should be made if appropriate. - -``do()`` - This method is called by a command stack to execute the command and to - return any result. The command must save any state necessary for the - ``undo()`` and ``redo()`` methods to work. It is guaranteed that this - will only ever be called once and that it will be called before any call - to ``undo()`` or ``redo()``. - -``undo()`` - This method is called by a command stack to undo the command. - -``redo()`` - This method is called by a command stack to redo the command and to return - any result. - -``merge(other)`` - This method is called by the command stack to try and merge the ``other`` - command with this one. True should be returned if the commands were - merged. If the commands are merged then ``other`` will not be placed on - the command stack. A subsequent undo or redo of this modified command - must have the same effect as the two original commands. - - -AbstractCommand -............... - -``AbstractCommand`` is an abstract base class that implements the ``ICommand`` -interface. It provides a default implementation of the ``merge()`` method. - - -CommandAction -............. - -The ``CommandAction`` class is a sub-class of the PyFace ``Action`` class that -is used to wrap commands. - -``command`` - This callable trait must be set to a factory that will return an object - that implements ``ICommand``. It will be called when the action is invoked - and the object created pushed onto the command stack. - -``command_stack`` - This instance trait must be set to the command stack that commands invoked - by the action are pushed to. - -``data`` - This optional trait is a reference to the data object that will be passed - to the ``command`` factory when it is called. - - -UndoAction -.......... - -The ``UndoAction`` class is a canned PyFace action that undoes the last -command of the active command stack. - - -RedoAction -.......... - -The ``RedoAction`` class is a canned PyFace action that redoes the last -command undone of the active command stack. - - -.. _API: api/index.html -.. _example: https://svn.enthought.com/enthought/browser/AppTools/trunk/examples/undo/ From 8654a7ba8ca0134d61a24e12e9643f8f97b78003 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 11:44:52 -0600 Subject: [PATCH 09/14] getting the undo example working --- examples/undo/example.py | 6 +++++- examples/undo/example_undo_window.py | 3 ++- pyface/undo/i_undo_manager.py | 4 ++-- pyface/undo/undo_manager.py | 2 +- pyface/workbench/i_editor.py | 6 +++--- pyface/workbench/i_workbench.py | 2 +- pyface/workbench/workbench.py | 4 ++-- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/examples/undo/example.py b/examples/undo/example.py index bdc1e5513..7062b71bf 100644 --- a/examples/undo/example.py +++ b/examples/undo/example.py @@ -29,6 +29,7 @@ # Enthought library imports. from pyface.api import GUI, YES from pyface.workbench.api import Workbench +from pyface.undo.api import UndoManager # Local imports. from example_undo_window import ExampleUndoWindow @@ -71,7 +72,10 @@ def main(argv): gui = GUI() # Create the workbench. - workbench = ExampleUndo(state_location=gui.state_location) + workbench = ExampleUndo( + state_location=gui.state_location, + undo_manager=UndoManager() + ) window = workbench.create_window(position=(300, 300), size=(400, 300)) window.open() diff --git a/examples/undo/example_undo_window.py b/examples/undo/example_undo_window.py index ebcfd74be..e07b40ea2 100644 --- a/examples/undo/example_undo_window.py +++ b/examples/undo/example_undo_window.py @@ -68,7 +68,8 @@ def __file_menu_default(self): def __undo_menu_default(self): """ Trait initialiser. """ - + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + print(self.workbench) undo_manager = self.workbench.undo_manager undo_action = UndoAction(undo_manager=undo_manager) diff --git a/pyface/undo/i_undo_manager.py b/pyface/undo/i_undo_manager.py index 70e1b7fdd..8f7208890 100644 --- a/pyface/undo/i_undo_manager.py +++ b/pyface/undo/i_undo_manager.py @@ -38,7 +38,7 @@ class IUndoManager(Interface): #: is set when some sort of editor becomes active. #: IUndoManager and ICommandStack depend on one another, hence we can't #: directly import ICommandStack and use it here. - active_stack = Instance("apptools.undo.api.ICommandStack") + active_stack = Instance("pyface.undo.api.ICommandStack") #: This reflects the clean state of the currently active command stack. It #: is intended to support a "document modified" indicator in the GUI. It is @@ -57,7 +57,7 @@ class IUndoManager(Interface): #: This event is fired when the index of a command stack changes. Note that #: it may not be the active stack. - stack_updated = Event(Instance("apptools.undo.api.ICommandStack")) + stack_updated = Event(Instance("pyface.undo.api.ICommandStack")) #: This is the name of the command that can be undone. It will be empty if #: there is no command that can be undone. It is maintained by the undo diff --git a/pyface/undo/undo_manager.py b/pyface/undo/undo_manager.py index 8a47e8bd7..b38aa07ee 100644 --- a/pyface/undo/undo_manager.py +++ b/pyface/undo/undo_manager.py @@ -48,7 +48,7 @@ class UndoManager(HasTraits): #: This is the currently active command stack and may be None. Typically it #: is set when some sort of editor becomes active. - active_stack = Instance("apptools.undo.api.ICommandStack") + active_stack = Instance("pyface.undo.api.ICommandStack") #: This reflects the clean state of the currently active command stack. It #: is intended to support a "document modified" indicator in the GUI. It is diff --git a/pyface/workbench/i_editor.py b/pyface/workbench/i_editor.py index 316c6103a..22b83baad 100755 --- a/pyface/workbench/i_editor.py +++ b/pyface/workbench/i_editor.py @@ -33,7 +33,7 @@ class IEditor(IWorkbenchPart): """ The interface of a workbench editor. """ # The optional command stack. - command_stack = Instance("apptools.undo.api.ICommandStack") + command_stack = Instance("pyface.undo.api.ICommandStack") # Is the object that the editor is editing 'dirty' i.e., has it been # modified but not saved? @@ -71,7 +71,7 @@ class MEditor(MWorkbenchPart): # 'IEditor' interface -------------------------------------------------# # The optional command stack. - command_stack = Instance("apptools.undo.api.ICommandStack") + command_stack = Instance("pyface.undo.api.ICommandStack") # Is the object that the editor is editing 'dirty' i.e., has it been # modified but not saved? @@ -140,7 +140,7 @@ def _command_stack_default(self): # We make sure the undo package is entirely optional. try: - from apptools.undo.api import CommandStack + from pyface.undo.api import CommandStack except ImportError: return None diff --git a/pyface/workbench/i_workbench.py b/pyface/workbench/i_workbench.py index 188abdeda..1f7e166dc 100644 --- a/pyface/workbench/i_workbench.py +++ b/pyface/workbench/i_workbench.py @@ -35,7 +35,7 @@ class IWorkbench(Interface): state_location = Str() # The optional undo manager. - undo_manager = Instance("apptools.undo.api.IUndoManager") + undo_manager = Instance("pyface.undo.api.IUndoManager") # The user defined perspectives manager. user_perspective_manager = Instance(UserPerspectiveManager) diff --git a/pyface/workbench/workbench.py b/pyface/workbench/workbench.py index 97489ccf5..9500f0647 100755 --- a/pyface/workbench/workbench.py +++ b/pyface/workbench/workbench.py @@ -58,7 +58,7 @@ class Workbench(HasTraits): state_location = Str() # The optional undo manager. - undo_manager = Instance("apptools.undo.api.IUndoManager") + undo_manager = Instance("pyface.undo.api.IUndoManager") # The user-defined perspectives manager. user_perspective_manager = Instance(UserPerspectiveManager) @@ -266,7 +266,7 @@ def _undo_manager_default(self): # We make sure the undo package is entirely optional. try: - from apptools.undo.api import UndoManager + from pyface.undo.api import UndoManager except ImportError: return None From 3e16b6165bf0e757feafa3a33c6b8a79687ebaef Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 11:48:25 -0600 Subject: [PATCH 10/14] undoing an unneeded change --- examples/undo/example.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/undo/example.py b/examples/undo/example.py index 7062b71bf..a050dc037 100644 --- a/examples/undo/example.py +++ b/examples/undo/example.py @@ -72,10 +72,7 @@ def main(argv): gui = GUI() # Create the workbench. - workbench = ExampleUndo( - state_location=gui.state_location, - undo_manager=UndoManager() - ) + workbench = ExampleUndo(state_location=gui.state_location) window = workbench.create_window(position=(300, 300), size=(400, 300)) window.open() From 8fcedc567bef944208b16ddc5a1352f56ab87072 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 14:45:50 -0600 Subject: [PATCH 11/14] adding more tests --- examples/undo/example_undo_window.py | 2 - pyface/undo/tests/test_command_stack.py | 20 +++++++ pyface/undo/tests/test_undo_manager.py | 72 +++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 pyface/undo/tests/test_undo_manager.py diff --git a/examples/undo/example_undo_window.py b/examples/undo/example_undo_window.py index e07b40ea2..1abb315a7 100644 --- a/examples/undo/example_undo_window.py +++ b/examples/undo/example_undo_window.py @@ -68,8 +68,6 @@ def __file_menu_default(self): def __undo_menu_default(self): """ Trait initialiser. """ - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!') - print(self.workbench) undo_manager = self.workbench.undo_manager undo_action = UndoAction(undo_manager=undo_manager) diff --git a/pyface/undo/tests/test_command_stack.py b/pyface/undo/tests/test_command_stack.py index 4d1c1476c..9e52ede94 100644 --- a/pyface/undo/tests/test_command_stack.py +++ b/pyface/undo/tests/test_command_stack.py @@ -56,6 +56,18 @@ def test_undo_n_command(self): for i in range(n): self.stack.undo() + def test_undo_redo_sequence_nr(self): + n = 4 + for i in range(n): + self.stack.push(self.command) + self.assertEqual(self.stack._index, 3) + # undo back to the 1st command in the stack + self.stack.undo(1) + self.assertEqual(self.stack._index, 0) + # redo back to the 3rd command in the stack + self.stack.redo(3) + self.assertEqual(self.stack._index, 2) + def test_undo_unnamed_command(self): unnamed_command = UnnamedCommand() with self.assert_n_commands_pushed(self.stack, 1): @@ -132,6 +144,14 @@ def test_push_undo_save_redo_is_dirty(self): self.stack.redo() self.assertFalse(self.stack.clean) + def test_clear(self): + n = 5 + for _ in range(n): + self.stack.push(self.command) + self.stack.clear() + self.assertEqual(self.stack._stack, []) + self.assertTrue(self.stack.clean) + # Assertion helpers ------------------------------------------------------- @contextmanager diff --git a/pyface/undo/tests/test_undo_manager.py b/pyface/undo/tests/test_undo_manager.py new file mode 100644 index 000000000..48cee9242 --- /dev/null +++ b/pyface/undo/tests/test_undo_manager.py @@ -0,0 +1,72 @@ +# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +from contextlib import contextmanager +import unittest + +from traits.testing.api import UnittestTools + +from pyface.undo.api import CommandStack, UndoManager +from pyface.undo.tests.testing_commands import SimpleCommand, UnnamedCommand + + +class TestUndoManager(unittest.TestCase, UnittestTools): + + def setUp(self): + self.stack_a = CommandStack() + self.stack_b = CommandStack() + self.undo_manager = UndoManager() + self.stack_a.undo_manager = self.undo_manager + self.stack_b.undo_manager = self.undo_manager + + self.undo_manager.active_stack = self.stack_a + + self.command = SimpleCommand() + + # Command pushing tests --------------------------------------------------- + + def test_undo(self): + self.assertEqual(self.stack_a._index, -1) + self.stack_a.push(self.command) + self.assertEqual(self.stack_a._index, 0) + with self.assertTraitChanges(self.undo_manager,'stack_updated', count=1): + self.undo_manager.undo() + self.assertEqual(self.stack_a._index, -1) + + def test_redo(self): + self.assertEqual(self.stack_a._index, -1) + self.stack_a.push(self.command) + self.undo_manager.undo() + self.assertEqual(self.stack_a._index, -1) + with self.assertTraitChanges(self.undo_manager,'stack_updated', count=1): + self.undo_manager.redo() + self.assertEqual(self.stack_a._index, 0) + + def test_change_active_stack(self): + for _ in range(5): + self.stack_a.push(self.command) + self.assertEqual(self.stack_a._index, 4) + self.undo_manager.active_stack = self.stack_b + for _ in range(5): + self.stack_b.push(self.command) + self.assertEqual(self.stack_b._index, 4) + for _ in range(3): + self.undo_manager.undo() + self.undo_manager.redo() + + self.assertEqual(self.stack_a._index, 4) + self.assertEqual(self.stack_b._index, 2) + + def test_active_stack_clean(self): + self.assertTrue(self.undo_manager.active_stack_clean) + self.stack_a.push(self.command) + self.assertFalse(self.undo_manager.active_stack_clean) + self.undo_manager.active_stack = None + self.assertTrue(self.undo_manager.active_stack_clean) From a35c2f7cb112e66894242d882b169a5ce9ec44e3 Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Mon, 30 Nov 2020 15:45:21 -0600 Subject: [PATCH 12/14] add some simple tests for undo.actions --- pyface/undo/action/tests/__init__.py | 9 ++++ pyface/undo/action/tests/test_actions.py | 53 ++++++++++++++++++++++++ pyface/undo/tests/test_undo_manager.py | 7 ++-- 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 pyface/undo/action/tests/__init__.py create mode 100644 pyface/undo/action/tests/test_actions.py diff --git a/pyface/undo/action/tests/__init__.py b/pyface/undo/action/tests/__init__.py new file mode 100644 index 000000000..6b94a8b23 --- /dev/null +++ b/pyface/undo/action/tests/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! diff --git a/pyface/undo/action/tests/test_actions.py b/pyface/undo/action/tests/test_actions.py new file mode 100644 index 000000000..c455804df --- /dev/null +++ b/pyface/undo/action/tests/test_actions.py @@ -0,0 +1,53 @@ +# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +import unittest + +from traits.testing.api import UnittestTools + +from pyface.undo.api import CommandStack, UndoManager +from pyface.undo.tests.testing_commands import SimpleCommand, UnnamedCommand + +from pyface.undo.action.api import RedoAction, UndoAction + + +class TestRedoAction(unittest.TestCase): + + def setUp(self): + self.stack = CommandStack() + self.undo_manager = UndoManager() + self.stack.undo_manager = self.undo_manager + self.undo_manager.active_stack = self.stack + + self.command = SimpleCommand() + + def test_udpate(self): + redo_action = RedoAction(command=self.command, undo_manager=self.undo_manager) + self.stack.push(self.command) + self.undo_manager.undo() + self.assertTrue(redo_action.enabled) + self.assertEqual(redo_action.name, "&Redo Increment by 1") + + +class TestUndoAction(unittest.TestCase): + + def setUp(self): + self.stack = CommandStack() + self.undo_manager = UndoManager() + self.stack.undo_manager = self.undo_manager + self.undo_manager.active_stack = self.stack + + self.command = SimpleCommand() + + def test_update(self): + undo_action = UndoAction(command=self.command, undo_manager=self.undo_manager) + self.stack.push(self.command) + self.assertTrue(undo_action.enabled) + self.assertEqual(undo_action.name, "&Undo Increment by 1") diff --git a/pyface/undo/tests/test_undo_manager.py b/pyface/undo/tests/test_undo_manager.py index 48cee9242..15cd467ad 100644 --- a/pyface/undo/tests/test_undo_manager.py +++ b/pyface/undo/tests/test_undo_manager.py @@ -8,7 +8,6 @@ # # Thanks for using Enthought open source! -from contextlib import contextmanager import unittest from traits.testing.api import UnittestTools @@ -36,7 +35,8 @@ def test_undo(self): self.assertEqual(self.stack_a._index, -1) self.stack_a.push(self.command) self.assertEqual(self.stack_a._index, 0) - with self.assertTraitChanges(self.undo_manager,'stack_updated', count=1): + with self.assertTraitChanges( + self.undo_manager, 'stack_updated', count=1): self.undo_manager.undo() self.assertEqual(self.stack_a._index, -1) @@ -45,7 +45,8 @@ def test_redo(self): self.stack_a.push(self.command) self.undo_manager.undo() self.assertEqual(self.stack_a._index, -1) - with self.assertTraitChanges(self.undo_manager,'stack_updated', count=1): + with self.assertTraitChanges( + self.undo_manager, 'stack_updated', count=1): self.undo_manager.redo() self.assertEqual(self.stack_a._index, 0) From 76bdcc018a32b97644bb97f36537b5fe45498590 Mon Sep 17 00:00:00 2001 From: aaronayres35 <36972686+aaronayres35@users.noreply.github.com> Date: Tue, 1 Dec 2020 07:40:30 -0600 Subject: [PATCH 13/14] Apply suggestions from code review Co-authored-by: Poruri Sai Rahul --- examples/undo/commands.py | 4 ++-- examples/undo/model.py | 2 +- pyface/undo/__init__.py | 1 - pyface/undo/action/tests/test_actions.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/undo/commands.py b/examples/undo/commands.py index 630daadcb..3a341cd48 100644 --- a/examples/undo/commands.py +++ b/examples/undo/commands.py @@ -47,7 +47,7 @@ class LabelIncrementSizeCommand(AbstractCommand): #### Private interface #################################################### - _incremented_by = Int + _incremented_by = Int() ########################################################################### # 'ICommand' interface. @@ -90,7 +90,7 @@ class LabelDecrementSizeCommand(AbstractCommand): #### Private interface #################################################### - _decremented_by = Int + _decremented_by = Int() ########################################################################### # 'ICommand' interface. diff --git a/examples/undo/model.py b/examples/undo/model.py index c60176f12..65b2d7f12 100644 --- a/examples/undo/model.py +++ b/examples/undo/model.py @@ -33,7 +33,7 @@ class Label(HasTraits): #### 'Label' interface #################################################### # The name. - name = Str + name = Str() # The size in points. size = Int(18) diff --git a/pyface/undo/__init__.py b/pyface/undo/__init__.py index b48c90bcb..23776f87d 100644 --- a/pyface/undo/__init__.py +++ b/pyface/undo/__init__.py @@ -8,5 +8,4 @@ # # Thanks for using Enthought open source! """ Supports undoing and scripting application commands. - Part of the AppTools project of the Enthought Tool Suite. """ diff --git a/pyface/undo/action/tests/test_actions.py b/pyface/undo/action/tests/test_actions.py index c455804df..687794b1a 100644 --- a/pyface/undo/action/tests/test_actions.py +++ b/pyface/undo/action/tests/test_actions.py @@ -28,7 +28,7 @@ def setUp(self): self.command = SimpleCommand() - def test_udpate(self): + def test_update(self): redo_action = RedoAction(command=self.command, undo_manager=self.undo_manager) self.stack.push(self.command) self.undo_manager.undo() From 012e27f1627bfdc5940ca6ba8357f621f7fa956f Mon Sep 17 00:00:00 2001 From: Aaron Ayres Date: Tue, 1 Dec 2020 07:41:17 -0600 Subject: [PATCH 14/14] another suggestion from code review --- docs/source/undo.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/undo.rst b/docs/source/undo.rst index e28a9d2a3..4de550ca4 100644 --- a/docs/source/undo.rst +++ b/docs/source/undo.rst @@ -42,7 +42,7 @@ The following are the concepts supported by the framework. done and undone. It may be explicitly set, for example when the data being manipulated by the commands is saved to disk. - Canned PyFace actions are provided as wrappers around command stack methods + PyFace actions are provided as wrappers around command stack methods to implement common menu items. - Undo Manager