How can I access the QUndoStack
of a QTextDocument
?
(For example, I want to be able to add custom QUndoCommand
objects to the document's undo stack)
How can I access the QUndoStack
of a QTextDocument
?
(For example, I want to be able to add custom QUndoCommand
objects to the document's undo stack)
I have been reading the documentation and it doesn't seems to be a way to get the QUndoStack
directly for the Widget.
Probably the only way is to create your own QUndoStack
object and manually add the changes and then re-implement the redo()
/ undo()
slots. I would have a look to the source code, you can probably get most of the code you need from there to store the changes in QTextDocument
.
There is no way :(
The way I used is modifying QTextDocument Class for my needs and then recompile Gui module.
The static linking is a good choice for this purpose.
As others have said, indeed there seems to be no way to directly access the Undo stack as for May 2020. E.g. This 2017 answer by user mrjj at the Qt forum says that the stack lives inside qtextdocument_p.cpp
and that there isn't a way to access it through the interfaces.
Instead, everyone suggests to implement your own undo-able commands and that it is a piece of cake, but I wasn't able to find such meaningful implementation. Also, the built-in functionality is well documented in the QTextDocument docs and at least to me it doesn't look too straightforward to just reimplement:
Undo/redo of operations performed on the document can be controlled using the setUndoRedoEnabled() function. The undo/redo system can be controlled by an editor widget through the undo() and redo() slots; the document also provides contentsChanged() , undoAvailable() , and redoAvailable() signals that inform connected editor widgets about the state of the undo/redo system. The following are the undo/redo operations of a QTextDocument :
- Insertion or removal of characters. A sequence of insertions or removals within the same text block are regarded as a single undo/redo operation.
- Insertion or removal of text blocks. Sequences of insertion or removals in a single operation (e.g., by selecting and then deleting text) are regarded as a single undo/redo operation.
- Text character format changes.
- Text block format changes.
- Text block group format changes.
As we can see it integrates many different kinds of complex events and on the top of that it features command compressions. I personally disliked the idea of reimplementing that very much.
Ideally, we would access the stack through the API and we would be done! Hopefully this is supported at some point (please let me know in the comments if that is the case). In this answer, I show a way to integrate the built-in QTextDocument Undo stack with minimal effort and retaining all its functionality. I tried many different ways and I liked this one the best. Hope this helps!
This code exemplifies the usage with a QPlainTextEdit
, but you can reproduce it with other widgets. See the docstrings for explanations:
from PySide2 import QtWidgets, QtGui, QtCore
class TextDocumentUndoWrapperCommand(QtWidgets.QUndoCommand):
"""
This command is a wrapper that simply uses the text document stack, but
allows to register the action on a different stack for integration.
"""
def __init__(self, txt_editor, parent=None):
super().__init__("Text Document Action", parent)
self.txt_editor = txt_editor
def undo(self):
self.txt_editor.document().undo()
def redo(self):
self.txt_editor.document().redo()
class TextEditor(QtWidgets.QPlainTextEdit):
"""
QTextDocument document has a really nice built-in undo stack, but
unfortunately it cannot be accessed or integrated with other undo stacks.
This class exemplifies such integration, as follows:
1. Important: we do NOT disable undo/redo functionality. We keep it on!
2. Every time that QTextDocument adds a Command to its own stack, we add
a wrapper command to our own main stack
3. Every time the user sends an undo/redo event, we intercept it and send
it through our wrapper command. This way we have effectively integrated
the built-in undo stack into our own main stack.
"""
def __init__(self, parent=None, undo_stack=None):
"""
"""
super().__init__(parent)
self.setLineWrapMode(self.WidgetWidth) # matter of taste
if undo_stack is not None:
# if we provide a stack, integrate internal stack with it
self.installEventFilter(self)
self.undo_stack = undo_stack
self.document().undoCommandAdded.connect(self.handle_undo_added)
def handle_undo_added(self, *args, **kwargs):
"""
The key information is WHEN to create an undo command. Luckily,
the QTextDocument provides us that information. That way, we can keep
both undo stacks in perfect sync.
"""
cmd = TextDocumentUndoWrapperCommand(self)
self.undo_stack.push(cmd)
def eventFilter(self, obj, evt):
"""
We didn't deactivate the undo functionality. We simply want to
re-route it through our stack, which is synched with the built-in
one.
"""
if evt.type() == QtCore.QEvent.KeyPress:
if evt.matches(QtGui.QKeySequence.Undo):
self.undo_stack.undo()
return True
if evt.matches(QtGui.QKeySequence.Redo):
self.undo_stack.redo()
return True
return super().eventFilter(obj, evt)
The TextEditor
can be then simply used as a regular widget. If we don't provide a stack to the constructor, the default built-in hidden stack will be used. If we provide one, the wrapper mechanism will integrate the hidden stack into the provided one.
Note: I am not providing a solution for "just the QTextDocument" because I wasn't able to make the eventFilter
work for it (I'm happy to hear about others' efforts). In any case, the QTextDocument is always inside any sort of parent widget/window, and then this logic should apply identically. There are plenty of forums asking for this functionality, and I think this was the best place to post this answer (let me know otherwise).
© 2022 - 2024 — McMap. All rights reserved.
contextMenuEvent()
, get the default menu withcreateStandardContextMenu()
, retrieve the undo/redo actions withfindChild()
using their object name (edit-undo
andedit-redo
), disconnect theirtriggered
signals and connect them with the related slots of the custom undo stack. – Periwinkle