[geary/wip/714104-refine-account-dialog: 23/69] Add a user command abstraction and manager for handling undo/redo.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/714104-refine-account-dialog: 23/69] Add a user command abstraction and manager for handling undo/redo.
- Date: Fri, 30 Nov 2018 12:50:16 +0000 (UTC)
commit 6e1ff62b60afcd366f146f0f0e75bba80d40a72c
Author: Michael James Gratton <mike vee net>
Date: Wed Jun 13 17:31:46 2018 +1000
Add a user command abstraction and manager for handling undo/redo.
po/POTFILES.in | 1 +
src/client/application/application-command.vala | 251 ++++++++++++++++++++++++
src/client/meson.build | 1 +
3 files changed, 253 insertions(+)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0eee4f71..b1ee0817 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -29,6 +29,7 @@ src/client/accounts/local-service-information.vala
src/client/accounts/login-dialog.vala
src/client/application/application-avatar-store.vala
src/client/application/autostart-manager.vala
+src/client/application/application-command.vala
src/client/application/geary-application.vala
src/client/application/geary-args.vala
src/client/application/geary-controller.vala
diff --git a/src/client/application/application-command.vala b/src/client/application/application-command.vala
new file mode 100644
index 00000000..533370c0
--- /dev/null
+++ b/src/client/application/application-command.vala
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2018 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+
+/**
+ * A generic application user command with undo and redo support.
+ */
+public abstract class Application.Command : GLib.Object {
+
+
+ /**
+ * A human-readable label describing the effect of calling {@link undo}.
+ *
+ * This can be used in a user interface, perhaps as a tooltip for
+ * an Undo button, to indicate what will happen if the command is
+ * un-done. For example, "Conversation restored from Trash".
+ */
+ public string? undo_label { get; protected set; default = null; }
+
+ /**
+ * A human-readable label describing the effect of calling {@link redo}.
+ *
+ * This can be used in a user interface, perhaps as a tooltip for
+ * a Redo button, to indicate what will happen if the command is
+ * re-done. For example, "Conversation restored from Trash".
+ */
+ public string? redo_label { get; protected set; default = null; }
+
+ /**
+ * A human-readable label describing the result of calling {@link execute}.
+ *
+ * This can be used in a user interface to indicate the effects of
+ * the action just executed. For example, "Conversation moved to
+ * Trash".
+ *
+ * Since the effects of re-doing a command should be identical to
+ * that of executing it, this string can also be used to describe
+ * the effects of {@link redo}.
+ */
+ public string? executed_label { get; protected set; default = null; }
+
+ /**
+ * A human-readable label describing the result of calling {@link undo}.
+ *
+ * This can be used in a user interface to indicate the effects of
+ * the action just executed. For example, "Conversation restored
+ * from Trash".
+ */
+ public string? undone_label { get; protected set; default = null; }
+
+
+ /**
+ * Called by {@link CommandStack} to execute the command.
+ *
+ * Applications should not call this method directly, rather pass
+ * it to {@link CommandStack.execute}.
+ *
+ * Command implementations should apply the user command when this
+ * method is called. It will be called at most once when used sole
+ * with the command stack.
+ */
+ public abstract async void execute(GLib.Cancellable? cancellable)
+ throws GLib.Error;
+
+ /**
+ * Called by {@link CommandStack} to undo the executed command.
+ *
+ * Applications should not call this method directly, rather they
+ * should call {@link CommandStack.undo} so that it is managed
+ * correctly.
+ *
+ * Command implementations should reverse the user command carried
+ * out by the call to {@link execute}. It will be called zero or
+ * more times, but only ever after a call to either {@link
+ * execute} or {@link redo} when used sole with the command stack.
+ */
+ public abstract async void undo(GLib.Cancellable? cancellable)
+ throws GLib.Error;
+
+ /**
+ * Called by {@link CommandStack} to redo the executed command.
+ *
+ * Applications should not call this method directly, rather they
+ * should call {@link CommandStack.redo} so that it is managed
+ * correctly.
+ *
+ * Command implementations should re-apply a user command that has
+ * been un-done by a call to {@link undo}. By default, this method
+ * simply calls {@link execute}, but implementations with more
+ * complex requirements can override this. It will called zero or
+ * more times, but only ever after a call to {@link undo} when
+ * used sole with the command stack.
+ */
+ public virtual async void redo(GLib.Cancellable? cancellable)
+ throws GLib.Error {
+ yield execute(cancellable);
+ }
+
+}
+
+
+/**
+ * A stack of executed application commands.
+ *
+ * The command stack manages calling the {@link Command.execute},
+ * {@link Command.undo}, and {@link Command.redo} methods on an
+ * application's user commands. It enforces the strict ordering of
+ * calls to those methods so that if a command is well implemented,
+ * then the application will be in the same state after executing and
+ * re-doing a command, and the application will return to the original
+ * state after being undone, both for individual commands and between
+ * after a number of commands have been executed.
+ *
+ * Applications should call {@link execute} to execute a command,
+ * which will push it on to an undo stack after executing it. The
+ * command at the top of the stack can be undone by calling {@link
+ * undo}, which undoes the command, pops it from the undo stack and
+ * pushes it on the redo stack. If a new command is executed when the
+ * redo stack is non-empty, it will be emptied first.
+ */
+public class Application.CommandStack : GLib.Object {
+
+
+ // The can_undo and can_redo are automatic properties so
+ // applications can get notified when they change.
+
+ /** Determines if there are any commands able to be un-done. */
+ public bool can_undo { get; private set; }
+
+ /** Determines if there are any commands available to be re-done. */
+ public bool can_redo { get; private set; }
+
+
+ private Gee.LinkedList<Command> undo_stack = new Gee.LinkedList<Command>();
+ private Gee.LinkedList<Command> redo_stack = new Gee.LinkedList<Command>();
+
+
+ /** Fired when a command is first executed */
+ public signal void executed(Command command);
+
+ /** Fired when a command is un-done */
+ public signal void undone(Command command);
+
+ /** Fired when a command is re-executed */
+ public signal void redone(Command command);
+
+
+ /**
+ * Executes an command and pushes it onto the undo stack.
+ *
+ * This calls {@link Command.execute} and if no error is thrown,
+ * pushes the command onto the undo stack.
+ */
+ public async void execute(Command target, GLib.Cancellable? cancellable)
+ throws GLib.Error {
+ yield target.execute(cancellable);
+
+ this.undo_stack.insert(0, target);
+ this.can_undo = true;
+
+ this.redo_stack.clear();
+ this.can_redo = false;
+
+ executed(target);
+ }
+
+ /**
+ * Pops a command off the undo stack and un-does is.
+ *
+ * This calls {@link Command.undo} on the topmost command on the
+ * undo stack and if no error is thrown, pushes it on the redo
+ * stack. If an error is thrown, the command is discarded and the
+ * redo stack is emptied.
+ */
+ public async void undo(GLib.Cancellable? cancellable)
+ throws GLib.Error {
+ if (!this.undo_stack.is_empty) {
+ Command target = this.undo_stack.remove_at(0);
+
+ if (this.undo_stack.is_empty) {
+ this.can_undo = false;
+ }
+
+ try {
+ yield target.undo(cancellable);
+ } catch (Error err) {
+ this.redo_stack.clear();
+ this.can_redo = false;
+ throw err;
+ }
+
+ this.redo_stack.insert(0, target);
+ this.can_redo = true;
+ undone(target);
+ }
+ }
+
+ /**
+ * Pops a command off the redo stack and re-applies it.
+ *
+ * This calls {@link Command.redo} on the topmost command on the
+ * redo stack and if no error is thrown, pushes it on the undo
+ * stack. If an error is thrown, the command is discarded and the
+ * redo stack is emptied.
+ */
+ public async void redo(GLib.Cancellable? cancellable)
+ throws GLib.Error {
+ if (!this.redo_stack.is_empty) {
+ Command target = this.redo_stack.remove_at(0);
+
+ if (this.redo_stack.is_empty) {
+ this.can_redo = false;
+ }
+
+ try {
+ yield target.redo(cancellable);
+ } catch (Error err) {
+ this.redo_stack.clear();
+ this.can_redo = false;
+ throw err;
+ }
+
+ this.undo_stack.insert(0, target);
+ this.can_undo = true;
+ redone(target);
+ }
+ }
+
+ /** Returns the command at the top of the undo stack, if any. */
+ public Command? peek_undo() {
+ return this.undo_stack.is_empty ? null : this.undo_stack[0];
+ }
+
+ /** Returns the command at the top of the redo stack, if any. */
+ public Command? peek_redo() {
+ return this.redo_stack.is_empty ? null : this.redo_stack[0];
+ }
+
+ /** Clears all commands from both the undo and redo stacks. */
+ public void clear() {
+ this.undo_stack.clear();
+ this.can_undo = false;
+ this.redo_stack.clear();
+ this.can_redo = false;
+ }
+
+}
diff --git a/src/client/meson.build b/src/client/meson.build
index e106bb86..62caec66 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -2,6 +2,7 @@
geary_client_vala_sources = files(
'application/application-avatar-store.vala',
'application/autostart-manager.vala',
+ 'application/application-command.vala',
'application/geary-application.vala',
'application/geary-args.vala',
'application/geary-config.vala',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]