Skip to content

Commit

Permalink
Merge pull request #3187 from ferdnyc/pending
Browse files Browse the repository at this point in the history
Add pending_action tracking to update manager
  • Loading branch information
jonoomph authored Feb 28, 2020
2 parents ddf89b9 + 1e0ecf3 commit 8201f31
Showing 1 changed file with 58 additions and 30 deletions.
88 changes: 58 additions & 30 deletions src/classes/updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,32 @@
"""

from classes.logger import log
from classes import info
import copy
import os
import json


class UpdateWatcher:
""" Interface for classes that listen for 'undo' and 'redo' events. """

def updateStatusChanged(self, undo_status, redo_status):
""" Easily be notified each time there are 'undo' or 'redo' actions available in the UpdateManager. """
""" Easily be notified each time there are 'undo' or 'redo' actions
available in the UpdateManager. """
raise NotImplementedError("updateStatus() not implemented in UpdateWatcher implementer.")


class UpdateInterface:
""" Interface for classes that listen for changes (insert, update, and delete). """

def changed(self, action):
""" This method is invoked each time the UpdateManager is changed. The action contains all the details of what changed,
""" This method is invoked each time the UpdateManager is changed.
The action contains all the details of what changed,
including the type of change (insert, update, or delete). """
raise NotImplementedError("changed() not implemented in UpdateInterface implementer.")


class UpdateAction:
"""A data structure representing a single update manager action, including any necessary data to reverse the action."""
"""A data structure representing a single update manager action,
including any necessary data to reverse the action."""

def __init__(self, type=None, key=[], values=None, partial_update=False):
self.type = type # insert, update, or delete
Expand Down Expand Up @@ -121,22 +123,23 @@ def load_json(self, value):


class UpdateManager:
""" This class is used to track and distribute changes to listeners. Typically, only 1 instance of this class is needed,
and many different listeners are connected with the add_listener() method. """
""" This class is used to track and distribute changes to listeners.
Typically, only 1 instance of this class is needed, and many different
listeners are connected with the add_listener() method. """

def __init__(self):
self.statusWatchers = [] # List of watchers
self.updateListeners = [] # List of listeners
self.actionHistory = [] # List of actions performed to current state
self.redoHistory = [] # List of actions undone
self.currentStatus = [None, None] # Status of Undo and Redo buttons (true/false for should be enabled)
self.ignore_history = False # Ignore saving actions to history, to prevent a huge undo/redo list
self.last_action = None
self.ignore_history = False # Ignore saving actions to history, to prevent a huge undo/redo list
self.last_action = None # The last action processed
self.pending_action = None # Last action not added to actionHistory list

def load_history(self, project):
"""Load history from project"""
self.redoHistory.clear()
self.actionHistory.clear()
self.reset()

# Get history from project data
history = project.get("history")
Expand Down Expand Up @@ -183,22 +186,24 @@ def save_history(self, project, history_length):
log.info("Saving undo history, skipped key: %s" % str(action.key))

# Set history data in project
self.ignore_history = True
self.update(["history"], { "redo": redo_list, "undo": undo_list})
self.ignore_history = False
self.update_untracked(["history"], {"redo": redo_list, "undo": undo_list})

def reset(self):
""" Reset the UpdateManager, and clear all UpdateActions and History. This does not clear listeners and watchers. """
""" Reset the UpdateManager, and clear all UpdateActions and History.
This does not clear listeners and watchers. """
self.actionHistory.clear()
self.redoHistory.clear()
self.pending_action = None
self.last_action = None

# Notify watchers of new history state
self.update_watchers()

def add_listener(self, listener, index=-1):
""" Add a new listener (which will invoke the changed(action) method each time an UpdateAction is available). """
""" Add a new listener (which will invoke the changed(action) method
each time an UpdateAction is available). """

if not listener in self.updateListeners:
if listener not in self.updateListeners:
if index <= -1:
# Add listener to end of list
self.updateListeners.append(listener)
Expand All @@ -209,9 +214,10 @@ def add_listener(self, listener, index=-1):
log.warning("Cannot add existing listener: {}".format(str(listener)))

