Merge branch 'mjog/233-entry-undo' into 'mainline'
Application-wide GtkEntry undo support Closes #233 See merge request GNOME/geary!360
This commit is contained in:
commit
63f626e050
24 changed files with 836 additions and 295 deletions
|
|
@ -93,7 +93,8 @@
|
|||
<description>
|
||||
<p>Enhancements included in this release:</p>
|
||||
<ul>
|
||||
<li>Improved and pervasive undo support for email actions</li>
|
||||
<li>Unlimited undo for all email actions such as archiving, marking</li>
|
||||
<li>Undo support for all text entry fields, including the composer</li>
|
||||
<li>App-wide notification preferences now handled by desktop</li>
|
||||
<li>Improved missing attachment detection in composer</li>
|
||||
<li>Initial plugin system</li>
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ src/client/application/geary-application.vala
|
|||
src/client/application/goa-mediator.vala
|
||||
src/client/application/main.vala
|
||||
src/client/application/secret-mediator.vala
|
||||
src/client/client-action.vala
|
||||
src/client/components/client-web-view.vala
|
||||
src/client/components/components-attachment-pane.vala
|
||||
src/client/components/components-entry-undo.vala
|
||||
src/client/components/components-in-app-notification.vala
|
||||
src/client/components/components-inspector.vala
|
||||
src/client/components/components-placeholder-pane.vala
|
||||
|
|
|
|||
|
|
@ -517,11 +517,19 @@ private abstract class Accounts.AddPaneRow<Value> :
|
|||
private abstract class Accounts.EntryRow : AddPaneRow<Gtk.Entry> {
|
||||
|
||||
|
||||
protected EntryRow(string label, string? placeholder = null) {
|
||||
private Components.EntryUndo undo;
|
||||
|
||||
|
||||
protected EntryRow(string label,
|
||||
string? initial_value = null,
|
||||
string? placeholder = null) {
|
||||
base(label, new Gtk.Entry());
|
||||
|
||||
this.value.text = initial_value ?? "";
|
||||
this.value.placeholder_text = placeholder ?? "";
|
||||
this.value.width_chars = 32;
|
||||
|
||||
this.undo = new Components.EntryUndo(this.value);
|
||||
}
|
||||
|
||||
public override bool focus(Gtk.DirectionType direction) {
|
||||
|
|
@ -548,12 +556,12 @@ private class Accounts.NameRow : EntryRow {
|
|||
public NameRow(string default_name) {
|
||||
// Translators: Label for the person's actual name when adding
|
||||
// an account
|
||||
base(_("Your name"));
|
||||
base(_("Your name"), default_name.strip());
|
||||
this.validator = new Components.Validator(this.value);
|
||||
if (default_name.strip() != "") {
|
||||
// Set the text after hooking up the validator, so if the
|
||||
// string is non-null it will already be valid
|
||||
this.value.set_text(default_name);
|
||||
if (this.value.text != "") {
|
||||
// Validate if the string is non-empty so it will be good
|
||||
// to go from the start
|
||||
this.value.activate();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -566,6 +574,7 @@ private class Accounts.EmailRow : EntryRow {
|
|||
public EmailRow() {
|
||||
base(
|
||||
_("Email address"),
|
||||
null,
|
||||
// Translators: Placeholder for the default sender address
|
||||
// when adding an account
|
||||
_("person@example.com")
|
||||
|
|
@ -634,7 +643,7 @@ private class Accounts.HostnameRow : EntryRow {
|
|||
break;
|
||||
}
|
||||
|
||||
base(label, placeholder);
|
||||
base(label, null, placeholder);
|
||||
this.type = type;
|
||||
|
||||
this.validator = new Components.NetworkAddressValidator(this.value, 0);
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ internal class Accounts.EditorEditPane :
|
|||
private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
|
||||
|
||||
|
||||
private Components.EntryUndo value_undo;
|
||||
private Application.CommandStack commands;
|
||||
private GLib.Cancellable? cancellable;
|
||||
|
||||
|
|
@ -284,12 +285,19 @@ private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
|
|||
|
||||
update();
|
||||
|
||||
// Hook up after updating the value so the default value isn't
|
||||
// undoable
|
||||
this.value_undo = new Components.EntryUndo(this.value);
|
||||
|
||||
this.value.focus_out_event.connect(on_focus_out);
|
||||
}
|
||||
|
||||
public override void update() {
|
||||
this.value.set_placeholder_text(this.account.primary_mailbox.address);
|
||||
this.value.set_text(this.account.display_name);
|
||||
this.value.placeholder_text = this.account.primary_mailbox.address;
|
||||
// Only update if changed to avoid adding more undo edits
|
||||
if (this.value.text != this.account.display_name) {
|
||||
this.value.text = this.account.display_name;
|
||||
}
|
||||
}
|
||||
|
||||
private void commit() {
|
||||
|
|
@ -434,7 +442,9 @@ internal class Accounts.MailboxEditorPopover : EditorPopover {
|
|||
|
||||
|
||||
private Gtk.Entry name_entry = new Gtk.Entry();
|
||||
private Components.EntryUndo name_undo;
|
||||
private Gtk.Entry address_entry = new Gtk.Entry();
|
||||
private Components.EntryUndo address_undo;
|
||||
private Components.EmailValidator address_validator;
|
||||
private Gtk.Button remove_button;
|
||||
|
||||
|
|
@ -460,6 +470,8 @@ internal class Accounts.MailboxEditorPopover : EditorPopover {
|
|||
this.name_entry.activate.connect(on_activate);
|
||||
this.name_entry.show();
|
||||
|
||||
this.name_undo = new Components.EntryUndo(this.name_entry);
|
||||
|
||||
this.address_entry.input_purpose = Gtk.InputPurpose.EMAIL;
|
||||
this.address_entry.set_text(address ?? "");
|
||||
this.address_entry.set_placeholder_text(
|
||||
|
|
@ -473,6 +485,8 @@ internal class Accounts.MailboxEditorPopover : EditorPopover {
|
|||
this.address_entry.activate.connect(on_activate);
|
||||
this.address_entry.show();
|
||||
|
||||
this.address_undo = new Components.EntryUndo(this.address_entry);
|
||||
|
||||
this.address_validator =
|
||||
new Components.EmailValidator(this.address_entry);
|
||||
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
|||
if (command.executed_label != null) {
|
||||
Components.InAppNotification ian =
|
||||
new Components.InAppNotification(command.executed_label);
|
||||
ian.set_button(_("Undo"), "win." + GearyApplication.ACTION_UNDO);
|
||||
ian.set_button(_("Undo"), Action.Edit.prefix(Action.Edit.UNDO));
|
||||
this.editor.add_notification(ian);
|
||||
}
|
||||
}
|
||||
|
|
@ -254,7 +254,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
|||
if (command.undone_label != null) {
|
||||
Components.InAppNotification ian =
|
||||
new Components.InAppNotification(command.undone_label);
|
||||
ian.set_button(_("Redo"), "win." + GearyApplication.ACTION_REDO);
|
||||
ian.set_button(_("Redo"), Action.Edit.prefix(Action.Edit.REDO));
|
||||
this.editor.add_notification(ian);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -711,6 +711,7 @@ private class Accounts.ServiceHostRow :
|
|||
}
|
||||
}
|
||||
|
||||
private Components.EntryUndo value_undo;
|
||||
private Application.CommandStack commands;
|
||||
private GLib.Cancellable? cancellable;
|
||||
|
||||
|
|
@ -741,9 +742,11 @@ private class Accounts.ServiceHostRow :
|
|||
this.validator = new Components.NetworkAddressValidator(this.value);
|
||||
|
||||
// Update after the validator is wired up to ensure the value
|
||||
// is validated
|
||||
// is validated, wire up undo after updating so the default
|
||||
// value isn't undoable.
|
||||
setup_validator();
|
||||
update();
|
||||
this.value_undo = new Components.EntryUndo(this.value);
|
||||
}
|
||||
|
||||
public override void update() {
|
||||
|
|
@ -862,6 +865,7 @@ private class Accounts.ServiceLoginRow :
|
|||
}
|
||||
}
|
||||
|
||||
private Components.EntryUndo value_undo;
|
||||
private Application.CommandStack commands;
|
||||
private GLib.Cancellable? cancellable;
|
||||
private ServicePasswordRow? password_row;
|
||||
|
|
@ -894,9 +898,11 @@ private class Accounts.ServiceLoginRow :
|
|||
}
|
||||
|
||||
// Update after the validator is wired up to ensure the value
|
||||
// is validated
|
||||
update();
|
||||
// is validated, wire up undo after updating so the default
|
||||
// value isn't undoable.
|
||||
setup_validator();
|
||||
update();
|
||||
this.value_undo = new Components.EntryUndo(this.value);
|
||||
}
|
||||
|
||||
public override void update() {
|
||||
|
|
@ -983,6 +989,7 @@ private class Accounts.ServicePasswordRow :
|
|||
}
|
||||
}
|
||||
|
||||
private Components.EntryUndo value_undo;
|
||||
private Application.CommandStack commands;
|
||||
private GLib.Cancellable? cancellable;
|
||||
|
||||
|
|
@ -1008,9 +1015,11 @@ private class Accounts.ServicePasswordRow :
|
|||
this.validator = new Components.Validator(this.value);
|
||||
|
||||
// Update after the validator is wired up to ensure the value
|
||||
// is validated
|
||||
update();
|
||||
// is validated, wire up undo after updating so the default
|
||||
// value isn't undoable.
|
||||
setup_validator();
|
||||
update();
|
||||
this.value_undo = new Components.EntryUndo(this.value);
|
||||
}
|
||||
|
||||
public override void update() {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@
|
|||
public class Accounts.Editor : Gtk.Dialog {
|
||||
|
||||
|
||||
private const ActionEntry[] ACTION_ENTRIES = {
|
||||
{ GearyApplication.ACTION_REDO, on_redo },
|
||||
{ GearyApplication.ACTION_UNDO, on_undo },
|
||||
private const ActionEntry[] EDIT_ACTIONS = {
|
||||
{ Action.Edit.REDO, on_redo },
|
||||
{ Action.Edit.UNDO, on_undo },
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ public class Accounts.Editor : Gtk.Dialog {
|
|||
get; private set;
|
||||
}
|
||||
|
||||
private SimpleActionGroup actions = new SimpleActionGroup();
|
||||
private GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.Overlay notifications_pane;
|
||||
|
|
@ -67,8 +67,8 @@ public class Accounts.Editor : Gtk.Dialog {
|
|||
|
||||
this.accounts = application.controller.account_manager;
|
||||
|
||||
this.actions.add_action_entries(ACTION_ENTRIES, this);
|
||||
insert_action_group("win", this.actions);
|
||||
this.edit_actions.add_action_entries(EDIT_ACTIONS, this);
|
||||
insert_action_group(Action.Edit.GROUP_NAME, this.edit_actions);
|
||||
|
||||
this.editor_list_pane = new EditorListPane(this);
|
||||
push(this.editor_list_pane);
|
||||
|
|
@ -227,8 +227,8 @@ public class Accounts.Editor : Gtk.Dialog {
|
|||
can_redo = pane.commands.can_redo;
|
||||
}
|
||||
|
||||
get_action(GearyApplication.ACTION_UNDO).set_enabled(can_undo);
|
||||
get_action(GearyApplication.ACTION_REDO).set_enabled(can_redo);
|
||||
get_action(Action.Edit.UNDO).set_enabled(can_undo);
|
||||
get_action(Action.Edit.REDO).set_enabled(can_redo);
|
||||
}
|
||||
|
||||
private inline EditorPane? get_current_pane() {
|
||||
|
|
@ -236,7 +236,7 @@ public class Accounts.Editor : Gtk.Dialog {
|
|||
}
|
||||
|
||||
private inline GLib.SimpleAction get_action(string name) {
|
||||
return (GLib.SimpleAction) this.actions.lookup_action(name);
|
||||
return (GLib.SimpleAction) this.edit_actions.lookup_action(name);
|
||||
}
|
||||
|
||||
private void on_undo() {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ extern const string _PROFILE;
|
|||
extern const string _VERSION;
|
||||
extern const string _REVNO;
|
||||
|
||||
|
||||
/**
|
||||
* The interface between Geary and the desktop environment.
|
||||
*/
|
||||
|
|
@ -52,25 +53,6 @@ public class GearyApplication : Gtk.Application {
|
|||
null
|
||||
};
|
||||
|
||||
// Common window actions
|
||||
public const string ACTION_CLOSE = "close";
|
||||
public const string ACTION_COPY = "copy";
|
||||
public const string ACTION_HELP_OVERLAY = "show-help-overlay";
|
||||
public const string ACTION_REDO = "redo";
|
||||
public const string ACTION_UNDO = "undo";
|
||||
|
||||
// App-wide actions
|
||||
public const string ACTION_ABOUT = "about";
|
||||
public const string ACTION_ACCOUNTS = "accounts";
|
||||
public const string ACTION_COMPOSE = "compose";
|
||||
public const string ACTION_INSPECT = "inspect";
|
||||
public const string ACTION_HELP = "help";
|
||||
public const string ACTION_MAILTO = "mailto";
|
||||
public const string ACTION_PREFERENCES = "preferences";
|
||||
public const string ACTION_SHOW_EMAIL = "show-email";
|
||||
public const string ACTION_SHOW_FOLDER = "show-folder";
|
||||
public const string ACTION_QUIT = "quit";
|
||||
|
||||
// Local-only command line options
|
||||
private const string OPTION_VERSION = "version";
|
||||
|
||||
|
|
@ -90,16 +72,16 @@ public class GearyApplication : Gtk.Application {
|
|||
private const string OPTION_REVOKE_CERTS = "revoke-certs";
|
||||
|
||||
private const ActionEntry[] ACTION_ENTRIES = {
|
||||
{ACTION_ABOUT, on_activate_about},
|
||||
{ACTION_ACCOUNTS, on_activate_accounts},
|
||||
{ACTION_COMPOSE, on_activate_compose},
|
||||
{ACTION_HELP, on_activate_help},
|
||||
{ACTION_INSPECT, on_activate_inspect},
|
||||
{ACTION_MAILTO, on_activate_mailto, "s"},
|
||||
{ACTION_PREFERENCES, on_activate_preferences},
|
||||
{ACTION_QUIT, on_activate_quit},
|
||||
{ACTION_SHOW_EMAIL, on_activate_show_email, "(svv)"},
|
||||
{ACTION_SHOW_FOLDER, on_activate_show_folder, "(sv)"}
|
||||
{ Action.Application.ABOUT, on_activate_about},
|
||||
{ Action.Application.ACCOUNTS, on_activate_accounts},
|
||||
{ Action.Application.COMPOSE, on_activate_compose},
|
||||
{ Action.Application.HELP, on_activate_help},
|
||||
{ Action.Application.INSPECT, on_activate_inspect},
|
||||
{ Action.Application.MAILTO, on_activate_mailto, "s"},
|
||||
{ Action.Application.PREFERENCES, on_activate_preferences},
|
||||
{ Action.Application.QUIT, on_activate_quit},
|
||||
{ Action.Application.SHOW_EMAIL, on_activate_show_email, "(svv)"},
|
||||
{ Action.Application.SHOW_FOLDER, on_activate_show_folder, "(sv)"}
|
||||
};
|
||||
|
||||
// This is also the order in which they are presented to the user,
|
||||
|
|
@ -435,21 +417,26 @@ public class GearyApplication : Gtk.Application {
|
|||
Gtk.Window.set_default_icon_name(APP_ID);
|
||||
|
||||
// Application accels
|
||||
add_app_accelerators(ACTION_COMPOSE, { "<Ctrl>N" });
|
||||
add_app_accelerators(ACTION_HELP, { "F1" });
|
||||
add_app_accelerators(ACTION_INSPECT, { "<Alt><Shift>I" });
|
||||
add_app_accelerators(ACTION_QUIT, { "<Ctrl>Q" });
|
||||
add_app_accelerators(Action.Application.COMPOSE, { "<Ctrl>N" });
|
||||
add_app_accelerators(Action.Application.HELP, { "F1" });
|
||||
add_app_accelerators(Action.Application.INSPECT, { "<Alt><Shift>I" });
|
||||
add_app_accelerators(Action.Application.QUIT, { "<Ctrl>Q" });
|
||||
|
||||
// Common window accels
|
||||
add_window_accelerators(ACTION_CLOSE, { "<Ctrl>W" });
|
||||
add_window_accelerators(ACTION_COPY, { "<Ctrl>C" });
|
||||
add_window_accelerators(ACTION_HELP_OVERLAY, { "<Ctrl>F1", "<Ctrl>question" });
|
||||
add_window_accelerators(ACTION_REDO, { "<Ctrl><Shift>Z" });
|
||||
add_window_accelerators(ACTION_UNDO, { "<Ctrl>Z" });
|
||||
add_window_accelerators(Action.Window.CLOSE, { "<Ctrl>W" });
|
||||
add_window_accelerators(
|
||||
Action.Window.SHORTCUT_HELP, { "<Ctrl>F1", "<Ctrl>question" }
|
||||
);
|
||||
|
||||
MainWindow.add_window_accelerators(this);
|
||||
ComposerWidget.add_window_accelerators(this);
|
||||
Components.Inspector.add_window_accelerators(this);
|
||||
// Common edit accels
|
||||
add_edit_accelerators(Action.Edit.COPY, { "<Ctrl>C" });
|
||||
add_edit_accelerators(Action.Edit.REDO, { "<Ctrl><Shift>Z" });
|
||||
add_edit_accelerators(Action.Edit.UNDO, { "<Ctrl>Z" });
|
||||
|
||||
MainWindow.add_accelerators(this);
|
||||
ComposerWidget.add_accelerators(this);
|
||||
Components.Inspector.add_accelerators(this);
|
||||
Dialogs.ProblemDetailsDialog.add_accelerators(this);
|
||||
|
||||
if (this.is_background_service) {
|
||||
// Since command_line won't be called below if running as
|
||||
|
|
@ -494,7 +481,18 @@ public class GearyApplication : Gtk.Application {
|
|||
public void add_window_accelerators(string action,
|
||||
string[] accelerators,
|
||||
Variant? param = null) {
|
||||
string name = "win." + action;
|
||||
string name = Action.Window.prefix(action);
|
||||
string[] all_accel = get_accels_for_action(name);
|
||||
foreach (string accel in accelerators) {
|
||||
all_accel += accel;
|
||||
}
|
||||
set_accels_for_action(name, all_accel);
|
||||
}
|
||||
|
||||
public void add_edit_accelerators(string action,
|
||||
string[] accelerators,
|
||||
Variant? param = null) {
|
||||
string name = Action.Edit.prefix(action);
|
||||
string[] all_accel = get_accels_for_action(name);
|
||||
foreach (string accel in accelerators) {
|
||||
all_accel += accel;
|
||||
|
|
@ -875,13 +873,10 @@ public class GearyApplication : Gtk.Application {
|
|||
foreach (string arg in args) {
|
||||
// the only acceptable arguments are mailto:'s
|
||||
if (arg == MAILTO_URI_SCHEME_PREFIX) {
|
||||
activate_action(GearyApplication.ACTION_COMPOSE, null);
|
||||
activate_action(Action.Application.COMPOSE, null);
|
||||
activated = true;
|
||||
} else if (arg.down().has_prefix(MAILTO_URI_SCHEME_PREFIX)) {
|
||||
activate_action(
|
||||
GearyApplication.ACTION_MAILTO,
|
||||
new GLib.Variant.string(arg)
|
||||
);
|
||||
activate_action(Action.Application.MAILTO, new GLib.Variant.string(arg));
|
||||
activated = true;
|
||||
} else {
|
||||
command_line.printerr("%s: ", this.binary);
|
||||
|
|
|
|||
73
src/client/client-action.vala
Normal file
73
src/client/client-action.vala
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/** Common client GAction and action group names */
|
||||
namespace Action {
|
||||
|
||||
|
||||
/** Common application GAction names. */
|
||||
namespace Application {
|
||||
|
||||
/** Application GAction group name */
|
||||
public const string GROUP_NAME = "app";
|
||||
|
||||
public const string ABOUT = "about";
|
||||
public const string ACCOUNTS = "accounts";
|
||||
public const string COMPOSE = "compose";
|
||||
public const string INSPECT = "inspect";
|
||||
public const string HELP = "help";
|
||||
public const string MAILTO = "mailto";
|
||||
public const string PREFERENCES = "preferences";
|
||||
public const string SHOW_EMAIL = "show-email";
|
||||
public const string SHOW_FOLDER = "show-folder";
|
||||
public const string QUIT = "quit";
|
||||
|
||||
|
||||
/** Returns the given action name prefixed with the group name. */
|
||||
public string prefix(string action_name) {
|
||||
return GROUP_NAME + "." + action_name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Common window GAction names. */
|
||||
namespace Window {
|
||||
|
||||
/** Window GAction group name */
|
||||
public const string GROUP_NAME = "win";
|
||||
|
||||
public const string CLOSE = "close";
|
||||
public const string SHORTCUT_HELP = "show-help-overlay";
|
||||
|
||||
|
||||
/** Returns the given action name prefixed with the group name. */
|
||||
public string prefix(string action_name) {
|
||||
return GROUP_NAME + "." + action_name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Common editing GAction names. */
|
||||
namespace Edit {
|
||||
|
||||
/** Editing GAction group name */
|
||||
public const string GROUP_NAME = "edt";
|
||||
|
||||
public const string COPY = "copy";
|
||||
public const string REDO = "redo";
|
||||
public const string UNDO = "undo";
|
||||
|
||||
|
||||
/** Returns the given action name prefixed with the group name. */
|
||||
public string prefix(string action_name) {
|
||||
return GROUP_NAME + "." + action_name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
333
src/client/components/components-entry-undo.vala
Normal file
333
src/client/components/components-entry-undo.vala
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides per-GTK Entry undo and redo using a command stack.
|
||||
*/
|
||||
public class Components.EntryUndo : Geary.BaseObject {
|
||||
|
||||
|
||||
private const ActionEntry[] EDIT_ACTIONS = {
|
||||
{ Action.Edit.UNDO, on_undo },
|
||||
{ Action.Edit.REDO, on_redo },
|
||||
};
|
||||
|
||||
|
||||
private enum EditType { NONE, INSERT, DELETE; }
|
||||
|
||||
|
||||
private class EditCommand : Application.Command {
|
||||
|
||||
|
||||
private weak EntryUndo manager;
|
||||
private EditType edit;
|
||||
private int position;
|
||||
private string text;
|
||||
|
||||
|
||||
public EditCommand(EntryUndo manager,
|
||||
EditType edit,
|
||||
int position,
|
||||
string text) {
|
||||
this.manager = manager;
|
||||
this.edit = edit;
|
||||
this.position = position;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public override async void execute(GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
// No-op, has already been executed
|
||||
}
|
||||
|
||||
public override async void undo(GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
EntryUndo? manager = this.manager;
|
||||
if (manager != null) {
|
||||
manager.events_enabled = false;
|
||||
switch (this.edit) {
|
||||
case INSERT:
|
||||
do_delete(manager.target);
|
||||
break;
|
||||
case DELETE:
|
||||
do_insert(manager.target);
|
||||
break;
|
||||
}
|
||||
manager.events_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override async void redo(GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
EntryUndo? manager = this.manager;
|
||||
if (manager != null) {
|
||||
manager.events_enabled = false;
|
||||
switch (this.edit) {
|
||||
case INSERT:
|
||||
do_insert(manager.target);
|
||||
break;
|
||||
case DELETE:
|
||||
do_delete(manager.target);
|
||||
break;
|
||||
}
|
||||
manager.events_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void do_insert(Gtk.Entry target) {
|
||||
int position = this.position;
|
||||
target.insert_text(this.text, -1, ref position);
|
||||
target.set_position(position);
|
||||
}
|
||||
|
||||
private void do_delete(Gtk.Entry target) {
|
||||
target.delete_text(
|
||||
this.position, this.position + this.text.char_count()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** The entry being managed */
|
||||
public Gtk.Entry target { get; private set; }
|
||||
|
||||
private Application.CommandStack commands;
|
||||
private EditType last_edit = NONE;
|
||||
private int edit_start = 0;
|
||||
private int edit_end = 0;
|
||||
private GLib.StringBuilder edit_accumuluator = new GLib.StringBuilder();
|
||||
|
||||
private bool events_enabled = true;
|
||||
|
||||
private GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
||||
|
||||
|
||||
public EntryUndo(Gtk.Entry target) {
|
||||
this.edit_actions.add_action_entries(EDIT_ACTIONS, this);
|
||||
|
||||
this.target = target;
|
||||
this.target.insert_action_group(Action.Edit.GROUP_NAME, this.edit_actions);
|
||||
this.target.insert_text.connect(on_inserted);
|
||||
this.target.delete_text.connect(on_deleted);
|
||||
|
||||
this.commands = new Application.CommandStack();
|
||||
this.commands.executed.connect(this.update_command_actions);
|
||||
this.commands.undone.connect(this.update_command_actions);
|
||||
this.commands.redone.connect(this.update_command_actions);
|
||||
}
|
||||
|
||||
~EntryUndo() {
|
||||
this.target.insert_text.disconnect(on_inserted);
|
||||
this.target.delete_text.disconnect(on_deleted);
|
||||
}
|
||||
|
||||
/** Resets the editing stack for the target entry. */
|
||||
public void reset() {
|
||||
this.last_edit = NONE;
|
||||
this.commands.clear();
|
||||
}
|
||||
|
||||
private void execute(Application.Command command) {
|
||||
bool complete = false;
|
||||
this.commands.execute.begin(
|
||||
command,
|
||||
null,
|
||||
(obj, res) => {
|
||||
try {
|
||||
this.commands.execute.end(res);
|
||||
} catch (GLib.Error thrown) {
|
||||
debug(
|
||||
"Failed to execute entry edit command: %s",
|
||||
thrown.message
|
||||
);
|
||||
}
|
||||
complete = true;
|
||||
}
|
||||
);
|
||||
while (!complete) {
|
||||
Gtk.main_iteration();
|
||||
}
|
||||
}
|
||||
|
||||
private void do_undo() {
|
||||
flush_command();
|
||||
bool complete = false;
|
||||
this.commands.undo.begin(
|
||||
null,
|
||||
(obj, res) => {
|
||||
try {
|
||||
this.commands.undo.end(res);
|
||||
} catch (GLib.Error thrown) {
|
||||
debug(
|
||||
"Failed to undo entry edit command: %s",
|
||||
thrown.message
|
||||
);
|
||||
}
|
||||
complete = true;
|
||||
}
|
||||
);
|
||||
while (!complete) {
|
||||
Gtk.main_iteration();
|
||||
}
|
||||
}
|
||||
|
||||
private void do_redo() {
|
||||
flush_command();
|
||||
bool complete = false;
|
||||
this.commands.redo.begin(
|
||||
null,
|
||||
(obj, res) => {
|
||||
try {
|
||||
this.commands.redo.end(res);
|
||||
} catch (GLib.Error thrown) {
|
||||
debug(
|
||||
"Failed to undo entry edit command: %s",
|
||||
thrown.message
|
||||
);
|
||||
}
|
||||
complete = true;
|
||||
}
|
||||
);
|
||||
while (!complete) {
|
||||
Gtk.main_iteration();
|
||||
}
|
||||
}
|
||||
|
||||
private void flush_command() {
|
||||
EditCommand? command = extract_command();
|
||||
if (command != null) {
|
||||
execute(command);
|
||||
}
|
||||
}
|
||||
|
||||
private EditCommand? extract_command() {
|
||||
EditCommand? command = null;
|
||||
if (this.last_edit != NONE) {
|
||||
command = new EditCommand(
|
||||
this,
|
||||
this.last_edit,
|
||||
this.edit_start,
|
||||
this.edit_accumuluator.str
|
||||
);
|
||||
this.edit_accumuluator.truncate();
|
||||
}
|
||||
this.last_edit = NONE;
|
||||
return command;
|
||||
}
|
||||
|
||||
private void update_command_actions() {
|
||||
((GLib.SimpleAction) this.edit_actions.lookup_action(Action.Edit.UNDO))
|
||||
.set_enabled(this.commands.can_undo);
|
||||
((GLib.SimpleAction) this.edit_actions.lookup_action(Action.Edit.REDO))
|
||||
.set_enabled(this.commands.can_redo);
|
||||
}
|
||||
|
||||
private void on_inserted(string inserted, int inserted_len, ref int pos) {
|
||||
if (this.events_enabled) {
|
||||
// Normalise to something useful
|
||||
inserted_len = inserted.char_count();
|
||||
|
||||
bool is_non_trivial = inserted_len > 1;
|
||||
bool insert_handled = false;
|
||||
if (this.last_edit == DELETE) {
|
||||
Application.Command? command = extract_command();
|
||||
if (command != null &&
|
||||
this.edit_start == pos &&
|
||||
is_non_trivial) {
|
||||
// Delete followed by a non-trivial insert at the
|
||||
// same position indicates something was probably
|
||||
// pasted/spellchecked/completed/etc, so execute
|
||||
// together as a single command.
|
||||
this.last_edit = INSERT;
|
||||
this.edit_start = pos;
|
||||
this.edit_accumuluator.append(inserted);
|
||||
command = new Application.CommandSequence({
|
||||
command, extract_command()
|
||||
});
|
||||
insert_handled = true;
|
||||
}
|
||||
if (command != null) {
|
||||
execute(command);
|
||||
}
|
||||
}
|
||||
|
||||
if (!insert_handled) {
|
||||
bool is_disjoint_edit = (
|
||||
this.last_edit == INSERT && this.edit_end != pos
|
||||
);
|
||||
bool is_non_alpha_num = (
|
||||
inserted_len == 1 && !inserted.get_char(0).isalnum()
|
||||
);
|
||||
|
||||
// Flush any existing edits if any of the special
|
||||
// cases hold
|
||||
if (is_disjoint_edit || is_non_alpha_num || is_non_trivial) {
|
||||
flush_command();
|
||||
}
|
||||
|
||||
if (this.last_edit == NONE) {
|
||||
this.last_edit = INSERT;
|
||||
this.edit_start = pos;
|
||||
this.edit_end = pos;
|
||||
}
|
||||
|
||||
this.edit_end += inserted_len;
|
||||
this.edit_accumuluator.append(inserted);
|
||||
|
||||
// Flush the new edit if we don't want to coalesce
|
||||
// with subsequent inserts
|
||||
if (is_non_alpha_num || is_non_trivial) {
|
||||
flush_command();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void on_deleted(int start, int end) {
|
||||
if (this.events_enabled) {
|
||||
// Normalise value of end to be something useful if needed
|
||||
string text = this.target.buffer.get_text();
|
||||
if (end < 0) {
|
||||
end = text.char_count();
|
||||
}
|
||||
|
||||
// Don't flush non-trivial deletes since we want to be
|
||||
// able to combine them with non-trivial inserts for
|
||||
// better handling of pasting/spell-checking
|
||||
// replacement/etc.
|
||||
bool is_disjoint_edit = (
|
||||
this.last_edit == DELETE && this.edit_start != end
|
||||
);
|
||||
if (this.last_edit == INSERT || is_disjoint_edit) {
|
||||
flush_command();
|
||||
}
|
||||
|
||||
if (this.last_edit == NONE) {
|
||||
this.last_edit = DELETE;
|
||||
this.edit_end = end;
|
||||
}
|
||||
|
||||
this.edit_start = start;
|
||||
this.edit_accumuluator.prepend(
|
||||
text.slice(
|
||||
text.index_of_nth_char(start),
|
||||
text.index_of_nth_char(end)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_undo() {
|
||||
do_undo();
|
||||
}
|
||||
|
||||
private void on_redo() {
|
||||
do_redo();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,16 +28,20 @@ public class Components.Inspector : Gtk.ApplicationWindow {
|
|||
private const string ACTION_SEARCH_TOGGLE = "toggle-search";
|
||||
private const string ACTION_SEARCH_ACTIVATE = "activate-search";
|
||||
|
||||
private const ActionEntry[] action_entries = {
|
||||
{GearyApplication.ACTION_CLOSE, on_close },
|
||||
{GearyApplication.ACTION_COPY, on_copy_clicked },
|
||||
{ACTION_CLOSE, on_close },
|
||||
{ACTION_PLAY_TOGGLE, on_logs_play_toggled, null, "true" },
|
||||
{ACTION_SEARCH_TOGGLE, on_logs_search_toggled, null, "false" },
|
||||
{ACTION_SEARCH_ACTIVATE, on_logs_search_activated },
|
||||
private const ActionEntry[] EDIT_ACTIONS = {
|
||||
{ Action.Edit.COPY, on_copy_clicked },
|
||||
};
|
||||
|
||||
public static void add_window_accelerators(GearyApplication app) {
|
||||
private const ActionEntry[] WINDOW_ACTIONS = {
|
||||
{ Action.Window.CLOSE, on_close },
|
||||
{ ACTION_CLOSE, on_close },
|
||||
{ ACTION_PLAY_TOGGLE, on_logs_play_toggled, null, "true" },
|
||||
{ ACTION_SEARCH_TOGGLE, on_logs_search_toggled, null, "false" },
|
||||
{ ACTION_SEARCH_ACTIVATE, on_logs_search_activated },
|
||||
};
|
||||
|
||||
|
||||
public static void add_accelerators(GearyApplication app) {
|
||||
app.add_window_accelerators(ACTION_CLOSE, { "Escape" } );
|
||||
app.add_window_accelerators(ACTION_PLAY_TOGGLE, { "space" } );
|
||||
app.add_window_accelerators(ACTION_SEARCH_ACTIVATE, { "<Ctrl>F" } );
|
||||
|
|
@ -67,7 +71,13 @@ public class Components.Inspector : Gtk.ApplicationWindow {
|
|||
Object(application: application);
|
||||
this.title = this.header_bar.title = _("Inspector");
|
||||
|
||||
add_action_entries(Inspector.action_entries, this);
|
||||
// Edit actions
|
||||
GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
||||
edit_actions.add_action_entries(EDIT_ACTIONS, this);
|
||||
insert_action_group(Action.Edit.GROUP_NAME, edit_actions);
|
||||
|
||||
// Window actions
|
||||
add_action_entries(WINDOW_ACTIONS, this);
|
||||
|
||||
this.log_pane = new InspectorLogView(application.config, null);
|
||||
this.log_pane.record_selection_changed.connect(
|
||||
|
|
|
|||
|
|
@ -34,14 +34,13 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
public const string ACTION_TRASH_CONVERSATION = "trash-conversation";
|
||||
public const string ACTION_ZOOM = "zoom";
|
||||
|
||||
private const int STATUS_BAR_HEIGHT = 18;
|
||||
private const int UPDATE_UI_INTERVAL = 60;
|
||||
private const int MIN_CONVERSATION_COUNT = 50;
|
||||
private const ActionEntry[] EDIT_ACTIONS = {
|
||||
{ Action.Edit.UNDO, on_undo },
|
||||
{ Action.Edit.REDO, on_redo },
|
||||
};
|
||||
|
||||
private const ActionEntry[] win_action_entries = {
|
||||
{ GearyApplication.ACTION_CLOSE, on_close },
|
||||
{ GearyApplication.ACTION_UNDO, on_undo },
|
||||
{ GearyApplication.ACTION_REDO, on_redo },
|
||||
private const ActionEntry[] WINDOW_ACTIONS = {
|
||||
{ Action.Window.CLOSE, on_close },
|
||||
|
||||
{ ACTION_CONVERSATION_LIST, on_conversation_list },
|
||||
{ ACTION_FIND_IN_CONVERSATION, on_find_in_conversation_action },
|
||||
|
|
@ -70,8 +69,12 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
{ ACTION_ZOOM, on_zoom, "s" },
|
||||
};
|
||||
|
||||
private const int STATUS_BAR_HEIGHT = 18;
|
||||
private const int UPDATE_UI_INTERVAL = 60;
|
||||
private const int MIN_CONVERSATION_COUNT = 50;
|
||||
|
||||
public static void add_window_accelerators(GearyApplication owner) {
|
||||
|
||||
public static void add_accelerators(GearyApplication owner) {
|
||||
// Marking actions
|
||||
//
|
||||
// Unread is the primary action, so it doesn't get the <Shift>
|
||||
|
|
@ -209,6 +212,8 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
|
||||
private Application.Controller.AccountContext? context = null;
|
||||
|
||||
private GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
||||
|
||||
// Determines if the conversation viewer should autoselect on next
|
||||
// load
|
||||
private bool previous_selection_was_interactive = false;
|
||||
|
|
@ -277,7 +282,13 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
|
||||
load_config(application.config);
|
||||
restore_saved_window_state();
|
||||
add_action_entries(win_action_entries, this);
|
||||
|
||||
// Edit actions
|
||||
this.edit_actions.add_action_entries(EDIT_ACTIONS, this);
|
||||
insert_action_group(Action.Edit.GROUP_NAME, this.edit_actions);
|
||||
|
||||
// Window actions
|
||||
add_action_entries(MainWindow.WINDOW_ACTIONS, this);
|
||||
|
||||
set_styling();
|
||||
setup_layout(application.config);
|
||||
|
|
@ -572,7 +583,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
new ComposerWindow(composer, this.application);
|
||||
} else {
|
||||
this.conversation_viewer.do_compose(composer);
|
||||
get_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false);
|
||||
get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -893,6 +904,8 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
* ConversationWebView instances, since none of them handle
|
||||
* events.
|
||||
*
|
||||
* See also the note in EmailEntry::on_key_press.
|
||||
*
|
||||
* The work around here is completely override the default
|
||||
* implementation to reverse it. So if something related to
|
||||
* key handling breaks in the future, this might be a good
|
||||
|
|
@ -969,10 +982,10 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
|
||||
private void update_command_actions() {
|
||||
Application.Controller.AccountContext? selected = this.context;
|
||||
get_action(GearyApplication.ACTION_UNDO).set_enabled(
|
||||
get_edit_action(Action.Edit.UNDO).set_enabled(
|
||||
selected != null && selected.commands.can_undo
|
||||
);
|
||||
get_action(GearyApplication.ACTION_REDO).set_enabled(
|
||||
get_edit_action(Action.Edit.REDO).set_enabled(
|
||||
selected != null && selected.commands.can_redo
|
||||
);
|
||||
}
|
||||
|
|
@ -1379,7 +1392,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
bool sensitive = (count != NONE);
|
||||
bool multiple = (count == MULTIPLE);
|
||||
|
||||
get_action(ACTION_FIND_IN_CONVERSATION).set_enabled(
|
||||
get_window_action(ACTION_FIND_IN_CONVERSATION).set_enabled(
|
||||
sensitive && !multiple
|
||||
);
|
||||
|
||||
|
|
@ -1389,29 +1402,29 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
this.selected_folder != null &&
|
||||
this.selected_folder.special_folder_type != DRAFTS
|
||||
);
|
||||
get_action(ACTION_REPLY_CONVERSATION).set_enabled(reply_sensitive);
|
||||
get_action(ACTION_REPLY_ALL_CONVERSATION).set_enabled(reply_sensitive);
|
||||
get_action(ACTION_FORWARD_CONVERSATION).set_enabled(reply_sensitive);
|
||||
get_window_action(ACTION_REPLY_CONVERSATION).set_enabled(reply_sensitive);
|
||||
get_window_action(ACTION_REPLY_ALL_CONVERSATION).set_enabled(reply_sensitive);
|
||||
get_window_action(ACTION_FORWARD_CONVERSATION).set_enabled(reply_sensitive);
|
||||
|
||||
bool move_enabled = (
|
||||
sensitive && (selected_folder is Geary.FolderSupport.Move)
|
||||
);
|
||||
this.main_toolbar.move_message_button.set_sensitive(move_enabled);
|
||||
get_action(ACTION_SHOW_MOVE_MENU).set_enabled(move_enabled);
|
||||
get_window_action(ACTION_SHOW_MOVE_MENU).set_enabled(move_enabled);
|
||||
|
||||
bool copy_enabled = (
|
||||
sensitive && (selected_folder is Geary.FolderSupport.Copy)
|
||||
);
|
||||
this.main_toolbar.copy_message_button.set_sensitive(copy_enabled);
|
||||
get_action(ACTION_SHOW_COPY_MENU).set_enabled(move_enabled);
|
||||
get_window_action(ACTION_SHOW_COPY_MENU).set_enabled(move_enabled);
|
||||
|
||||
get_action(ACTION_ARCHIVE_CONVERSATION).set_enabled(
|
||||
get_window_action(ACTION_ARCHIVE_CONVERSATION).set_enabled(
|
||||
sensitive && (selected_folder is Geary.FolderSupport.Archive)
|
||||
);
|
||||
get_action(ACTION_TRASH_CONVERSATION).set_enabled(
|
||||
get_window_action(ACTION_TRASH_CONVERSATION).set_enabled(
|
||||
sensitive && this.selected_folder_supports_trash
|
||||
);
|
||||
get_action(ACTION_DELETE_CONVERSATION).set_enabled(
|
||||
get_window_action(ACTION_DELETE_CONVERSATION).set_enabled(
|
||||
sensitive && (selected_folder is Geary.FolderSupport.Remove)
|
||||
);
|
||||
|
||||
|
|
@ -1454,15 +1467,15 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
supported_operations.add_all(selected_operations.get_values());
|
||||
}
|
||||
|
||||
get_action(ACTION_SHOW_MARK_MENU).set_enabled(
|
||||
get_window_action(ACTION_SHOW_MARK_MENU).set_enabled(
|
||||
sensitive &&
|
||||
(typeof(Geary.FolderSupport.Mark) in supported_operations)
|
||||
);
|
||||
get_action(ACTION_SHOW_COPY_MENU).set_enabled(
|
||||
get_window_action(ACTION_SHOW_COPY_MENU).set_enabled(
|
||||
sensitive &&
|
||||
(supported_operations.contains(typeof(Geary.FolderSupport.Copy)))
|
||||
);
|
||||
get_action(ACTION_SHOW_MOVE_MENU).set_enabled(
|
||||
get_window_action(ACTION_SHOW_MOVE_MENU).set_enabled(
|
||||
sensitive &&
|
||||
(supported_operations.contains(typeof(Geary.FolderSupport.Move)))
|
||||
);
|
||||
|
|
@ -1489,10 +1502,14 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
}
|
||||
}
|
||||
|
||||
private SimpleAction get_action(string name) {
|
||||
private SimpleAction get_window_action(string name) {
|
||||
return (SimpleAction) lookup_action(name);
|
||||
}
|
||||
|
||||
private SimpleAction get_edit_action(string name) {
|
||||
return (SimpleAction) this.edit_actions.lookup_action(name);
|
||||
}
|
||||
|
||||
private void on_scan_completed(Geary.App.ConversationMonitor monitor) {
|
||||
// Done scanning. Check if we have enough messages to fill
|
||||
// the conversation list; if not, trigger a load_more();
|
||||
|
|
@ -1595,6 +1612,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
}
|
||||
|
||||
private void on_command_undo(Application.Command command) {
|
||||
update_command_actions();
|
||||
Application.EmailCommand? email = command as Application.EmailCommand;
|
||||
if (email != null) {
|
||||
if (email.conversations.size > 1) {
|
||||
|
|
@ -1610,20 +1628,19 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
if (command.undone_label != null) {
|
||||
Components.InAppNotification ian =
|
||||
new Components.InAppNotification(command.undone_label);
|
||||
ian.set_button(_("Redo"), "win." + GearyApplication.ACTION_REDO);
|
||||
ian.set_button(_("Redo"), Action.Edit.prefix(Action.Edit.REDO));
|
||||
add_notification(ian);
|
||||
}
|
||||
update_command_actions();
|
||||
}
|
||||
|
||||
private void on_command_redo(Application.Command command) {
|
||||
update_command_actions();
|
||||
if (command.executed_label != null) {
|
||||
Components.InAppNotification ian =
|
||||
new Components.InAppNotification(command.executed_label);
|
||||
ian.set_button(_("Undo"), "win." + GearyApplication.ACTION_UNDO);
|
||||
new Components.InAppNotification(command.executed_label);
|
||||
ian.set_button(_("Undo"), Action.Edit.prefix(Action.Edit.UNDO));
|
||||
add_notification(ian);
|
||||
}
|
||||
update_command_actions();
|
||||
}
|
||||
|
||||
private void on_conversation_view_added(ConversationListBox list) {
|
||||
|
|
@ -1781,14 +1798,14 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
|
|||
unstarred_selected = true;
|
||||
}
|
||||
}
|
||||
get_action(ACTION_MARK_AS_READ).set_enabled(unread_selected);
|
||||
get_action(ACTION_MARK_AS_UNREAD).set_enabled(read_selected);
|
||||
get_action(ACTION_MARK_AS_STARRED).set_enabled(unstarred_selected);
|
||||
get_action(ACTION_MARK_AS_UNSTARRED).set_enabled(starred_selected);
|
||||
get_window_action(ACTION_MARK_AS_READ).set_enabled(unread_selected);
|
||||
get_window_action(ACTION_MARK_AS_UNREAD).set_enabled(read_selected);
|
||||
get_window_action(ACTION_MARK_AS_STARRED).set_enabled(unstarred_selected);
|
||||
get_window_action(ACTION_MARK_AS_UNSTARRED).set_enabled(starred_selected);
|
||||
|
||||
// If we're in Drafts/Outbox, we also shouldn't set a message as SPAM.
|
||||
bool in_spam_folder = selected_folder.special_folder_type == Geary.SpecialFolderType.SPAM;
|
||||
get_action(ACTION_TOGGLE_SPAM).set_enabled(!in_spam_folder &&
|
||||
get_window_action(ACTION_TOGGLE_SPAM).set_enabled(!in_spam_folder &&
|
||||
selected_folder.special_folder_type != Geary.SpecialFolderType.DRAFTS &&
|
||||
selected_folder.special_folder_type != Geary.SpecialFolderType.OUTBOX);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ public class SearchBar : Gtk.SearchBar {
|
|||
public bool search_entry_has_focus { get { return search_entry.has_focus; } }
|
||||
|
||||
private Gtk.SearchEntry search_entry = new Gtk.SearchEntry();
|
||||
private Components.EntryUndo search_undo;
|
||||
private Geary.ProgressMonitor? search_upgrade_progress_monitor = null;
|
||||
private MonitoredProgressBar search_upgrade_progress_bar = new MonitoredProgressBar();
|
||||
private Geary.Account? current_account = null;
|
||||
|
|
@ -29,6 +30,10 @@ public class SearchBar : Gtk.SearchBar {
|
|||
});
|
||||
search_entry.has_focus = true;
|
||||
|
||||
this.search_undo = new Components.EntryUndo(this.search_entry);
|
||||
|
||||
this.notify["search-mode-enabled"].connect(on_search_mode_changed);
|
||||
|
||||
// Search upgrade progress bar.
|
||||
search_upgrade_progress_bar.show_text = true;
|
||||
search_upgrade_progress_bar.visible = false;
|
||||
|
|
@ -41,7 +46,7 @@ public class SearchBar : Gtk.SearchBar {
|
|||
}
|
||||
|
||||
public void set_search_text(string text) {
|
||||
search_entry.text = text;
|
||||
this.search_entry.text = text;
|
||||
}
|
||||
|
||||
public void give_search_focus() {
|
||||
|
|
@ -110,4 +115,9 @@ public class SearchBar : Gtk.SearchBar {
|
|||
_("Search %s account").printf(current_account.information.display_name));
|
||||
}
|
||||
|
||||
private void on_search_mode_changed() {
|
||||
if (!this.search_mode_enabled) {
|
||||
this.search_undo.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,70 +90,71 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
|
||||
// ACTION_INSERT_LINK and ACTION_REMOVE_FORMAT are missing from
|
||||
// here since they are handled in update_selection_actions
|
||||
private const string[] html_actions = {
|
||||
private const string[] HTML_ACTIONS = {
|
||||
ACTION_BOLD, ACTION_ITALIC, ACTION_UNDERLINE, ACTION_STRIKETHROUGH,
|
||||
ACTION_FONT_SIZE, ACTION_FONT_FAMILY, ACTION_COLOR, ACTION_JUSTIFY,
|
||||
ACTION_INSERT_IMAGE, ACTION_COPY_LINK,
|
||||
ACTION_OLIST, ACTION_ULIST
|
||||
};
|
||||
|
||||
private const ActionEntry[] editor_action_entries = {
|
||||
{GearyApplication.ACTION_UNDO, on_undo },
|
||||
{GearyApplication.ACTION_REDO, on_redo },
|
||||
{GearyApplication.ACTION_COPY, on_copy },
|
||||
{ACTION_CUT, on_cut },
|
||||
{ACTION_COPY_LINK, on_copy_link },
|
||||
{ACTION_PASTE, on_paste },
|
||||
{ACTION_PASTE_WITHOUT_FORMATTING, on_paste_without_formatting },
|
||||
{ACTION_SELECT_ALL, on_select_all },
|
||||
{ACTION_BOLD, on_action, null, "false" },
|
||||
{ACTION_ITALIC, on_action, null, "false" },
|
||||
{ACTION_UNDERLINE, on_action, null, "false" },
|
||||
{ACTION_STRIKETHROUGH, on_action, null, "false" },
|
||||
{ACTION_FONT_SIZE, on_font_size, "s", "'medium'" },
|
||||
{ACTION_FONT_FAMILY, on_font_family, "s", "'sans'" },
|
||||
{ACTION_REMOVE_FORMAT, on_remove_format, null, "false" },
|
||||
{ACTION_INDENT, on_indent },
|
||||
{ACTION_OLIST, on_olist },
|
||||
{ACTION_ULIST, on_ulist },
|
||||
{ACTION_OUTDENT, on_action },
|
||||
{ACTION_JUSTIFY, on_justify, "s", "'left'" },
|
||||
{ACTION_COLOR, on_select_color },
|
||||
{ACTION_INSERT_IMAGE, on_insert_image },
|
||||
{ACTION_INSERT_LINK, on_insert_link },
|
||||
{ACTION_OPEN_INSPECTOR, on_open_inspector },
|
||||
private const ActionEntry[] EDITOR_ACTIONS = {
|
||||
{ Action.Edit.COPY, on_copy },
|
||||
{ Action.Edit.REDO, on_redo },
|
||||
{ Action.Edit.UNDO, on_undo },
|
||||
{ ACTION_BOLD, on_action, null, "false" },
|
||||
{ ACTION_COLOR, on_select_color },
|
||||
{ ACTION_COPY_LINK, on_copy_link },
|
||||
{ ACTION_CUT, on_cut },
|
||||
{ ACTION_FONT_FAMILY, on_font_family, "s", "'sans'" },
|
||||
{ ACTION_FONT_SIZE, on_font_size, "s", "'medium'" },
|
||||
{ ACTION_INDENT, on_indent },
|
||||
{ ACTION_INSERT_IMAGE, on_insert_image },
|
||||
{ ACTION_INSERT_LINK, on_insert_link },
|
||||
{ ACTION_ITALIC, on_action, null, "false" },
|
||||
{ ACTION_JUSTIFY, on_justify, "s", "'left'" },
|
||||
{ ACTION_OLIST, on_olist },
|
||||
{ ACTION_OUTDENT, on_action },
|
||||
{ ACTION_PASTE, on_paste },
|
||||
{ ACTION_PASTE_WITHOUT_FORMATTING, on_paste_without_formatting },
|
||||
{ ACTION_REMOVE_FORMAT, on_remove_format, null, "false" },
|
||||
{ ACTION_SELECT_ALL, on_select_all },
|
||||
{ ACTION_STRIKETHROUGH, on_action, null, "false" },
|
||||
{ ACTION_ULIST, on_ulist },
|
||||
{ ACTION_UNDERLINE, on_action, null, "false" },
|
||||
};
|
||||
|
||||
private const ActionEntry[] composer_action_entries = {
|
||||
{GearyApplication.ACTION_CLOSE, on_close },
|
||||
{ACTION_CLOSE, on_close },
|
||||
{ACTION_ADD_ATTACHMENT, on_add_attachment },
|
||||
{ACTION_ADD_ORIGINAL_ATTACHMENTS, on_pending_attachments },
|
||||
{ACTION_CLOSE_AND_DISCARD, on_close_and_discard },
|
||||
{ACTION_CLOSE_AND_SAVE, on_close_and_save },
|
||||
{ACTION_COMPOSE_AS_HTML, on_toggle_action, null, "true", on_compose_as_html_toggled },
|
||||
{ACTION_DETACH, on_detach },
|
||||
{ACTION_SELECT_DICTIONARY, on_select_dictionary },
|
||||
{ACTION_SEND, on_send },
|
||||
{ACTION_SHOW_EXTENDED, on_toggle_action, null, "false", on_show_extended_toggled },
|
||||
private const ActionEntry[] COMPOSER_ACTIONS = {
|
||||
{ Action.Window.CLOSE, on_close },
|
||||
{ ACTION_ADD_ATTACHMENT, on_add_attachment },
|
||||
{ ACTION_ADD_ORIGINAL_ATTACHMENTS, on_pending_attachments },
|
||||
{ ACTION_CLOSE, on_close },
|
||||
{ ACTION_CLOSE_AND_DISCARD, on_close_and_discard },
|
||||
{ ACTION_CLOSE_AND_SAVE, on_close_and_save },
|
||||
{ ACTION_COMPOSE_AS_HTML, on_toggle_action, null, "true", on_compose_as_html_toggled },
|
||||
{ ACTION_DETACH, on_detach },
|
||||
{ ACTION_OPEN_INSPECTOR, on_open_inspector },
|
||||
{ ACTION_SELECT_DICTIONARY, on_select_dictionary },
|
||||
{ ACTION_SEND, on_send },
|
||||
{ ACTION_SHOW_EXTENDED, on_toggle_action, null, "false", on_show_extended_toggled },
|
||||
};
|
||||
|
||||
public static void add_window_accelerators(GearyApplication application) {
|
||||
public static void add_accelerators(GearyApplication application) {
|
||||
application.add_window_accelerators(ACTION_CLOSE, { "Escape" } );
|
||||
application.add_window_accelerators(ACTION_CUT, { "<Ctrl>x" } );
|
||||
application.add_window_accelerators(ACTION_PASTE, { "<Ctrl>v" } );
|
||||
application.add_window_accelerators(ACTION_PASTE_WITHOUT_FORMATTING, { "<Ctrl><Shift>v" } );
|
||||
application.add_window_accelerators(ACTION_INSERT_IMAGE, { "<Ctrl>g" } );
|
||||
application.add_window_accelerators(ACTION_INSERT_LINK, { "<Ctrl>l" } );
|
||||
application.add_window_accelerators(ACTION_INDENT, { "<Ctrl>bracketright" } );
|
||||
application.add_window_accelerators(ACTION_OUTDENT, { "<Ctrl>bracketleft" } );
|
||||
application.add_window_accelerators(ACTION_REMOVE_FORMAT, { "<Ctrl>space" } );
|
||||
application.add_window_accelerators(ACTION_BOLD, { "<Ctrl>b" } );
|
||||
application.add_window_accelerators(ACTION_ITALIC, { "<Ctrl>i" } );
|
||||
application.add_window_accelerators(ACTION_UNDERLINE, { "<Ctrl>u" } );
|
||||
application.add_window_accelerators(ACTION_STRIKETHROUGH, { "<Ctrl>k" } );
|
||||
application.add_window_accelerators(ACTION_ADD_ATTACHMENT, { "<Ctrl>t" } );
|
||||
application.add_window_accelerators(ACTION_DETACH, { "<Ctrl>d" } );
|
||||
|
||||
application.add_edit_accelerators(ACTION_CUT, { "<Ctrl>x" } );
|
||||
application.add_edit_accelerators(ACTION_PASTE, { "<Ctrl>v" } );
|
||||
application.add_edit_accelerators(ACTION_PASTE_WITHOUT_FORMATTING, { "<Ctrl><Shift>v" } );
|
||||
application.add_edit_accelerators(ACTION_INSERT_IMAGE, { "<Ctrl>g" } );
|
||||
application.add_edit_accelerators(ACTION_INSERT_LINK, { "<Ctrl>l" } );
|
||||
application.add_edit_accelerators(ACTION_INDENT, { "<Ctrl>bracketright" } );
|
||||
application.add_edit_accelerators(ACTION_OUTDENT, { "<Ctrl>bracketleft" } );
|
||||
application.add_edit_accelerators(ACTION_REMOVE_FORMAT, { "<Ctrl>space" } );
|
||||
application.add_edit_accelerators(ACTION_BOLD, { "<Ctrl>b" } );
|
||||
application.add_edit_accelerators(ACTION_ITALIC, { "<Ctrl>i" } );
|
||||
application.add_edit_accelerators(ACTION_UNDERLINE, { "<Ctrl>u" } );
|
||||
application.add_edit_accelerators(ACTION_STRIKETHROUGH, { "<Ctrl>k" } );
|
||||
}
|
||||
|
||||
private const string DRAFT_SAVED_TEXT = _("Saved");
|
||||
|
|
@ -268,30 +269,43 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
[GtkChild]
|
||||
private Gtk.ComboBoxText from_multiple;
|
||||
private Gee.ArrayList<FromAddressMap> from_list = new Gee.ArrayList<FromAddressMap>();
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.EventBox to_box;
|
||||
[GtkChild]
|
||||
private Gtk.Label to_label;
|
||||
private EmailEntry to_entry;
|
||||
private Components.EntryUndo to_undo;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.EventBox cc_box;
|
||||
[GtkChild]
|
||||
private Gtk.Label cc_label;
|
||||
private EmailEntry cc_entry;
|
||||
private Components.EntryUndo cc_undo;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.EventBox bcc_box;
|
||||
[GtkChild]
|
||||
private Gtk.Label bcc_label;
|
||||
private EmailEntry bcc_entry;
|
||||
private Components.EntryUndo bcc_undo;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.EventBox reply_to_box;
|
||||
[GtkChild]
|
||||
private Gtk.Label reply_to_label;
|
||||
private EmailEntry reply_to_entry;
|
||||
private Components.EntryUndo reply_to_undo;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.Label subject_label;
|
||||
[GtkChild]
|
||||
private Gtk.Entry subject_entry;
|
||||
private Components.EntryUndo subject_undo;
|
||||
private Gspell.Checker subject_spell_checker = new Gspell.Checker(null);
|
||||
private Gspell.Entry subject_spell_entry;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.Label message_overlay_label;
|
||||
[GtkChild]
|
||||
|
|
@ -325,8 +339,8 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
[GtkChild]
|
||||
private Gtk.Label info_label;
|
||||
|
||||
private SimpleActionGroup composer_actions = new SimpleActionGroup();
|
||||
private SimpleActionGroup editor_actions = new SimpleActionGroup();
|
||||
private GLib.SimpleActionGroup composer_actions = new GLib.SimpleActionGroup();
|
||||
private GLib.SimpleActionGroup editor_actions = new GLib.SimpleActionGroup();
|
||||
|
||||
private Menu html_menu;
|
||||
private Menu plain_menu;
|
||||
|
|
@ -386,9 +400,6 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
get { return (ComposerContainer) parent; }
|
||||
}
|
||||
|
||||
private Gspell.Checker subject_spell_checker = new Gspell.Checker(null);
|
||||
private Gspell.Entry subject_spell_entry;
|
||||
|
||||
private GearyApplication application;
|
||||
|
||||
|
||||
|
|
@ -454,23 +465,30 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
this.to_entry = new EmailEntry(this);
|
||||
this.to_entry.changed.connect(on_envelope_changed);
|
||||
this.to_box.add(to_entry);
|
||||
this.to_label.set_mnemonic_widget(this.to_entry);
|
||||
this.to_undo = new Components.EntryUndo(this.to_entry);
|
||||
|
||||
this.cc_entry = new EmailEntry(this);
|
||||
this.cc_entry.changed.connect(on_envelope_changed);
|
||||
this.cc_box.add(cc_entry);
|
||||
this.cc_label.set_mnemonic_widget(this.cc_entry);
|
||||
this.cc_undo = new Components.EntryUndo(this.cc_entry);
|
||||
|
||||
this.bcc_entry = new EmailEntry(this);
|
||||
this.bcc_entry.changed.connect(on_envelope_changed);
|
||||
this.bcc_box.add(bcc_entry);
|
||||
this.bcc_label.set_mnemonic_widget(this.bcc_entry);
|
||||
this.bcc_undo = new Components.EntryUndo(this.bcc_entry);
|
||||
|
||||
this.reply_to_entry = new EmailEntry(this);
|
||||
this.reply_to_entry.changed.connect(on_envelope_changed);
|
||||
this.reply_to_box.add(reply_to_entry);
|
||||
|
||||
this.to_label.set_mnemonic_widget(this.to_entry);
|
||||
this.cc_label.set_mnemonic_widget(this.cc_entry);
|
||||
this.bcc_label.set_mnemonic_widget(this.bcc_entry);
|
||||
this.reply_to_label.set_mnemonic_widget(this.reply_to_entry);
|
||||
this.reply_to_undo = new Components.EntryUndo(this.reply_to_entry);
|
||||
|
||||
this.to_entry.margin_top = this.cc_entry.margin_top = this.bcc_entry.margin_top = this.reply_to_entry.margin_top = 6;
|
||||
|
||||
this.subject_undo = new Components.EntryUndo(this.subject_entry);
|
||||
this.subject_spell_entry = Gspell.Entry.get_from_gtk_entry(
|
||||
this.subject_entry
|
||||
);
|
||||
|
|
@ -858,29 +876,23 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
// Initializes all actions and adds them to the action group
|
||||
private void initialize_actions() {
|
||||
// Composer actions
|
||||
this.composer_actions.add_action_entries(
|
||||
ComposerWidget.composer_action_entries, this
|
||||
);
|
||||
// Main actions use 'win' prefix so they override main window
|
||||
// action. But for some reason, we can't use the same prefix
|
||||
// for the headerbar.
|
||||
insert_action_group("win", this.composer_actions);
|
||||
this.composer_actions.add_action_entries(COMPOSER_ACTIONS, this);
|
||||
// Main actions use the window prefix so they override main
|
||||
// window actions. But for some reason, we can't use the same
|
||||
// prefix for the headerbar.
|
||||
insert_action_group(Action.Window.GROUP_NAME, this.composer_actions);
|
||||
this.header.insert_action_group("cmh", this.composer_actions);
|
||||
|
||||
// Editor actions - scoped to the editor only. Need to include
|
||||
// composer actions however since if not found in this group,
|
||||
// ancestors (including the composer's) will not be consulted.
|
||||
this.editor_actions.add_action_entries(
|
||||
ComposerWidget.composer_action_entries, this
|
||||
// Editor actions - scoped to the editor only.
|
||||
this.editor_actions.add_action_entries(EDITOR_ACTIONS, this);
|
||||
this.editor_container.insert_action_group(
|
||||
Action.Edit.GROUP_NAME, this.editor_actions
|
||||
);
|
||||
this.editor_actions.add_action_entries(
|
||||
ComposerWidget.editor_action_entries, this
|
||||
);
|
||||
this.editor_container.insert_action_group("win", this.editor_actions);
|
||||
|
||||
SimpleActionGroup[] composer_action_entries_users
|
||||
= {this.editor_actions, this.composer_actions};
|
||||
foreach (SimpleActionGroup entries_users in composer_action_entries_users) {
|
||||
GLib.SimpleActionGroup[] composer_action_entries_users = {
|
||||
this.editor_actions, this.composer_actions
|
||||
};
|
||||
foreach (var entries_users in composer_action_entries_users) {
|
||||
entries_users.change_action_state(ACTION_SHOW_EXTENDED, false);
|
||||
entries_users.change_action_state(
|
||||
ACTION_COMPOSE_AS_HTML, this.application.config.compose_as_html
|
||||
|
|
@ -888,8 +900,8 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
}
|
||||
|
||||
get_action(ACTION_CLOSE_AND_SAVE).set_enabled(false);
|
||||
get_action(GearyApplication.ACTION_UNDO).set_enabled(false);
|
||||
get_action(GearyApplication.ACTION_REDO).set_enabled(false);
|
||||
get_action(Action.Edit.UNDO).set_enabled(false);
|
||||
get_action(Action.Edit.REDO).set_enabled(false);
|
||||
|
||||
update_cursor_actions();
|
||||
}
|
||||
|
|
@ -897,7 +909,7 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
private void update_cursor_actions() {
|
||||
bool has_selection = this.editor.has_selection;
|
||||
get_action(ACTION_CUT).set_enabled(has_selection);
|
||||
get_action(GearyApplication.ACTION_COPY).set_enabled(has_selection);
|
||||
get_action(Action.Edit.COPY).set_enabled(has_selection);
|
||||
|
||||
get_action(ACTION_INSERT_LINK).set_enabled(
|
||||
this.editor.is_rich_text && (has_selection || this.cursor_url != null)
|
||||
|
|
@ -1896,7 +1908,7 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
bool compose_as_html = new_state.get_boolean();
|
||||
action.set_state(compose_as_html);
|
||||
|
||||
foreach (string html_action in html_actions)
|
||||
foreach (string html_action in HTML_ACTIONS)
|
||||
get_action(html_action).set_enabled(compose_as_html);
|
||||
|
||||
update_cursor_actions();
|
||||
|
|
@ -2345,8 +2357,8 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
}
|
||||
|
||||
private void on_command_state_changed(bool can_undo, bool can_redo) {
|
||||
get_action(GearyApplication.ACTION_UNDO).set_enabled(can_undo);
|
||||
get_action(GearyApplication.ACTION_REDO).set_enabled(can_redo);
|
||||
get_action(Action.Edit.UNDO).set_enabled(can_undo);
|
||||
get_action(Action.Edit.REDO).set_enabled(can_redo);
|
||||
}
|
||||
|
||||
private void on_editor_content_loaded() {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
|
|||
private string current_key = "";
|
||||
|
||||
// List of (possibly incomplete) email addresses in the entry.
|
||||
private string[] email_addresses = {};
|
||||
private Gee.ArrayList<string> address_parts = new Gee.ArrayList<string>();
|
||||
|
||||
// Index of the email address the cursor is currently at
|
||||
private int cursor_at_address = -1;
|
||||
|
|
@ -98,10 +98,11 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
|
|||
model.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void trigger_selection() {
|
||||
if (last_iter != null) {
|
||||
on_match_selected(model, last_iter);
|
||||
last_iter = null;
|
||||
if (this.last_iter != null) {
|
||||
insert_address_at_cursor(this.last_iter);
|
||||
this.last_iter = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +111,7 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
|
|||
if (entry != null) {
|
||||
this.current_key = "";
|
||||
this.cursor_at_address = -1;
|
||||
this.email_addresses = {};
|
||||
this.address_parts.clear();
|
||||
|
||||
string text = entry.get_text();
|
||||
int cursor_pos = entry.get_position();
|
||||
|
|
@ -123,7 +124,7 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
|
|||
while (text.get_next_char(ref next_idx, out c)) {
|
||||
if (current_char == cursor_pos) {
|
||||
this.current_key = text.slice(start_idx, next_idx).strip();
|
||||
this.cursor_at_address = this.email_addresses.length;
|
||||
this.cursor_at_address = this.address_parts.size;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
|
|
@ -131,7 +132,7 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
|
|||
if (!in_quote) {
|
||||
// Don't include the comma in the address
|
||||
string address = text.slice(start_idx, next_idx -1);
|
||||
this.email_addresses += address.strip();
|
||||
this.address_parts.add(address);
|
||||
// Don't include it in the next one, either
|
||||
start_idx = next_idx;
|
||||
}
|
||||
|
|
@ -147,12 +148,66 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
|
|||
|
||||
// Add any remaining text after the last comma
|
||||
string address = text.substring(start_idx);
|
||||
this.email_addresses += address.strip();
|
||||
this.address_parts.add(address);
|
||||
}
|
||||
}
|
||||
|
||||
public async void search_contacts(string query,
|
||||
GLib.Cancellable? cancellable) {
|
||||
private void insert_address_at_cursor(Gtk.TreeIter iter) {
|
||||
Gtk.Entry? entry = get_entry() as Gtk.Entry;
|
||||
if (entry != null) {
|
||||
// Take care to do a delete then an insert here so that
|
||||
// Component.EntryUndo can combine the two into a single
|
||||
// undoable command
|
||||
int start_char = this.address_parts.slice(
|
||||
0, this.cursor_at_address
|
||||
).fold<int>(
|
||||
// address parts don't contain commas, so need to add
|
||||
// an char width for it
|
||||
(a, chars) => a.char_count() + chars + 1, 0
|
||||
);
|
||||
int end_char = (
|
||||
start_char +
|
||||
this.address_parts[this.cursor_at_address].char_count()
|
||||
);
|
||||
|
||||
// Format and use the selected address
|
||||
GLib.Value value;
|
||||
this.model.get_value(iter, Column.MAILBOX, out value);
|
||||
Geary.RFC822.MailboxAddress mailbox =
|
||||
(Geary.RFC822.MailboxAddress) value.get_object();
|
||||
string formatted = mailbox.to_full_display();
|
||||
if (this.cursor_at_address != 0) {
|
||||
// This isn't the first address, so add some
|
||||
// whitespace to pad it out
|
||||
formatted = " " + formatted;
|
||||
}
|
||||
this.address_parts[this.cursor_at_address] = formatted;
|
||||
|
||||
// Update the entry text
|
||||
entry.delete_text(start_char, end_char);
|
||||
entry.insert_text(
|
||||
formatted, formatted.char_count(), ref start_char
|
||||
);
|
||||
|
||||
// Update the entry cursor position. The previous call
|
||||
// updates the start so just use that, but add extra space
|
||||
// for the comma and any white space at the start of the
|
||||
// next address.
|
||||
++start_char;
|
||||
string? next_address = (
|
||||
this.cursor_at_address + 1 < this.address_parts.size
|
||||
? this.address_parts[this.cursor_at_address + 1]
|
||||
: ""
|
||||
);
|
||||
for (int i = 0; i < next_address.length && next_address[i] == ' '; i++) {
|
||||
++start_char;
|
||||
}
|
||||
entry.set_position(start_char);
|
||||
}
|
||||
}
|
||||
|
||||
private async void search_contacts(string query,
|
||||
GLib.Cancellable? cancellable) {
|
||||
Gee.Collection<Application.Contact>? results = null;
|
||||
try {
|
||||
results = yield this.contacts.search(
|
||||
|
|
@ -283,37 +338,7 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
|
|||
}
|
||||
|
||||
private bool on_match_selected(Gtk.TreeModel model, Gtk.TreeIter iter) {
|
||||
Gtk.Entry? entry = get_entry() as Gtk.Entry;
|
||||
if (entry != null) {
|
||||
// Update the address
|
||||
GLib.Value value;
|
||||
model.get_value(iter, Column.MAILBOX, out value);
|
||||
Geary.RFC822.MailboxAddress mailbox =
|
||||
(Geary.RFC822.MailboxAddress) value.get_object();
|
||||
this.email_addresses[this.cursor_at_address] =
|
||||
mailbox.to_full_display();
|
||||
|
||||
// Update the entry text
|
||||
bool current_is_last = (
|
||||
this.cursor_at_address == this.email_addresses.length - 1
|
||||
);
|
||||
int new_cursor_pos = -1;
|
||||
GLib.StringBuilder text = new GLib.StringBuilder();
|
||||
int i = 0;
|
||||
while (i < this.email_addresses.length) {
|
||||
text.append(this.email_addresses[i]);
|
||||
if (i == this.cursor_at_address) {
|
||||
new_cursor_pos = text.str.char_count();
|
||||
}
|
||||
|
||||
i++;
|
||||
if (i != this.email_addresses.length || current_is_last) {
|
||||
text.append(", ");
|
||||
}
|
||||
}
|
||||
entry.text = text.str;
|
||||
entry.set_position(current_is_last ? -1 : new_cursor_pos);
|
||||
}
|
||||
insert_address_at_cursor(iter);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,13 +81,26 @@ public class EmailEntry : Gtk.Entry {
|
|||
}
|
||||
|
||||
private bool on_key_press(Gtk.Widget widget, Gdk.EventKey event) {
|
||||
bool ret = Gdk.EVENT_PROPAGATE;
|
||||
if (event.keyval == Gdk.Key.Tab) {
|
||||
((ContactEntryCompletion) get_completion()).trigger_selection();
|
||||
composer.child_focus(Gtk.DirectionType.TAB_FORWARD);
|
||||
return true;
|
||||
ContactEntryCompletion? completion = (
|
||||
get_completion() as ContactEntryCompletion
|
||||
);
|
||||
if (completion != null) {
|
||||
completion.trigger_selection();
|
||||
composer.child_focus(Gtk.DirectionType.TAB_FORWARD);
|
||||
ret = Gdk.EVENT_STOP;
|
||||
}
|
||||
} else {
|
||||
// Keyboard shortcuts for undo/redo won't work when the
|
||||
// completion UI is visible unless we explicitly check for
|
||||
// them there. This may be related to the
|
||||
// single-key-shortcut handling hack in the MainWindow.
|
||||
Gtk.Window? window = get_toplevel() as Gtk.Window;
|
||||
if (window != null) {
|
||||
ret = window.activate_key(event);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
|||
|
||||
[GtkChild]
|
||||
internal Gtk.SearchEntry conversation_find_entry;
|
||||
private Components.EntryUndo conversation_find_undo;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.Button conversation_find_next;
|
||||
|
|
@ -126,6 +127,10 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
|||
);
|
||||
this.empty_search_page.add(empty_search);
|
||||
|
||||
this.conversation_find_undo = new Components.EntryUndo(
|
||||
this.conversation_find_entry
|
||||
);
|
||||
|
||||
// XXX GTK+ Bug 778190 workaround
|
||||
new_conversation_scroller();
|
||||
|
||||
|
|
@ -431,6 +436,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
|||
this.current_list.conversation.base_folder
|
||||
as Geary.SearchFolder
|
||||
);
|
||||
this.conversation_find_undo.reset();
|
||||
if (search_folder != null) {
|
||||
Geary.SearchQuery? search_query = search_folder.search_query;
|
||||
if (search_query != null) {
|
||||
|
|
|
|||
|
|
@ -16,15 +16,18 @@ public class Dialogs.ProblemDetailsDialog : Hdy.Dialog {
|
|||
private const string ACTION_SEARCH_TOGGLE = "toggle-search";
|
||||
private const string ACTION_SEARCH_ACTIVATE = "activate-search";
|
||||
|
||||
private const ActionEntry[] action_entries = {
|
||||
{GearyApplication.ACTION_CLOSE, on_close },
|
||||
{GearyApplication.ACTION_COPY, on_copy_clicked },
|
||||
{ACTION_CLOSE, on_close },
|
||||
{ACTION_SEARCH_TOGGLE, on_logs_search_toggled, null, "false" },
|
||||
{ACTION_SEARCH_ACTIVATE, on_logs_search_activated },
|
||||
private const ActionEntry[] EDIT_ACTIONS = {
|
||||
{ Action.Edit.COPY, on_copy_clicked },
|
||||
};
|
||||
|
||||
public static void add_window_accelerators(GearyApplication app) {
|
||||
private const ActionEntry[] WINDOW_ACTIONS = {
|
||||
{ Action.Window.CLOSE, on_close },
|
||||
{ ACTION_CLOSE, on_close },
|
||||
{ ACTION_SEARCH_TOGGLE, on_logs_search_toggled, null, "false" },
|
||||
{ ACTION_SEARCH_ACTIVATE, on_logs_search_activated },
|
||||
};
|
||||
|
||||
public static void add_accelerators(GearyApplication app) {
|
||||
app.add_window_accelerators(ACTION_CLOSE, { "Escape" } );
|
||||
app.add_window_accelerators(ACTION_SEARCH_ACTIVATE, { "<Ctrl>F" } );
|
||||
}
|
||||
|
|
@ -64,9 +67,15 @@ public class Dialogs.ProblemDetailsDialog : Hdy.Dialog {
|
|||
this.account = (account_report != null) ? account_report.account : null;
|
||||
this.service = (service_report != null) ? service_report.service : null;
|
||||
|
||||
GLib.SimpleActionGroup actions = new GLib.SimpleActionGroup();
|
||||
actions.add_action_entries(ProblemDetailsDialog.action_entries, this);
|
||||
insert_action_group("win", actions);
|
||||
// Edit actions
|
||||
GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
||||
edit_actions.add_action_entries(EDIT_ACTIONS, this);
|
||||
insert_action_group(Action.Edit.GROUP_NAME, edit_actions);
|
||||
|
||||
// Window actions
|
||||
GLib.SimpleActionGroup window_actions = new GLib.SimpleActionGroup();
|
||||
window_actions.add_action_entries(WINDOW_ACTIONS, this);
|
||||
insert_action_group(Action.Window.GROUP_NAME, window_actions);
|
||||
|
||||
this.error_pane = new Components.InspectorErrorView(
|
||||
error, account, service
|
||||
|
|
|
|||
|
|
@ -25,8 +25,11 @@ geary_client_vala_sources = files(
|
|||
'accounts/accounts-signature-web-view.vala',
|
||||
'accounts/accounts-manager.vala',
|
||||
|
||||
'client-action.vala',
|
||||
|
||||
'components/client-web-view.vala',
|
||||
'components/components-attachment-pane.vala',
|
||||
'components/components-entry-undo.vala',
|
||||
'components/components-inspector.vala',
|
||||
'components/components-in-app-notification.vala',
|
||||
'components/components-inspector-error-view.vala',
|
||||
|
|
|
|||
|
|
@ -149,9 +149,9 @@ public class Plugin.DesktopNotifications : Notification {
|
|||
};
|
||||
|
||||
if (id == null) {
|
||||
action = GearyApplication.ACTION_SHOW_FOLDER;
|
||||
action = Action.Application.SHOW_FOLDER;
|
||||
} else {
|
||||
action = GearyApplication.ACTION_SHOW_EMAIL;
|
||||
action = Action.Application.SHOW_EMAIL;
|
||||
target_param += new GLib.Variant.variant(id.to_variant());
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ public class Plugin.DesktopNotifications : Notification {
|
|||
ARRIVED_ID,
|
||||
summary,
|
||||
body,
|
||||
"app." + action,
|
||||
Action.Application.prefix(action),
|
||||
new GLib.Variant.tuple(target_param)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">win.undo</property>
|
||||
<property name="action_name">edt.undo</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes"
|
||||
comments="Tooltip for inspector button">Copy to clipboard</property>
|
||||
<property name="action_name">win.copy</property>
|
||||
<property name="action_name">edt.copy</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
|
|
|
|||
|
|
@ -5,41 +5,41 @@
|
|||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">S_ans Serif</attribute>
|
||||
<attribute name="action">win.font-family</attribute>
|
||||
<attribute name="action">edt.font-family</attribute>
|
||||
<attribute name="target">sans</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">S_erif</attribute>
|
||||
<attribute name="action">win.font-family</attribute>
|
||||
<attribute name="action">edt.font-family</attribute>
|
||||
<attribute name="target">serif</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Fixed Width</attribute>
|
||||
<attribute name="action">win.font-family</attribute>
|
||||
<attribute name="action">edt.font-family</attribute>
|
||||
<attribute name="target">monospace</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Small</attribute>
|
||||
<attribute name="action">win.font-size</attribute>
|
||||
<attribute name="action">edt.font-size</attribute>
|
||||
<attribute name="target">small</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Medium</attribute>
|
||||
<attribute name="action">win.font-size</attribute>
|
||||
<attribute name="action">edt.font-size</attribute>
|
||||
<attribute name="target">medium</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Lar_ge</attribute>
|
||||
<attribute name="action">win.font-size</attribute>
|
||||
<attribute name="action">edt.font-size</attribute>
|
||||
<attribute name="target">large</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">C_olor</attribute>
|
||||
<attribute name="action">win.color</attribute>
|
||||
<attribute name="action">edt.color</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
|
|
@ -76,49 +76,49 @@
|
|||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Undo</attribute>
|
||||
<attribute name="action">win.undo</attribute>
|
||||
<attribute name="action">edt.undo</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Redo</attribute>
|
||||
<attribute name="action">win.redo</attribute>
|
||||
<attribute name="action">edt.redo</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="context_menu_rich_text">
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Cu_t</attribute>
|
||||
<attribute name="action">win.cut</attribute>
|
||||
<attribute name="action">edt.cut</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Copy</attribute>
|
||||
<attribute name="action">win.copy</attribute>
|
||||
<attribute name="action">edt.copy</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Paste</attribute>
|
||||
<attribute name="action">win.paste</attribute>
|
||||
<attribute name="action">edt.paste</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes" context="Clipboard paste as plain text">Paste _Without Formatting</attribute>
|
||||
<attribute name="action">win.paste-without-formatting</attribute>
|
||||
<attribute name="action">edt.paste-without-formatting</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="context_menu_plain_text">
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Cu_t</attribute>
|
||||
<attribute name="action">win.cut</attribute>
|
||||
<attribute name="action">edt.cut</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Copy</attribute>
|
||||
<attribute name="action">win.copy</attribute>
|
||||
<attribute name="action">edt.copy</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Paste</attribute>
|
||||
<attribute name="action">win.paste</attribute>
|
||||
<attribute name="action">edt.paste</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Select _All</attribute>
|
||||
<attribute name="action">win.select-all</attribute>
|
||||
<attribute name="action">edt.select-all</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="context_menu_webkit_text_entry"/>
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Undo last edit (Ctrl+Z)</property>
|
||||
<property name="action_name">win.undo</property>
|
||||
<property name="action_name">edt.undo</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
|
|
@ -375,7 +375,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Redo last edit (Ctrl+Shift+Z)</property>
|
||||
<property name="action_name">win.redo</property>
|
||||
<property name="action_name">edt.redo</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
|
|
@ -413,7 +413,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Bold (Ctrl+B)</property>
|
||||
<property name="action_name">win.bold</property>
|
||||
<property name="action_name">edt.bold</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="bold_image">
|
||||
|
|
@ -437,7 +437,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Italic (Ctrl+I)</property>
|
||||
<property name="action_name">win.italic</property>
|
||||
<property name="action_name">edt.italic</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="italics_image">
|
||||
|
|
@ -461,7 +461,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Underline (Ctrl+U)</property>
|
||||
<property name="action_name">win.underline</property>
|
||||
<property name="action_name">edt.underline</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="underline_image">
|
||||
|
|
@ -485,7 +485,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Strikethrough (Ctrl+K)</property>
|
||||
<property name="action_name">win.strikethrough</property>
|
||||
<property name="action_name">edt.strikethrough</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="strikethrough_image">
|
||||
|
|
@ -523,7 +523,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Insert unordered list</property>
|
||||
<property name="action_name">win.ulist</property>
|
||||
<property name="action_name">edt.ulist</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="ulist_image">
|
||||
|
|
@ -547,7 +547,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Insert ordered list</property>
|
||||
<property name="action_name">win.olist</property>
|
||||
<property name="action_name">edt.olist</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="olist_image">
|
||||
|
|
@ -585,7 +585,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Quote text (Ctrl+])</property>
|
||||
<property name="action_name">win.indent</property>
|
||||
<property name="action_name">edt.indent</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="indent_image">
|
||||
|
|
@ -609,7 +609,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Unquote text (Ctrl+[)</property>
|
||||
<property name="action_name">win.outdent</property>
|
||||
<property name="action_name">edt.outdent</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="outdent_image">
|
||||
|
|
@ -647,7 +647,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Insert or update selection link (Ctrl+L)</property>
|
||||
<property name="action_name">win.insert-link</property>
|
||||
<property name="action_name">edt.insert-link</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="insert_link_image">
|
||||
|
|
@ -671,7 +671,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Insert an image (Ctrl+G)</property>
|
||||
<property name="action_name">win.insert-image</property>
|
||||
<property name="action_name">edt.insert-image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
|
|
@ -705,7 +705,7 @@
|
|||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove selection formatting (Ctrl+Space)</property>
|
||||
<property name="action_name">win.remove-format</property>
|
||||
<property name="action_name">edt.remove-format</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="remove_format_image">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue