OpenShot Video Editor  2.0.0
updates.py
Go to the documentation of this file.
1 ##
2 #
3 # @file
4 # @brief This file contains the classes needed for tracking updates and distributing changes
5 # @author Noah Figg <eggmunkee@hotmail.com>
6 # @author Jonathan Thomas <jonathan@openshot.org>
7 # @author Olivier Girard <eolinwen@gmail.com>
8 #
9 # @section LICENSE
10 #
11 # Copyright (c) 2008-2016 OpenShot Studios, LLC
12 # (http://www.openshotstudios.com). This file is part of
13 # OpenShot Video Editor (http://www.openshot.org), an open-source project
14 # dedicated to delivering high quality video editing and animation solutions
15 # to the world.
16 #
17 # OpenShot Video Editor is free software: you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation, either version 3 of the License, or
20 # (at your option) any later version.
21 #
22 # OpenShot Video Editor is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 # GNU General Public License for more details.
26 #
27 # You should have received a copy of the GNU General Public License
28 # along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
29 #
30 
31 from classes.logger import log
32 
33 try:
34  import json
35 except ImportError:
36  import simplejson as json
37 
38 
39 ##
40 # Interface for classes that listen for 'undo' and 'redo' events.
42 
43  ##
44  # Easily be notified each time there are 'undo' or 'redo' actions available in the UpdateManager.
45  def updateStatusChanged(self, undo_status, redo_status):
46  raise NotImplementedError("updateStatus() not implemented in UpdateWatcher implementer.")
47 
48 
49 ##
50 # Interface for classes that listen for changes (insert, update, and delete).
52 
53  ##
54  # This method is invoked each time the UpdateManager is changed. The action contains all the details of what changed,
55  # including the type of change (insert, update, or delete).
56  def changed(self, action):
57  raise NotImplementedError("changed() not implemented in UpdateInterface implementer.")
58 
59 
60 ##
61 # A data structure representing a single update manager action, including any necessary data to reverse the action.
63 
64  def __init__(self, type=None, key=[], values=None, partial_update=False):
65  self.type = type # insert, update, or delete
66  self.key = key # list which contains the path to the item, for example: ["clips",{"id":"123"}]
67  self.values = values
68  self.old_values = None
69  self.partial_update = partial_update
70 
71  def set_old_values(self, old_vals):
72  self.old_values = old_vals
73 
74  ##
75  # Get the JSON string representing this UpdateAction
76  def json(self, is_array=False, only_value=False):
77 
78  # Build the dictionary to be serialized
79  if only_value:
80  data_dict = self.values
81  else:
82  data_dict = {"type": self.type,
83  "key": self.key,
84  "value": self.values,
85  "partial": self.partial_update}
86 
87  if not is_array:
88  # Use a JSON Object as the root object
89  update_action_dict = data_dict
90  else:
91  # Use a JSON Array as the root object
92  update_action_dict = [data_dict]
93 
94  # Serialize as JSON
95  return json.dumps(update_action_dict)
96 
97  ##
98  # Load this UpdateAction from a JSON string
99  def load_json(self, value):
100 
101  # Load JSON string
102  update_action_dict = json.loads(value)
103 
104  # Set the Update Action properties
105  self.type = update_action_dict["type"]
106  self.key = update_action_dict["key"]
107  self.values = update_action_dict["value"]
108  self.old_values = update_action_dict["old_value"]
109  self.partial_update = update_action_dict["partial"]
110 
111 
112 ##
113 # This class is used to track and distribute changes to listeners. Typically, only 1 instance of this class is needed,
114 # and many different listeners are connected with the add_listener() method.
116 
117  def __init__(self):
118  self.statusWatchers = [] # List of watchers
119  self.updateListeners = [] # List of listeners
120  self.actionHistory = [] # List of actions performed to current state
121  self.redoHistory = [] # List of actions undone
122  self.currentStatus = [None, None] # Status of Undo and Redo buttons (true/false for should be enabled)
123 
124  ##
125  # Reset the UpdateManager, and clear all UpdateActions and History. This does not clear listeners and watchers.
126  def reset(self):
127  self.actionHistory.clear()
128  self.redoHistory.clear()
129 
130  ##
131  # Add a new listener (which will invoke the changed(action) method each time an UpdateAction is available).
132  def add_listener(self, listener, index=-1):
133 
134  if not listener in self.updateListeners:
135  if index <= -1:
136  # Add listener to end of list
137  self.updateListeners.append(listener)
138  else:
139  # Insert listener at index
140  self.updateListeners.insert(index, listener)
141  else:
142  log.warning("Listener already added.")
143 
144  ##
145  # Add a new watcher (which will invoke the updateStatusChanged() method each time a 'redo' or 'undo' action is available).
146  def add_watcher(self, watcher):
147 
148  if not watcher in self.statusWatchers:
149  self.statusWatchers.append(watcher)
150  else:
151  log.warning("Watcher already added.")
152 
153  ##
154  # Notify all watchers if any 'undo' or 'redo' actions are available.
155  def update_watchers(self):
156 
157  new_status = (len(self.actionHistory) >= 1, len(self.redoHistory) >= 1)
158  if self.currentStatus[0] != new_status[0] or self.currentStatus[1] != new_status[1]:
159  for watcher in self.statusWatchers:
160  watcher.updateStatusChanged(*new_status)
161 
162  # This can only be called on actions already run,
163  # as the old_values member is only populated during the
164  # add/update/remove task on the project data store.
165  # the old_values member is needed to reverse the changes
166  # caused by actions.
167  ##
168  # Convert an UpdateAction into the opposite type (i.e. 'insert' becomes an 'delete')
169  def get_reverse_action(self, action):
170 
171  # log.info("Reversing action: {}, {}, {}, {}".format(action.type, action.key, action.values, action.partial_update))
172  reverse = UpdateAction(action.type, action.key, action.values, action.partial_update)
173  # On adds, setup remove
174  if action.type == "insert":
175  reverse.type = "delete"
176 
177  # replace last part of key with ID (so the delete knows which item to delete)
178  id = action.values["id"]
179  action.key.append({"id": id})
180 
181  # On removes, setup add with old value
182  elif action.type == "delete":
183  reverse.type = "insert"
184 
185  # On updates, just swap the old and new values data
186  # Swap old and new values
187  reverse.old_values = action.values
188  reverse.values = action.old_values
189 
190  # log.info("Reversed values: {}, {}, {}, {}".format(reverse.type, reverse.key, reverse.values, reverse.partial_update))
191  return reverse
192 
193  ##
194  # Undo the last UpdateAction (and notify all listeners and watchers)
195  def undo(self):
196 
197  if len(self.actionHistory) > 0:
198  last_action = self.actionHistory.pop()
199  self.redoHistory.append(last_action)
200  # Get reverse of last action and perform it
201  reverse_action = self.get_reverse_action(last_action)
202  self.dispatch_action(reverse_action)
203 
204  ##
205  # Redo the last UpdateAction (and notify all listeners and watchers)
206  def redo(self):
207 
208  if len(self.redoHistory) > 0:
209  # Get last undone action off redo history (remove)
210  next_action = self.redoHistory.pop()
211 
212  # Remove ID from insert (if found)
213  if next_action.type == "insert" and isinstance(next_action.key[-1], dict) and "id" in next_action.key[-1]:
214  next_action.key = next_action.key[:-1]
215 
216  self.actionHistory.append(next_action)
217  # Perform next redo action
218  self.dispatch_action(next_action)
219 
220  # Carry out an action on all listeners
221  ##
222  # Distribute changes to all listeners (by calling their changed() method)
223  def dispatch_action(self, action):
224 
225  try:
226  # Loop through all listeners
227  for listener in self.updateListeners:
228  # Invoke change method on listener
229  listener.changed(action)
230 
231  except Exception as ex:
232  log.error("Couldn't apply '{}' to update listener: {}\n{}".format(action.type, listener, ex))
233  self.update_watchers()
234 
235  # Perform load action (loading all project data), clearing history for taking a new path
236  ##
237  # Load all project data via an UpdateAction into the UpdateManager (this action will then be distributed to all listeners)
238  def load(self, values):
239 
240  self.redoHistory.clear()
241  self.actionHistory.append(UpdateAction('load', '', values))
242  self.dispatch_action(self.actionHistory[-1])
243 
244  # Perform new actions, clearing redo history for taking a new path
245  ##
246  # Insert a new UpdateAction into the UpdateManager (this action will then be distributed to all listeners)
247  def insert(self, key, values):
248 
249  self.redoHistory.clear()
250  self.actionHistory.append(UpdateAction('insert', key, values))
251  self.dispatch_action(self.actionHistory[-1])
252 
253  ##
254  # Update the UpdateManager with an UpdateAction (this action will then be distributed to all listeners)
255  def update(self, key, values, partial_update=False):
256 
257  self.redoHistory.clear()
258  self.actionHistory.append(UpdateAction('update', key, values, partial_update))
259  self.dispatch_action(self.actionHistory[-1])
260 
261  ##
262  # Delete an item from the UpdateManager with an UpdateAction (this action will then be distributed to all listeners)
263  def delete(self, key):
264 
265  self.redoHistory.clear()
266  self.actionHistory.append(UpdateAction('delete', key))
267  self.dispatch_action(self.actionHistory[-1])
def update_watchers(self)
Notify all watchers if any &#39;undo&#39; or &#39;redo&#39; actions are available.
Definition: updates.py:155
This class is used to track and distribute changes to listeners.
Definition: updates.py:115
def updateStatusChanged(self, undo_status, redo_status)
Easily be notified each time there are &#39;undo&#39; or &#39;redo&#39; actions available in the UpdateManager.
Definition: updates.py:45
def json(self, is_array=False, only_value=False)
Get the JSON string representing this UpdateAction.
Definition: updates.py:76
def load_json(self, value)
Load this UpdateAction from a JSON string.
Definition: updates.py:99
def get_reverse_action(self, action)
Convert an UpdateAction into the opposite type (i.e.
Definition: updates.py:169
def redo(self)
Redo the last UpdateAction (and notify all listeners and watchers)
Definition: updates.py:206
def dispatch_action(self, action)
Distribute changes to all listeners (by calling their changed() method)
Definition: updates.py:223
def set_old_values(self, old_vals)
Definition: updates.py:71
A data structure representing a single update manager action, including any necessary data to reverse...
Definition: updates.py:62
def undo(self)
Undo the last UpdateAction (and notify all listeners and watchers)
Definition: updates.py:195
def reset(self)
Reset the UpdateManager, and clear all UpdateActions and History.
Definition: updates.py:126
def add_watcher(self, watcher)
Add a new watcher (which will invoke the updateStatusChanged() method each time a &#39;redo&#39; or &#39;undo&#39; ac...
Definition: updates.py:146
def __init__(self, type=None, key=[], values=None, partial_update=False)
Definition: updates.py:64
def load(self, values)
Load all project data via an UpdateAction into the UpdateManager (this action will then be distribute...
Definition: updates.py:238
def add_listener(self, listener, index=-1)
Add a new listener (which will invoke the changed(action) method each time an UpdateAction is availab...
Definition: updates.py:132
Interface for classes that listen for &#39;undo&#39; and &#39;redo&#39; events.
Definition: updates.py:41
def insert(self, key, values)
Insert a new UpdateAction into the UpdateManager (this action will then be distributed to all listene...
Definition: updates.py:247
def changed(self, action)
This method is invoked each time the UpdateManager is changed.
Definition: updates.py:56
def delete(self, key)
Delete an item from the UpdateManager with an UpdateAction (this action will then be distributed to a...
Definition: updates.py:263
Interface for classes that listen for changes (insert, update, and delete).
Definition: updates.py:51
def update(self, key, values, partial_update=False)
Update the UpdateManager with an UpdateAction (this action will then be distributed to all listeners)...
Definition: updates.py:255
def __init__(self)
Definition: updates.py:117