def add_watcher(self, watcher):
""" Add a new watcher (which will invoke the updateStatusChanged() method each time a 'redo' or 'undo' action is available). """
""" Add a new watcher (which will invoke the updateStatusChanged() method
each time a 'redo' or 'undo' action is available). """

if not watcher in self.statusWatchers:
if watcher not in self.statusWatchers:
self.statusWatchers.append(watcher)
else:
log.warning("Cannot add existing watcher: {}".format(str(watcher)))
Expand Down Expand Up @@ -262,6 +268,7 @@ def undo(self):
last_action = copy.deepcopy(self.actionHistory.pop())

self.redoHistory.append(last_action)
self.pending_action = None
# Get reverse of last action and perform it
reverse_action = self.get_reverse_action(last_action)
self.dispatch_action(reverse_action)
Expand All @@ -278,6 +285,7 @@ def redo(self):
next_action.key = next_action.key[:-1]

self.actionHistory.append(next_action)
self.pending_action = None
# Perform next redo action
self.dispatch_action(next_action)

Expand All @@ -297,55 +305,75 @@ def dispatch_action(self, action):

# Perform load action (loading all project data), clearing history for taking a new path
def load(self, values):
""" Load all project data via an UpdateAction into the UpdateManager (this action will then be distributed to all listeners) """
""" Load all project data via an UpdateAction into the UpdateManager
(this action will then be distributed to all listeners) """

self.last_action = UpdateAction('load', '', values)
self.redoHistory.clear()
self.actionHistory.clear()
self.pending_action = None
self.dispatch_action(self.last_action)

# Perform new actions, clearing redo history for taking a new path
def insert(self, key, values):
""" Insert a new UpdateAction into the UpdateManager (this action will then be distributed to all listeners) """
""" Insert a new UpdateAction into the UpdateManager
(this action will then be distributed to all listeners) """

self.last_action = UpdateAction('insert', key, values)
if not self.ignore_history:
if self.ignore_history:
self.pending_action = self.last_action
else:
self.redoHistory.clear()
self.pending_action = None
self.actionHistory.append(self.last_action)
self.dispatch_action(self.last_action)

def update(self, key, values, partial_update=False):
""" Update the UpdateManager with an UpdateAction (this action will then be distributed to all listeners) """
""" Update the UpdateManager with an UpdateAction
(this action will then be distributed to all listeners) """

self.last_action = UpdateAction('update', key, values, partial_update)
if not self.ignore_history:
if self.ignore_history:
self.pending_action = self.last_action
else:
if self.last_action.key and self.last_action.key[0] != "history":
# Clear redo history for any update except a "history" update
self.redoHistory.clear()
self.pending_action = None
self.actionHistory.append(self.last_action)
self.dispatch_action(self.last_action)

def update_untracked(self, key, values, partial_update=False):
""" Update the UpdateManager with an UpdateAction, without creating a new entry in the history table (this action will then be distributed to all listeners) """
""" Update the UpdateManager with an UpdateAction, without creating
a new entry in the history table
(this action will then be distributed to all listeners) """
previous_ignore = self.ignore_history
previous_pending = self.pending_action
self.ignore_history = True
self.update(key, values, partial_update)
self.ignore_history = previous_ignore
self.pending_action = previous_pending

def delete(self, key):
""" Delete an item from the UpdateManager with an UpdateAction (this action will then be distributed to all listeners) """
""" Delete an item from the UpdateManager with an UpdateAction
(this action will then be distributed to all listeners) """

self.last_action = UpdateAction('delete', key)
if not self.ignore_history:
if self.ignore_history:
self.pending_action = self.last_action
else:
self.redoHistory.clear()
self.pending_action = None
self.actionHistory.append(self.last_action)
self.dispatch_action(self.last_action)

def apply_last_action_to_history(self, previous_value):
""" Apply the last action to the history """
if self.last_action:
self.last_action.set_old_values(previous_value)
self.actionHistory.append(self.last_action)
if self.pending_action:
self.pending_action.set_old_values(previous_value)
self.actionHistory.append(self.pending_action)
self.last_action = self.pending_action
self.pending_action = None

# Notify watchers of new history state
self.update_watchers()

0 comments on commit 8201f31

Please sign in to comment.