class EDict(dict): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.stack = QUndoStack() def _realSet(self, key, val): super().__setitem__(key, val) def __setitem__(self, key, val): if key in self.keys(): self.stack.push(ModifyCommand(self, key, val)) else: self.stack.push(AppendCommand(self, key, val)) def undoText(self): return self.stack.undoText() def redoText(self): return self.stack.redoText() def undo(self): self.stack.undo() def redo(self): self.stack.redo()
if __name__ == '__main__': undo_stack = QUndoStack() dictionary = {"a": "AAA", "b": "BBB"} print('* initial dict', dictionary) undo_stack.push(AppendCommand(dictionary, "c", "CCC")) print(undo_stack.undoText(), dictionary) undo_stack.push(AppendCommand(dictionary, "d", "DDD")) print(undo_stack.undoText(), dictionary) undo_stack.undo() print(undo_stack.redoText(), dictionary) undo_stack.undo() print(undo_stack.redoText(), dictionary) undo_stack.redo() print(undo_stack.undoText(), dictionary) undo_stack.push(ModifyCommand(dictionary, "a", "---")) print(undo_stack.undoText(), dictionary) undo_stack.undo() print(undo_stack.redoText(), dictionary) undo_stack.redo() print(undo_stack.undoText(), dictionary)
class URDict(UserDict): """ The URDict class implements a dictionary-based class with undo/redo functionality based on QUndoStack. """ def __init__(self, *args, **kwargs): self._stack = QUndoStack() super().__init__(*args, **kwargs) self._macroRunning = False # Private URDict dictionary-based methods to be called via the QUndoCommand-based classes. def _realSetItem(self, key: Union[str, List], value: Any) -> NoReturn: """Actually changes the value for the existing key in dictionary.""" if isinstance(key, list): self.getItemByPath(key[:-1])[key[-1]] = value else: super().__setitem__(key, value) def _realAddItem(self, key: str, value: Any) -> NoReturn: """Actually adds a key-value pair to dictionary.""" super().__setitem__(key, value) def _realDelItem(self, key: str) -> NoReturn: """Actually deletes a key-value pair from dictionary.""" del self[key] def _realSetItemByPath(self, keys: list, value: Any) -> NoReturn: """Actually sets the value in a nested object by the key sequence.""" self.getItemByPath(keys[:-1])[keys[-1]] = value # Public URDict dictionary-based methods def __setitem__(self, key: str, val: Any) -> NoReturn: """Overrides default dictionary assignment to self[key] implementation. Calls the undoable command and pushes this command on the stack.""" if key in self: self._stack.push(_SetItemCommand(self, key, val)) else: self._stack.push(_AddItemCommand(self, key, val)) def setItemByPath(self, keys: list, value: Any) -> NoReturn: """Calls the undoable command to set a value in a nested object by key sequence and pushes this command on the stack.""" self._stack.push(_SetItemCommand(self, keys, value)) def getItemByPath(self, keys: list, default=None) -> Any: """Returns a value in a nested object by key sequence.""" item = self for key in keys: if key in item.keys(): item = item[key] else: return default return item def getItem(self, key: Union[str, list], default=None): """Returns a value in a nested object. Key can be either a sequence or a simple string.""" if isinstance(key, list): return self.getItemByPath(key, default) else: return self.get(key, default) # Public URDict undostack-based methods def undoText(self) -> NoReturn: """Returns the text of the command which will be undone in the next call to undo().""" return self._stack.undoText() def redoText(self) -> NoReturn: """Returns the text of the command which will be redone in the next call to redo().""" return self._stack.redoText() def undo(self) -> NoReturn: """Undoes the current command on stack.""" self._stack.undo() def redo(self) -> NoReturn: """Redoes the current command on stack.""" self._stack.redo() def startBulkUpdate(self, text='Bulk update') -> NoReturn: """Begins composition of a macro command with the given text description.""" if self._macroRunning: print('Macro already running') return self._stack.beginMacro(text) self._macroRunning = True def endBulkUpdate(self) -> NoReturn: """Ends composition of a macro command.""" if not self._macroRunning: print('Macro not running') return self._stack.endMacro() self._macroRunning = False
class UndoableDict(PathDict): """ The UndoableDict class implements a PathDict-base_dict class with undo/redo functionality base_dict on QUndoStack. """ def __init__(self, *args, **kwargs): self.__stack = QUndoStack() self._macroRunning = False super().__init__(*args, **kwargs) # Public methods: dictionary-related def __setitem__(self, key: str, val: Any) -> NoReturn: """ Calls the undoable command to override PathDict assignment to self[key] implementation and pushes this command on the stack. """ if key in self: self.__stack.push(_SetItemCommand(self, key, val)) else: self.__stack.push(_AddItemCommand(self, key, val)) def setItemByPath(self, keys: list, value: Any) -> NoReturn: """ Calls the undoable command to set a value in a nested object by key sequence and pushes this command on the stack. """ self.__stack.push(_SetItemCommand(self, keys, value)) # Public methods: undo/redo-related def clearUndoStack(self) -> NoReturn: """ Clears the command stack by deleting all commands on it, and returns the stack to the clean state. """ self.__stack.clear() def canUndo(self) -> bool: """ :return true if there is a command available for undo; otherwise returns false. """ return self.__stack.canUndo() def canRedo(self) -> bool: """ :return true if there is a command available for redo; otherwise returns false. """ return self.__stack.canRedo() def undo(self) -> NoReturn: """ Undoes the current command on stack. """ self.__stack.undo() def redo(self) -> NoReturn: """ Redoes the current command on stack. """ self.__stack.redo() def undoText(self) -> str: """ :return the current command on stack. """ return self.__stack.undoText() def redoText(self) -> str: """ :return the current command on stack. """ return self.__stack.redoText() def startBulkUpdate(self, text='Bulk update') -> NoReturn: """ Begins composition of a macro command with the given text description. """ if self._macroRunning: print('Macro already running') return self.__stack.beginMacro(text) self._macroRunning = True def endBulkUpdate(self) -> NoReturn: """ Ends composition of a macro command. """ if not self._macroRunning: print('Macro not running') return self.__stack.endMacro() self._macroRunning = False def bulkUpdate(self, key_list: list, item_list: list, text='Bulk update') -> NoReturn: """ Performs a bulk update base_dict on a list of keys and a list of values :param key_list: list of keys or path keys to be updated :param item_list: the value to be updated :return: None """ self.startBulkUpdate(text) for key, value in zip(key_list, item_list): self.setItemByPath(key, value) self.endBulkUpdate()
class URDict(dict): class _StackCommand(QUndoCommand): def __init__(self, dictionary, key, value): QUndoCommand.__init__(self) self._dictionary = dictionary self._key = key self._old_value = None thisKey = key if isinstance(thisKey, list): thisKey = thisKey[0] if thisKey in dictionary: self._old_value = dictionary[key] self._new_value = value def undo(self): # self.setText(" undo command {} - {}:{} = ".format(self._dictionary, self._key, self._value)) if self._old_value is None: self.setText(" undo command {} - {}:{} = ".format( self._dictionary, self._key, self._new_value)) del self._dictionary[self._key] else: self._dictionary.__realsetitem__(self._key, self._old_value) def redo(self): # self.setText(" do/redo command {} + {}:{} = ".format(self._dictionary, self._key, self._value)) self._dictionary.__realsetitem__(self._key, self._new_value) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__stack__ = QUndoStack() def __setitem__(self, key, val): self.__stack__.push(self._StackCommand(self, key, val)) def __getitem__(self, key): if isinstance(key, list): return self.getByPath(key) return super().__getitem__(key) def __realsetitem__(self, key, val): if isinstance(key, list): self.setByPath(key, val) else: super().__setitem__(key, val) def undoText(self): return self.__stack__.undoText() def redoText(self): return self.__stack__.redoText() def undo(self): self.__stack__.undo() def redo(self): self.__stack__.redo() def getByPath(self, keys): """Access a nested object in root by key sequence. We can't use reduce and operator""" item = self for key in keys: if key in item.keys(): item = item[key] else: raise KeyError return item def setByPath(self, keys, value): """Get a value in a nested object in root by key sequence.""" self.getByPath(keys[:-1])[keys[-1]] = value