Implement an application-level command stack

This will provide the basis for handling undo/redo in the main window.
This commit is contained in:
Michael Gratton 2019-10-05 10:49:16 +10:00 committed by Michael James Gratton
parent 8e9f00295e
commit 88eb8f5040
2 changed files with 76 additions and 2 deletions

View file

@ -127,6 +127,8 @@ public class Application.Controller : Geary.BaseObject {
// Null if no folder ever selected
private Geary.Account? current_account = null;
private Application.CommandStack commands { get; protected set; }
private Cancellable cancellable_folder = new Cancellable();
private Cancellable cancellable_search = new Cancellable();
private Cancellable cancellable_open_account = new Cancellable();
@ -207,8 +209,10 @@ public class Application.Controller : Geary.BaseObject {
);
this.plugin_manager.load();
this.commands = new CommandStack();
// Create the main window (must be done after creating actions.)
main_window = new MainWindow(this.application);
main_window = new MainWindow(this.application, this.commands);
main_window.retry_service_problem.connect(on_retry_service_problem);
main_window.notify["has-toplevel-focus"].connect(on_has_toplevel_focus);
@ -292,6 +296,36 @@ public class Application.Controller : Geary.BaseObject {
this.expunge_accounts.begin();
}
/** Un-does the last executed application command, if any. */
public async void undo() {
this.commands.undo.begin(
this.open_cancellable,
(obj, res) => {
try {
this.commands.undo.end(res);
} catch (GLib.Error err) {
// XXX extract account info somehow
report_problem(new Geary.ProblemReport(err));
}
}
);
}
/** Re-does the last undone application command, if any. */
public async void redo() {
this.commands.redo.begin(
this.open_cancellable,
(obj, res) => {
try {
this.commands.redo.end(res);
} catch (GLib.Error err) {
// XXX extract account info somehow
report_problem(new Geary.ProblemReport(err));
}
}
);
}
/** Closes all accounts and windows, releasing held resources. */
public async void close_async() {
// Cancel internal processes early so they don't block

View file

@ -205,6 +205,8 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
private Geary.TimeoutManager update_ui_timeout;
private int64 update_ui_last = 0;
private Application.CommandStack commands { get; protected set; }
[GtkChild]
private Gtk.Box main_layout;
@ -250,7 +252,8 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
public signal void on_shift_key(bool pressed);
public MainWindow(GearyApplication application) {
public MainWindow(GearyApplication application,
Application.CommandStack commands) {
Object(
application: application,
show_menubar: false
@ -265,6 +268,12 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
setup_layout(application.config);
on_change_orientation();
this.commands = commands;
this.commands.executed.connect(on_command_execute);
this.commands.undone.connect(on_command_undo);
this.commands.redone.connect(on_command_execute);
update_command_actions();
this.application.engine.account_available.connect(on_account_available);
this.application.engine.account_unavailable.connect(on_account_unavailable);
@ -674,6 +683,15 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
update_headerbar();
}
private void update_command_actions() {
get_action(GearyApplication.ACTION_UNDO).set_enabled(
this.commands.can_undo
);
get_action(GearyApplication.ACTION_REDO).set_enabled(
this.commands.can_redo
);
}
private void update_ui() {
// Only update if we haven't done so within the last while
int64 now = GLib.get_monotonic_time() / (1000 * 1000);
@ -1030,12 +1048,34 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
update_ui();
}
private void on_command_execute(Application.Command command) {
if (command.executed_label != null) {
Components.InAppNotification ian =
new Components.InAppNotification(command.executed_label);
ian.set_button(_("Undo"), "win." + GearyApplication.ACTION_UNDO);
add_notification(ian);
}
update_command_actions();
}
private void on_command_undo(Application.Command command) {
if (command.undone_label != null) {
Components.InAppNotification ian =
new Components.InAppNotification(command.undone_label);
ian.set_button(_("Redo"), "win." + GearyApplication.ACTION_REDO);
add_notification(ian);
}
update_command_actions();
}
// Action callbacks
private void on_undo() {
this.application.controller.undo.begin();
}
private void on_redo() {
this.application.controller.redo.begin();
}
private void on_close() {