-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add basic Undo/Redo functionality to Entry widget #2539
Conversation
This is amazing to have worked on thanks!
Is it likely that developers would want to disable undo/redo? I'm not sure this is needed. If it is then certainly should not be disabled by default! You're right that Entry will be changing as we move the ability to edit from a single style to rich styles. I wonder if you may be right about memory concerns. If someone loads a large text file then every time someone types a key it will add the whole amount again. So a 100k text file will be taking 10MB after just 100 keystrokes. Just on API naming did you consider |
Some thoughts as a partial response. Sorry for the length!
This flag is present mostly to mitigate the disadvantages of the current undo implementation (lots of memory duplication etc). I think you are right that history tracking should be enabled by default.
That's better, thanks!
I would argue otherwise. It seems to me that, typically, Entry.SetText invoked by an application corresponds to a user action elsewhere in the application (not in the entry itself), and thus if the action is to be undone, this should also be initiated from elsewhere in the application, not from the entry itself, since more things need to be changed. From this I reasoned that the default behavior of SetText should reset Undo history, with a special "undoable" variant for exceptional use cases like the ones you mentioned. Sorry for a messy sentence, it's hard to justify a gut feeling, but here are some thoughts/examples:
Not sure how convincing this is, it's just in my (very limited) experience with GUI application, all the situations in which SetText() is used are similar to one of the two situations sketched above, and making SetText() cancellable by default would: (a) make existing apps behave strangely; (b) make it easy to accidentally write similar strange behavior for new applications. In general, if the entry content depends on something else in the application (and why else would SetText() be called?), restoring the entry state directly, without any coordination with the other parts of the application, is a very suspicious action and, I think, should not be allowed unless the develop explicitly requests this by calling SetTextUndoable. |
As for the future RichText compatibility, would it be reasonable to add a This would have further benefits: in the rich text modification functions ( (Maybe in the future Entry would be smart enough to represent very long texts as several different |
Good argument :) I agree now thanks.
It would be worth looking at that yes. But you don't need to export the method as we're internal to
Not sure if this is the right way to optimise or not, if we record the actual changes (and where the cursor was) we can avoid cloning trees and diffing, so don't need to update the data structure to work efficiently. |
Sure, that's a good point. Copying the tree is the "deep" part of the "deep copy" :)
It wouldn't be easy to do this for a (future) fully RichText-based Entry without any "deepCopy" method on Overall, I'm not sure an implementation of a smarter undo/redo mechanism is worth attempting before RichText has been stabilized and integrated into Entry. |
Probably true. |
I think holding back until Entry embraces RichText fully is more sensible. With these modifications I now have an Undo/Redo functionality in an application I'm writing, which makes me content, and when RichText comes around, I'll try to port the modifications. |
OK thanks. Moved it to draft so we can skim over it in the list for now. |
I'm not sure if I'll have time to help port this code, so maybe this pull request should be closed. I'd just like to mention that since the undo mechanism implemented here is quite crude (as explained in the original description), essentially the only thing that's needed to adapt the code for It seems that a proper way would be to add a |
Description:
This pull request introduces a basic Undo/Redo functionality in the Entry widget, fixing issue #436 . The realization works as is for me, but I have some questions, presented slightly below. I am also working on writing tests for the new functionality.
Basic description
If
Entry.HistoryDisabled
is false, whenever a user performs an action like, e.g., "typed rune", "paste from clipboard", or "erase selection by pressing backspace", the full state of the entry widget is saved and appended to an action log. The state includes full text, cursor position, and scroll position. When user calls "Undo" on a history-enabled widget, the state is restored from the action log. Redo is implemented in a similar way.To improve user experience, a sequence of "typed rune" actions performed in a quick succession is considered a single action, so Undo does not cancel just one letter at a time. Same for a sequence of deletions (e.g., pressing backspace several times).
Calling SetText() erases the history, with a new SetTextUndoable() function that adds a new event to the history log instead.
For simplicity, undo action resets the current selection to empty.
Justification for the crude mechanism
Better history implementation would store only user actions and roll them back in a smart way when requested. Saving the whole widget state is slower and wasteful in terms of memory, but this is the approach used here. Why?
Some requests for feedback
Design questions
restoreHistorySnapshot()
would ideally restore the widget state completely while holding propertyLock(), but the current implementation ofupdateText()
is not particularly conductive to this (andsetFieldsAndRefresh
in general). Someone should take a look if there are severe problems with concurrency in this code, I'm not very experienced with this.Implementation questions
Public API changes
Added to widget.Entry:
func() bool
: return true if entry history is enabled and the corresponding action can be performed. Can be used by applications for disabling menu items or toolbar buttons.func()
: perform undo/redo.func(text string)
: sets the text of the entry to the provided string and saves this as a separate action in the action log.Modified behavior in widget.Entry:
New shortcuts:
struct{}
). On desktop they are bound to Ctrl+Z and Ctrl+Y.Checklist: