Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be3ed978e2 |
153 changed files with 6673 additions and 9492 deletions
|
|
@ -67,7 +67,7 @@ build.container.fedora@x86_64:
|
||||||
gsettings-desktop-schemas
|
gsettings-desktop-schemas
|
||||||
gsound-devel
|
gsound-devel
|
||||||
gspell-devel
|
gspell-devel
|
||||||
gtk3-devel
|
gtk4-devel
|
||||||
iso-codes-devel
|
iso-codes-devel
|
||||||
itstool
|
itstool
|
||||||
json-glib-devel
|
json-glib-devel
|
||||||
|
|
@ -76,6 +76,7 @@ build.container.fedora@x86_64:
|
||||||
libicu-devel
|
libicu-devel
|
||||||
libpeas-devel
|
libpeas-devel
|
||||||
libsecret-devel
|
libsecret-devel
|
||||||
|
libspelling-devel
|
||||||
libstemmer-devel
|
libstemmer-devel
|
||||||
libunwind-devel
|
libunwind-devel
|
||||||
libxml2-devel
|
libxml2-devel
|
||||||
|
|
@ -93,8 +94,8 @@ build.container.fedora@x86_64:
|
||||||
# When branching a stable release, change 'main' to the
|
# When branching a stable release, change 'main' to the
|
||||||
# release branch name to ensure that a new image will
|
# release branch name to ensure that a new image will
|
||||||
# be created, tailored for the stable branch.
|
# be created, tailored for the stable branch.
|
||||||
BRANCH_NAME: 'main'
|
BRANCH_NAME: 'nielsdg/gtk4'
|
||||||
CONTAINER_TAG: '2025-12-12.0'
|
CONTAINER_TAG: '2025-12-15.0'
|
||||||
FEDORA_VERSION: latest
|
FEDORA_VERSION: latest
|
||||||
# Derive FDO variables from this automatically.
|
# Derive FDO variables from this automatically.
|
||||||
# DO NOT edit, instead change the variables above
|
# DO NOT edit, instead change the variables above
|
||||||
|
|
|
||||||
39
meson.build
39
meson.build
|
|
@ -52,10 +52,10 @@ valac = meson.get_compiler('vala')
|
||||||
# Required libraries and other dependencies
|
# Required libraries and other dependencies
|
||||||
#
|
#
|
||||||
|
|
||||||
target_glib = '2.74'
|
target_glib = '2.80'
|
||||||
target_gtk = '3.24.24'
|
target_gtk = '4.16.0'
|
||||||
target_vala = '0.56'
|
target_vala = '0.56'
|
||||||
target_webkit = '2.30'
|
target_webkit = '2.40'
|
||||||
|
|
||||||
if not valac.version().version_compare('>=' + target_vala)
|
if not valac.version().version_compare('>=' + target_vala)
|
||||||
error('Vala does not meet minimum required version: ' + target_vala)
|
error('Vala does not meet minimum required version: ' + target_vala)
|
||||||
|
|
@ -64,9 +64,9 @@ endif
|
||||||
# Primary deps
|
# Primary deps
|
||||||
glib = dependency('glib-2.0', version: '>=' + target_glib)
|
glib = dependency('glib-2.0', version: '>=' + target_glib)
|
||||||
gmime = dependency('gmime-3.0', version: '>= 3.2.4')
|
gmime = dependency('gmime-3.0', version: '>= 3.2.4')
|
||||||
gtk = dependency('gtk+-3.0', version: '>=' + target_gtk)
|
gtk = dependency('gtk4', version: '>=' + target_gtk)
|
||||||
sqlite = dependency('sqlite3', version: '>= 3.24')
|
sqlite = dependency('sqlite3', version: '>= 3.24')
|
||||||
webkit2gtk = dependency('webkit2gtk-4.1', version: '>=' + target_webkit)
|
webkitgtk = dependency('webkitgtk-6.0', version: '>=' + target_webkit)
|
||||||
|
|
||||||
# Secondary deps - keep sorted alphabetically
|
# Secondary deps - keep sorted alphabetically
|
||||||
cairo = dependency('cairo')
|
cairo = dependency('cairo')
|
||||||
|
|
@ -74,18 +74,17 @@ enchant = dependency('enchant-2', version: '>=2.1')
|
||||||
folks = dependency('folks', version: '>=0.11')
|
folks = dependency('folks', version: '>=0.11')
|
||||||
gck = dependency('gck-2')
|
gck = dependency('gck-2')
|
||||||
gcr = dependency('gcr-4')
|
gcr = dependency('gcr-4')
|
||||||
gdk = dependency('gdk-3.0', version: '>=' + target_gtk)
|
|
||||||
gee = dependency('gee-0.8', version: '>= 0.8.5')
|
gee = dependency('gee-0.8', version: '>= 0.8.5')
|
||||||
gio = dependency('gio-2.0', version: '>=' + target_glib)
|
gio = dependency('gio-2.0', version: '>=' + target_glib)
|
||||||
goa = dependency('goa-1.0')
|
goa = dependency('goa-1.0')
|
||||||
gsound = dependency('gsound')
|
gsound = dependency('gsound')
|
||||||
gspell = dependency('gspell-1')
|
libspelling = dependency('libspelling-1')
|
||||||
gthread = dependency('gthread-2.0', version: '>=' + target_glib)
|
gthread = dependency('gthread-2.0', version: '>=' + target_glib)
|
||||||
icu_uc = dependency('icu-uc', version: '>=60')
|
icu_uc = dependency('icu-uc', version: '>=60')
|
||||||
iso_codes = dependency('iso-codes')
|
iso_codes = dependency('iso-codes')
|
||||||
javascriptcoregtk = dependency('javascriptcoregtk-4.1', version: '>=' + target_webkit)
|
javascriptcoregtk = dependency('javascriptcoregtk-6.0', version: '>=' + target_webkit)
|
||||||
json_glib = dependency('json-glib-1.0', version: '>= 1.0')
|
json_glib = dependency('json-glib-1.0', version: '>= 1.0')
|
||||||
libhandy = dependency('libhandy-1', version: '>= 1.6', required: false)
|
libadwaita = dependency('libadwaita-1', version: '>= 1.7')
|
||||||
libmath = cc.find_library('m')
|
libmath = cc.find_library('m')
|
||||||
libpeas = dependency('libpeas-2')
|
libpeas = dependency('libpeas-2')
|
||||||
libsecret = dependency('libsecret-1', version: '>= 0.11')
|
libsecret = dependency('libsecret-1', version: '>= 0.11')
|
||||||
|
|
@ -100,7 +99,7 @@ libunwind_generic_dep = dependency(
|
||||||
libxml = dependency('libxml-2.0', version: '>= 2.7.8')
|
libxml = dependency('libxml-2.0', version: '>= 2.7.8')
|
||||||
libytnef = dependency('libytnef', version: '>= 1.9.3', required: get_option('tnef'))
|
libytnef = dependency('libytnef', version: '>= 1.9.3', required: get_option('tnef'))
|
||||||
posix = valac.find_library('posix')
|
posix = valac.find_library('posix')
|
||||||
webkit2gtk_web_extension = dependency('webkit2gtk-web-extension-4.1', version: '>=' + target_webkit)
|
webkitgtk_web_extension = dependency('webkitgtk-web-process-extension-6.0', version: '>=' + target_webkit)
|
||||||
|
|
||||||
# System dependencies above ensures appropriate versions for the
|
# System dependencies above ensures appropriate versions for the
|
||||||
# following libraries, but the declared dependency is what we actually
|
# following libraries, but the declared dependency is what we actually
|
||||||
|
|
@ -133,26 +132,6 @@ libstemmer = declare_dependency(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Required until libhandy 1.2.1 is GA
|
|
||||||
libhandy_vapi = ''
|
|
||||||
if not libhandy.found()
|
|
||||||
libhandy_project = subproject(
|
|
||||||
'libhandy',
|
|
||||||
default_options: [
|
|
||||||
'examples=false',
|
|
||||||
'package_subdir=geary',
|
|
||||||
'tests=false',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
libhandy = declare_dependency(
|
|
||||||
dependencies: [
|
|
||||||
libhandy_project.get_variable('libhandy_dep'),
|
|
||||||
libhandy_project.get_variable('libhandy_vapi')
|
|
||||||
]
|
|
||||||
)
|
|
||||||
libhandy_vapi = meson.project_build_root() / 'subprojects' / 'libhandy' / 'src'
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Optional dependencies
|
# Optional dependencies
|
||||||
appstreamcli = find_program('appstreamcli', required: false)
|
appstreamcli = find_program('appstreamcli', required: false)
|
||||||
desktop_file_validate = find_program('desktop-file-validate', required: false)
|
desktop_file_validate = find_program('desktop-file-validate', required: false)
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,22 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "libspelling",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"config-opts": [
|
||||||
|
"-Dintrospection=enabled",
|
||||||
|
"-Dvapi=true",
|
||||||
|
"-Ddocs=false"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://gitlab.gnome.org/GNOME/libspelling.git",
|
||||||
|
"branch": "main"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "geary",
|
"name": "geary",
|
||||||
"buildsystem": "meson",
|
"buildsystem": "meson",
|
||||||
|
|
|
||||||
|
|
@ -474,5 +474,5 @@ ui/conversation-viewer.ui
|
||||||
ui/find_bar.glade
|
ui/find_bar.glade
|
||||||
ui/folder-popover.ui
|
ui/folder-popover.ui
|
||||||
ui/gtk/help-overlay.ui
|
ui/gtk/help-overlay.ui
|
||||||
ui/password-dialog.glade
|
ui/password-dialog.ui
|
||||||
ui/problem-details-dialog.ui
|
ui/problem-details-dialog.ui
|
||||||
|
|
|
||||||
|
|
@ -9,25 +9,21 @@
|
||||||
* An account editor pane for adding a new account.
|
* An account editor pane for adding a new account.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_add_pane.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_add_pane.ui")]
|
||||||
internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
internal class Accounts.EditorAddPane : Accounts.EditorPane {
|
||||||
|
|
||||||
|
|
||||||
internal Gtk.Widget initial_widget {
|
|
||||||
get { return this.real_name.value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal bool is_operation_running {
|
internal override bool is_operation_running {
|
||||||
get { return !this.sensitive; }
|
get { return !this.sensitive; }
|
||||||
protected set { update_operation_ui(value); }
|
protected set { update_operation_ui(value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal GLib.Cancellable? op_cancellable {
|
internal override Cancellable? op_cancellable {
|
||||||
get; protected set; default = new GLib.Cancellable();
|
get; protected set; default = new GLib.Cancellable();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected weak Accounts.Editor editor { get; set; }
|
protected override weak Accounts.Editor editor { get; set; }
|
||||||
|
|
||||||
private Geary.ServiceProvider provider;
|
private Geary.ServiceProvider provider;
|
||||||
|
|
||||||
|
|
@ -35,98 +31,55 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
private Geary.Engine engine;
|
private Geary.Engine engine;
|
||||||
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.HeaderBar header;
|
[GtkChild] private unowned Adw.HeaderBar header;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Stack stack;
|
[GtkChild] private unowned Gtk.Stack stack;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Adjustment pane_adjustment;
|
[GtkChild] private unowned Adw.PreferencesGroup details_list;
|
||||||
|
[GtkChild] private unowned Adw.EntryRow name_row;
|
||||||
|
[GtkChild] private unowned Adw.EntryRow email_row;
|
||||||
|
[GtkChild] private unowned Components.Validator email_validator;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox details_list;
|
[GtkChild] private unowned ServiceInformationWidget receiving_service_widget;
|
||||||
|
[GtkChild] private unowned ServiceInformationWidget sending_service_widget;
|
||||||
[GtkChild] private unowned Gtk.ListBox receiving_list;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox sending_list;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Button action_button;
|
[GtkChild] private unowned Gtk.Button action_button;
|
||||||
|
[GtkChild] private unowned Adw.Spinner action_spinner;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Button back_button;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Spinner action_spinner;
|
|
||||||
|
|
||||||
private NameRow real_name;
|
|
||||||
private EmailRow email = new EmailRow();
|
|
||||||
private string last_valid_email = "";
|
private string last_valid_email = "";
|
||||||
private string last_valid_hostname = "";
|
private string last_valid_hostname = "";
|
||||||
|
|
||||||
|
//XXX if this is set, we shuld hide the IMAP/SMTP hostnames/auth
|
||||||
|
private bool did_auto_config { get; private set; default = false; }
|
||||||
private GLib.Cancellable auto_config_cancellable = new GLib.Cancellable();
|
private GLib.Cancellable auto_config_cancellable = new GLib.Cancellable();
|
||||||
|
|
||||||
private HostnameRow imap_hostname = new HostnameRow(Geary.Protocol.IMAP);
|
|
||||||
private TransportSecurityRow imap_tls = new TransportSecurityRow();
|
|
||||||
private LoginRow imap_login = new LoginRow();
|
|
||||||
private PasswordRow imap_password = new PasswordRow();
|
|
||||||
|
|
||||||
private HostnameRow smtp_hostname = new HostnameRow(Geary.Protocol.SMTP);
|
|
||||||
private TransportSecurityRow smtp_tls = new TransportSecurityRow();
|
|
||||||
private OutgoingAuthRow smtp_auth = new OutgoingAuthRow();
|
|
||||||
private LoginRow smtp_login = new LoginRow();
|
|
||||||
private PasswordRow smtp_password = new PasswordRow();
|
|
||||||
|
|
||||||
private bool controls_valid = false;
|
private bool controls_valid = false;
|
||||||
|
|
||||||
|
public Components.ValidatorGroup validators { get; construct set; }
|
||||||
|
|
||||||
|
|
||||||
|
static construct {
|
||||||
|
typeof(Components.ValidatorGroup).ensure();
|
||||||
|
typeof(Components.Validator).ensure();
|
||||||
|
typeof(Components.EmailValidator).ensure();
|
||||||
|
}
|
||||||
|
|
||||||
internal EditorAddPane(Editor editor) {
|
internal EditorAddPane(Editor editor) {
|
||||||
this.editor = editor;
|
Object(editor: editor);
|
||||||
|
|
||||||
this.provider = Geary.ServiceProvider.OTHER;
|
this.provider = Geary.ServiceProvider.OTHER;
|
||||||
|
|
||||||
this.accounts = editor.application.controller.account_manager;
|
this.accounts = editor.application.controller.account_manager;
|
||||||
this.engine = editor.application.engine;
|
this.engine = editor.application.engine;
|
||||||
|
|
||||||
this.stack.set_focus_vadjustment(this.pane_adjustment);
|
this.name_row.text = this.accounts.get_account_name();
|
||||||
|
//XXX GTK4 make sure it's validated immediately
|
||||||
|
|
||||||
this.details_list.set_header_func(Editor.seperator_headers);
|
this.receiving_service_widget.service = new_imap_service();
|
||||||
this.receiving_list.set_header_func(Editor.seperator_headers);
|
this.sending_service_widget.service = new_smtp_service();
|
||||||
this.sending_list.set_header_func(Editor.seperator_headers);
|
|
||||||
|
|
||||||
this.real_name = new NameRow(this.accounts.get_account_name());
|
// XXX we need to make sure the validators for the service are wired up too
|
||||||
|
// this.smtp_auth.value.changed.connect(on_smtp_auth_changed);
|
||||||
this.details_list.add(this.real_name);
|
|
||||||
this.details_list.add(this.email);
|
|
||||||
|
|
||||||
this.real_name.validator.state_changed.connect(on_validated);
|
|
||||||
this.real_name.value.activate.connect(on_activated);
|
|
||||||
this.email.validator.state_changed.connect(on_validated);
|
|
||||||
this.email.value.activate.connect(on_activated);
|
|
||||||
this.email.value.changed.connect(on_email_changed);
|
|
||||||
|
|
||||||
this.imap_hostname.validator.state_changed.connect(on_validated);
|
|
||||||
this.imap_hostname.value.activate.connect(on_activated);
|
|
||||||
this.imap_tls.hide();
|
|
||||||
this.imap_login.validator.state_changed.connect(on_validated);
|
|
||||||
this.imap_login.value.activate.connect(on_activated);
|
|
||||||
this.imap_password.validator.state_changed.connect(on_validated);
|
|
||||||
this.imap_password.value.activate.connect(on_activated);
|
|
||||||
|
|
||||||
this.smtp_hostname.validator.state_changed.connect(on_validated);
|
|
||||||
this.smtp_hostname.value.activate.connect(on_activated);
|
|
||||||
this.smtp_tls.hide();
|
|
||||||
this.smtp_auth.value.changed.connect(on_smtp_auth_changed);
|
|
||||||
this.smtp_login.validator.state_changed.connect(on_validated);
|
|
||||||
this.smtp_login.value.activate.connect(on_activated);
|
|
||||||
this.smtp_password.validator.state_changed.connect(on_validated);
|
|
||||||
this.smtp_password.value.activate.connect(on_activated);
|
|
||||||
|
|
||||||
this.receiving_list.add(this.imap_hostname);
|
|
||||||
this.receiving_list.add(this.imap_tls);
|
|
||||||
this.receiving_list.add(this.imap_login);
|
|
||||||
this.receiving_list.add(this.imap_password);
|
|
||||||
|
|
||||||
this.sending_list.add(this.smtp_hostname);
|
|
||||||
this.sending_list.add(this.smtp_tls);
|
|
||||||
this.sending_list.add(this.smtp_auth);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Gtk.HeaderBar get_header() {
|
|
||||||
return this.header;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void validate_account(GLib.Cancellable? cancellable) {
|
private async void validate_account(GLib.Cancellable? cancellable) {
|
||||||
|
|
@ -140,18 +93,18 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
yield this.accounts.new_orphan_account(
|
yield this.accounts.new_orphan_account(
|
||||||
this.provider,
|
this.provider,
|
||||||
new Geary.RFC822.MailboxAddress(
|
new Geary.RFC822.MailboxAddress(
|
||||||
this.real_name.value.text.strip(),
|
this.name_row.text.strip(),
|
||||||
this.email.value.text.strip()
|
this.email_row.text.strip()
|
||||||
),
|
),
|
||||||
cancellable
|
cancellable
|
||||||
);
|
);
|
||||||
|
|
||||||
account.incoming = new_imap_service();
|
account.incoming = this.receiving_service_widget.service_mutable;
|
||||||
account.outgoing = new_smtp_service();
|
account.outgoing = this.sending_service_widget.service_mutable;
|
||||||
account.untrusted_host.connect(on_untrusted_host);
|
account.untrusted_host.connect(on_untrusted_host);
|
||||||
|
|
||||||
if (this.provider == Geary.ServiceProvider.OTHER &&
|
if (this.provider == Geary.ServiceProvider.OTHER &&
|
||||||
this.imap_hostname.get_visible()) {
|
!this.did_auto_config) {
|
||||||
bool imap_valid = false;
|
bool imap_valid = false;
|
||||||
bool smtp_valid = false;
|
bool smtp_valid = false;
|
||||||
|
|
||||||
|
|
@ -162,7 +115,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
imap_valid = true;
|
imap_valid = true;
|
||||||
} catch (Geary.ImapError.UNAUTHENTICATED err) {
|
} catch (Geary.ImapError.UNAUTHENTICATED err) {
|
||||||
debug("Error authenticating IMAP service: %s", err.message);
|
debug("Error authenticating IMAP service: %s", err.message);
|
||||||
to_focus = this.imap_login.value;
|
to_focus = this.receiving_service_widget;
|
||||||
// Translators: In-app notification label
|
// Translators: In-app notification label
|
||||||
message = _("Check your receiving login and password");
|
message = _("Check your receiving login and password");
|
||||||
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
|
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
|
||||||
|
|
@ -176,8 +129,9 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
Geary.ErrorContext context = new Geary.ErrorContext(err);
|
Geary.ErrorContext context = new Geary.ErrorContext(err);
|
||||||
debug("Error validating IMAP service: %s",
|
debug("Error validating IMAP service: %s",
|
||||||
context.format_full_error());
|
context.format_full_error());
|
||||||
this.imap_tls.show();
|
//XXX GTK4 not sure how to design a nice API for this
|
||||||
to_focus = this.imap_hostname.value;
|
// this.imap_tls.show();
|
||||||
|
to_focus = this.receiving_service_widget;
|
||||||
// Translators: In-app notification label
|
// Translators: In-app notification label
|
||||||
message = _("Check your receiving server details");
|
message = _("Check your receiving server details");
|
||||||
}
|
}
|
||||||
|
|
@ -197,9 +151,9 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
// There was an SMTP auth error, but IMAP already
|
// There was an SMTP auth error, but IMAP already
|
||||||
// succeeded, so the user probably needs to
|
// succeeded, so the user probably needs to
|
||||||
// specify custom creds here
|
// specify custom creds here
|
||||||
this.smtp_auth.value.source =
|
this.receiving_service_widget.service.credentials_requirement =
|
||||||
Geary.Credentials.Requirement.CUSTOM;
|
Geary.Credentials.Requirement.CUSTOM;
|
||||||
to_focus = this.smtp_login.value;
|
to_focus = this.receiving_service_widget;
|
||||||
// Translators: In-app notification label
|
// Translators: In-app notification label
|
||||||
message = _("Check your sending login and password");
|
message = _("Check your sending login and password");
|
||||||
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
|
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
|
||||||
|
|
@ -212,8 +166,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
Geary.ErrorContext context = new Geary.ErrorContext(err);
|
Geary.ErrorContext context = new Geary.ErrorContext(err);
|
||||||
debug("Error validating SMTP service: %s",
|
debug("Error validating SMTP service: %s",
|
||||||
context.format_full_error());
|
context.format_full_error());
|
||||||
this.smtp_tls.show();
|
to_focus = this.sending_service_widget;
|
||||||
to_focus = this.smtp_hostname.value;
|
|
||||||
// Translators: In-app notification label
|
// Translators: In-app notification label
|
||||||
message = _("Check your sending server details");
|
message = _("Check your sending server details");
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +181,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
is_valid = true;
|
is_valid = true;
|
||||||
} catch (Geary.ImapError.UNAUTHENTICATED err) {
|
} catch (Geary.ImapError.UNAUTHENTICATED err) {
|
||||||
debug("Error authenticating provider: %s", err.message);
|
debug("Error authenticating provider: %s", err.message);
|
||||||
to_focus = this.email.value;
|
to_focus = this.email_row;
|
||||||
// Translators: In-app notification label
|
// Translators: In-app notification label
|
||||||
message = _("Check your email address and password");
|
message = _("Check your email address and password");
|
||||||
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
|
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
|
||||||
|
|
@ -248,7 +201,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
if (is_valid) {
|
if (is_valid) {
|
||||||
try {
|
try {
|
||||||
yield this.accounts.create_account(account, cancellable);
|
yield this.accounts.create_account(account, cancellable);
|
||||||
this.editor.pop();
|
this.editor.pop_pane();
|
||||||
} catch (GLib.Error err) {
|
} catch (GLib.Error err) {
|
||||||
debug("Failed to create new local account: %s", err.message);
|
debug("Failed to create new local account: %s", err.message);
|
||||||
is_valid = false;
|
is_valid = false;
|
||||||
|
|
@ -268,8 +221,8 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
to_focus.grab_focus();
|
to_focus.grab_focus();
|
||||||
}
|
}
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
this.editor.add_notification(
|
this.editor.add_toast(
|
||||||
new Components.InAppNotification(
|
new Adw.Toast(
|
||||||
// Translators: In-app notification label, the
|
// Translators: In-app notification label, the
|
||||||
// string substitution is a more detailed reason.
|
// string substitution is a more detailed reason.
|
||||||
_("Account not created: %s").printf(message)
|
_("Account not created: %s").printf(message)
|
||||||
|
|
@ -280,63 +233,23 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Geary.ServiceInformation new_imap_service() {
|
private Geary.ServiceInformation new_imap_service() {
|
||||||
Geary.ServiceInformation service = new Geary.ServiceInformation(
|
var service = new Geary.ServiceInformation(
|
||||||
Geary.Protocol.IMAP, this.provider
|
Geary.Protocol.IMAP, this.provider
|
||||||
);
|
);
|
||||||
|
|
||||||
service.credentials = new Geary.Credentials(
|
service.credentials = new Geary.Credentials(
|
||||||
Geary.Credentials.Method.PASSWORD,
|
Geary.Credentials.Method.PASSWORD, ""
|
||||||
this.imap_login.value.get_text().strip(),
|
|
||||||
this.imap_password.value.get_text().strip()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Components.NetworkAddressValidator host =
|
|
||||||
(Components.NetworkAddressValidator)
|
|
||||||
this.imap_hostname.validator;
|
|
||||||
GLib.NetworkAddress address = host.validated_address;
|
|
||||||
service.host = address.hostname;
|
|
||||||
service.port = (uint16) address.port;
|
|
||||||
service.transport_security = this.imap_tls.value.method;
|
|
||||||
|
|
||||||
if (service.port == 0) {
|
|
||||||
service.port = service.get_default_port();
|
|
||||||
}
|
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Geary.ServiceInformation new_smtp_service() {
|
private Geary.ServiceInformation new_smtp_service() {
|
||||||
Geary.ServiceInformation service = new Geary.ServiceInformation(
|
return new Geary.ServiceInformation(
|
||||||
Geary.Protocol.SMTP, this.provider
|
Geary.Protocol.SMTP, this.provider
|
||||||
);
|
);
|
||||||
|
|
||||||
service.credentials_requirement = this.smtp_auth.value.source;
|
|
||||||
if (service.credentials_requirement ==
|
|
||||||
Geary.Credentials.Requirement.CUSTOM) {
|
|
||||||
service.credentials = new Geary.Credentials(
|
|
||||||
Geary.Credentials.Method.PASSWORD,
|
|
||||||
this.smtp_login.value.get_text().strip(),
|
|
||||||
this.smtp_password.value.get_text().strip()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Components.NetworkAddressValidator host =
|
|
||||||
(Components.NetworkAddressValidator)
|
|
||||||
this.smtp_hostname.validator;
|
|
||||||
GLib.NetworkAddress address = host.validated_address;
|
|
||||||
|
|
||||||
service.host = address.hostname;
|
|
||||||
service.port = (uint16) address.port;
|
|
||||||
service.transport_security = this.smtp_tls.value.method;
|
|
||||||
|
|
||||||
if (service.port == 0) {
|
|
||||||
service.port = service.get_default_port();
|
|
||||||
}
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void check_validation() {
|
private void check_validation() {
|
||||||
|
#if 0
|
||||||
bool server_settings_visible = this.stack.get_visible_child_name() == "server_settings";
|
bool server_settings_visible = this.stack.get_visible_child_name() == "server_settings";
|
||||||
bool controls_valid = true;
|
bool controls_valid = true;
|
||||||
Gtk.ListBox[] list_boxes;
|
Gtk.ListBox[] list_boxes;
|
||||||
|
|
@ -348,22 +261,23 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
list_boxes = new Gtk.ListBox[] { this.details_list };
|
list_boxes = new Gtk.ListBox[] { this.details_list };
|
||||||
}
|
}
|
||||||
foreach (Gtk.ListBox list_box in list_boxes) {
|
foreach (Gtk.ListBox list_box in list_boxes) {
|
||||||
list_box.foreach((child) => {
|
for (int i = 0; true; i++) {
|
||||||
AddPaneRow? validatable = child as AddPaneRow;
|
unowned var validatable = list_box.get_row_at_index(i) as AddPaneRow;
|
||||||
if (validatable != null && !validatable.validator.is_valid) {
|
if (validatable == null)
|
||||||
controls_valid = false;
|
break;
|
||||||
}
|
if (!validatable.validator.is_valid) {
|
||||||
});
|
controls_valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.action_button.set_sensitive(controls_valid);
|
this.action_button.set_sensitive(controls_valid);
|
||||||
this.controls_valid = controls_valid;
|
this.controls_valid = controls_valid;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_operation_ui(bool is_running) {
|
private void update_operation_ui(bool is_running) {
|
||||||
this.action_spinner.visible = is_running;
|
this.action_spinner.visible = is_running;
|
||||||
this.action_spinner.active = is_running;
|
|
||||||
this.action_button.sensitive = !is_running;
|
this.action_button.sensitive = !is_running;
|
||||||
this.back_button.sensitive = !is_running;
|
|
||||||
this.sensitive = !is_running;
|
this.sensitive = !is_running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,36 +285,37 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
this.stack.set_visible_child_name("user_settings");
|
this.stack.set_visible_child_name("user_settings");
|
||||||
this.action_button.set_label(_("_Next"));
|
this.action_button.set_label(_("_Next"));
|
||||||
this.action_button.set_sensitive(true);
|
this.action_button.set_sensitive(true);
|
||||||
this.action_button.get_style_context().remove_class("suggested-action");
|
this.action_button.remove_css_class("suggested-action");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switch_to_server_settings() {
|
private void switch_to_server_settings() {
|
||||||
this.stack.set_visible_child_name("server_settings");
|
this.stack.set_visible_child_name("server_settings");
|
||||||
this.action_button.set_label(_("_Create"));
|
this.action_button.set_label(_("_Create"));
|
||||||
this.action_button.set_sensitive(false);
|
this.action_button.set_sensitive(false);
|
||||||
this.action_button.get_style_context().add_class("suggested-action");
|
this.action_button.add_css_class("suggested-action");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set_server_settings_from_autoconfig(AutoConfig auto_config,
|
private void set_server_settings_from_autoconfig(AutoConfig auto_config,
|
||||||
GLib.AsyncResult res)
|
GLib.AsyncResult res)
|
||||||
throws Accounts.AutoConfigError {
|
throws Accounts.AutoConfigError {
|
||||||
AutoConfigValues auto_config_values = auto_config.get_config.end(res);
|
AutoConfigValues auto_config_values = auto_config.get_config.end(res);
|
||||||
Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
|
|
||||||
Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
|
|
||||||
TlsComboBox imap_tls_combo_box = this.imap_tls.value;
|
|
||||||
TlsComboBox smtp_tls_combo_box = this.smtp_tls.value;
|
|
||||||
|
|
||||||
imap_hostname_entry.text = auto_config_values.imap_server +
|
Geary.ServiceInformation imap_service = this.receiving_service_widget.service;
|
||||||
":" + auto_config_values.imap_port;
|
Geary.ServiceInformation smtp_service = this.sending_service_widget.service;
|
||||||
smtp_hostname_entry.text = auto_config_values.smtp_server +
|
|
||||||
":" + auto_config_values.smtp_port;
|
|
||||||
imap_tls_combo_box.method = auto_config_values.imap_tls_method;
|
|
||||||
smtp_tls_combo_box.method = auto_config_values.smtp_tls_method;
|
|
||||||
|
|
||||||
this.imap_hostname.hide();
|
imap_service.host = auto_config_values.imap_server;
|
||||||
this.smtp_hostname.hide();
|
imap_service.port = (uint16) uint.parse(auto_config_values.imap_port);
|
||||||
this.imap_tls.hide();
|
imap_service.transport_security = auto_config_values.imap_tls_method;
|
||||||
this.smtp_tls.hide();
|
|
||||||
|
smtp_service.host = auto_config_values.smtp_server;
|
||||||
|
smtp_service.port = (uint16) uint.parse(auto_config_values.smtp_port);
|
||||||
|
smtp_service.transport_security = auto_config_values.smtp_tls_method;
|
||||||
|
|
||||||
|
//XXX GTK4 hide servr rows
|
||||||
|
// this.imap_hostname.hide();
|
||||||
|
// this.smtp_hostname.hide();
|
||||||
|
// this.imap_tls.hide();
|
||||||
|
// this.smtp_tls.hide();
|
||||||
|
|
||||||
switch (auto_config_values.id) {
|
switch (auto_config_values.id) {
|
||||||
case "googlemail.com":
|
case "googlemail.com":
|
||||||
|
|
@ -416,25 +331,26 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set_server_settings_from_hostname(string hostname) {
|
private void set_server_settings_from_hostname(string hostname) {
|
||||||
Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
|
Geary.ServiceInformation imap_service = this.receiving_service_widget.service;
|
||||||
Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
|
Geary.ServiceInformation smtp_service = this.sending_service_widget.service;
|
||||||
string smtp_hostname = "smtp." + hostname;
|
string smtp_hostname = "smtp." + hostname;
|
||||||
string imap_hostname = "imap." + hostname;
|
string imap_hostname = "imap." + hostname;
|
||||||
string last_imap_hostname = "";
|
string last_imap_hostname = "";
|
||||||
string last_smtp_hostname = "";
|
string last_smtp_hostname = "";
|
||||||
|
|
||||||
this.imap_hostname.show();
|
// XXX GTK4 show these again if an autoconf happened
|
||||||
this.smtp_hostname.show();
|
// this.imap_hostname.show();
|
||||||
|
// this.smtp_hostname.show();
|
||||||
|
|
||||||
if (this.last_valid_hostname != "") {
|
if (this.last_valid_hostname != "") {
|
||||||
last_imap_hostname = "imap." + this.last_valid_hostname;
|
last_imap_hostname = "imap." + this.last_valid_hostname;
|
||||||
last_smtp_hostname = "smtp." + this.last_valid_hostname;
|
last_smtp_hostname = "smtp." + this.last_valid_hostname;
|
||||||
}
|
}
|
||||||
if (imap_hostname_entry.text == last_imap_hostname) {
|
if (imap_service.host == last_imap_hostname) {
|
||||||
imap_hostname_entry.text = imap_hostname;
|
imap_service.host = imap_hostname;
|
||||||
}
|
}
|
||||||
if (smtp_hostname_entry.text == last_smtp_hostname) {
|
if (smtp_service.host == last_smtp_hostname) {
|
||||||
smtp_hostname_entry.text = smtp_hostname;
|
smtp_service.host = smtp_hostname;
|
||||||
}
|
}
|
||||||
this.last_valid_hostname = hostname;
|
this.last_valid_hostname = hostname;
|
||||||
}
|
}
|
||||||
|
|
@ -458,51 +374,59 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
if (add_local) {
|
if (add_local) {
|
||||||
switch_to_server_settings();
|
switch_to_server_settings();
|
||||||
} else {
|
} else {
|
||||||
this.editor.pop();
|
this.editor.pop_pane();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_validated(Components.Validator.Trigger reason) {
|
[GtkCallback]
|
||||||
|
private void on_validated(Components.ValidatorGroup validators,
|
||||||
|
Components.Validator validator) {
|
||||||
check_validation();
|
check_validation();
|
||||||
if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) {
|
//XXX GTK4 we somehow lost the Validator.Trigger here
|
||||||
this.action_button.clicked();
|
// if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) {
|
||||||
}
|
// this.action_button.clicked();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
private void on_activated() {
|
private void on_activated() {
|
||||||
if (this.controls_valid) {
|
if (this.controls_valid) {
|
||||||
this.action_button.clicked();
|
this.action_button.clicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_email_changed() {
|
[GtkCallback]
|
||||||
Gtk.Entry imap_login_entry = this.imap_login.value;
|
private void on_email_row_changed(Gtk.Editable editable) {
|
||||||
Gtk.Entry smtp_login_entry = this.smtp_login.value;
|
var imap_service = this.receiving_service_widget.service;
|
||||||
|
var smtp_service = this.sending_service_widget.service;
|
||||||
|
|
||||||
this.auto_config_cancellable.cancel();
|
this.auto_config_cancellable.cancel();
|
||||||
|
|
||||||
if (this.email.validator.state != Components.Validator.Validity.VALID) {
|
if (this.email_validator.state != Components.Validator.Validity.VALID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string email = this.email.value.text;
|
string email = this.email_row.text;
|
||||||
string hostname = email.split("@")[1];
|
string hostname = email.split("@")[1];
|
||||||
|
|
||||||
// Do not update entries if changed by user
|
// Do not update entries if changed by user
|
||||||
if (imap_login_entry.text == this.last_valid_email) {
|
if (imap_service.credentials.user == this.last_valid_email) {
|
||||||
imap_login_entry.text = email;
|
imap_service.credentials = new Geary.Credentials(
|
||||||
|
Geary.Credentials.Method.PASSWORD, email
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (smtp_login_entry.text == this.last_valid_email) {
|
if (smtp_service.credentials.user == this.last_valid_email) {
|
||||||
smtp_login_entry.text = email;
|
smtp_service.credentials = new Geary.Credentials(
|
||||||
|
Geary.Credentials.Method.PASSWORD, email
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.last_valid_email = email;
|
this.last_valid_email = email;
|
||||||
|
|
||||||
// Try to get configuration from Thunderbird autoconfig service
|
// Try to get configuration from Thunderbird autoconfig service
|
||||||
this.action_spinner.visible = true;
|
this.action_spinner.visible = true;
|
||||||
this.action_spinner.active = true;
|
|
||||||
this.auto_config_cancellable = new GLib.Cancellable();
|
this.auto_config_cancellable = new GLib.Cancellable();
|
||||||
var auto_config = new AutoConfig(this.auto_config_cancellable);
|
var auto_config = new AutoConfig(this.auto_config_cancellable);
|
||||||
auto_config.get_config.begin(hostname, (obj, res) => {
|
auto_config.get_config.begin(hostname, (obj, res) => {
|
||||||
|
|
@ -513,19 +437,20 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
set_server_settings_from_hostname(hostname);
|
set_server_settings_from_hostname(hostname);
|
||||||
}
|
}
|
||||||
this.action_spinner.visible = false;
|
this.action_spinner.visible = false;
|
||||||
this.action_spinner.active = false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_smtp_auth_changed() {
|
private void on_smtp_auth_changed() {
|
||||||
|
#if 0
|
||||||
if (this.smtp_auth.value.source == Geary.Credentials.Requirement.CUSTOM) {
|
if (this.smtp_auth.value.source == Geary.Credentials.Requirement.CUSTOM) {
|
||||||
this.sending_list.add(this.smtp_login);
|
this.sending_list.append(this.smtp_login);
|
||||||
this.sending_list.add(this.smtp_password);
|
this.sending_list.append(this.smtp_password);
|
||||||
} else if (this.smtp_login.parent != null) {
|
} else if (this.smtp_login.parent != null) {
|
||||||
this.sending_list.remove(this.smtp_login);
|
this.sending_list.remove(this.smtp_login);
|
||||||
this.sending_list.remove(this.smtp_password);
|
this.sending_list.remove(this.smtp_password);
|
||||||
}
|
}
|
||||||
check_validation();
|
check_validation();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_untrusted_host(Geary.AccountInformation account,
|
private void on_untrusted_host(Geary.AccountInformation account,
|
||||||
|
|
@ -564,221 +489,4 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
|
||||||
this.validate_account.begin(this.op_cancellable);
|
this.validate_account.begin(this.op_cancellable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
|
||||||
private void on_back_button_clicked() {
|
|
||||||
if (this.stack.get_visible_child_name() == "user_settings") {
|
|
||||||
this.editor.pop();
|
|
||||||
} else {
|
|
||||||
switch_to_user_settings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[GtkCallback]
|
|
||||||
private bool on_list_keynav_failed(Gtk.Widget widget,
|
|
||||||
Gtk.DirectionType direction) {
|
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
|
||||||
Gtk.Container? next = null;
|
|
||||||
if (direction == Gtk.DirectionType.DOWN) {
|
|
||||||
if (widget == this.details_list) {
|
|
||||||
debug("Have details!");
|
|
||||||
next = this.receiving_list;
|
|
||||||
} else if (widget == this.receiving_list) {
|
|
||||||
next = this.sending_list;
|
|
||||||
}
|
|
||||||
} else if (direction == Gtk.DirectionType.UP) {
|
|
||||||
if (widget == this.sending_list) {
|
|
||||||
next = this.receiving_list;
|
|
||||||
} else if (widget == this.receiving_list) {
|
|
||||||
next = this.details_list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next != null) {
|
|
||||||
next.child_focus(direction);
|
|
||||||
ret = Gdk.EVENT_STOP;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private abstract class Accounts.AddPaneRow<Value> :
|
|
||||||
LabelledEditorRow<EditorAddPane,Value> {
|
|
||||||
|
|
||||||
|
|
||||||
internal Components.Validator? validator { get; protected set; }
|
|
||||||
|
|
||||||
|
|
||||||
protected AddPaneRow(string label, Value value) {
|
|
||||||
base(label, value);
|
|
||||||
this.activatable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private abstract class Accounts.EntryRow : AddPaneRow<Gtk.Entry> {
|
|
||||||
|
|
||||||
|
|
||||||
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 = 16;
|
|
||||||
|
|
||||||
this.undo = new Components.EntryUndo(this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool focus(Gtk.DirectionType direction) {
|
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
|
||||||
switch (direction) {
|
|
||||||
case Gtk.DirectionType.TAB_FORWARD:
|
|
||||||
case Gtk.DirectionType.TAB_BACKWARD:
|
|
||||||
ret = this.value.child_focus(direction);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
ret = base.focus(direction);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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"), default_name.strip());
|
|
||||||
this.validator = new Components.Validator(this.value);
|
|
||||||
if (this.value.text != "") {
|
|
||||||
// Validate if the string is non-empty so it will be good
|
|
||||||
// to go from the start
|
|
||||||
this.validator.validate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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")
|
|
||||||
);
|
|
||||||
this.value.input_purpose = Gtk.InputPurpose.EMAIL;
|
|
||||||
this.validator = new Components.EmailValidator(this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class Accounts.LoginRow : EntryRow {
|
|
||||||
|
|
||||||
public LoginRow() {
|
|
||||||
// Translators: Label for an IMAP/SMTP service login/user name
|
|
||||||
// when adding an account
|
|
||||||
base(_("Login name"));
|
|
||||||
// Logins are not infrequently the same as the user's email
|
|
||||||
// address
|
|
||||||
this.value.input_purpose = Gtk.InputPurpose.EMAIL;
|
|
||||||
this.validator = new Components.Validator(this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class Accounts.PasswordRow : EntryRow {
|
|
||||||
|
|
||||||
|
|
||||||
public PasswordRow() {
|
|
||||||
base(_("Password"));
|
|
||||||
this.value.visibility = false;
|
|
||||||
this.value.input_purpose = Gtk.InputPurpose.PASSWORD;
|
|
||||||
this.validator = new Components.Validator(this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class Accounts.HostnameRow : EntryRow {
|
|
||||||
|
|
||||||
|
|
||||||
private Geary.Protocol type;
|
|
||||||
|
|
||||||
|
|
||||||
public HostnameRow(Geary.Protocol type) {
|
|
||||||
string label = "";
|
|
||||||
string placeholder = "";
|
|
||||||
switch (type) {
|
|
||||||
case Geary.Protocol.IMAP:
|
|
||||||
// Translators: Label for the IMAP server hostname when
|
|
||||||
// adding an account.
|
|
||||||
label = _("IMAP server");
|
|
||||||
// Translators: Placeholder for the IMAP server hostname
|
|
||||||
// when adding an account.
|
|
||||||
placeholder = _("imap.example.com");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Geary.Protocol.SMTP:
|
|
||||||
// Translators: Label for the SMTP server hostname when
|
|
||||||
// adding an account.
|
|
||||||
label = _("SMTP server");
|
|
||||||
// Translators: Placeholder for the SMTP server hostname
|
|
||||||
// when adding an account.
|
|
||||||
placeholder = _("smtp.example.com");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
base(label, null, placeholder);
|
|
||||||
this.type = type;
|
|
||||||
|
|
||||||
this.validator = new Components.NetworkAddressValidator(this.value, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class Accounts.TransportSecurityRow :
|
|
||||||
LabelledEditorRow<EditorAddPane,TlsComboBox> {
|
|
||||||
|
|
||||||
public TransportSecurityRow() {
|
|
||||||
TlsComboBox value = new TlsComboBox();
|
|
||||||
base(value.label, value);
|
|
||||||
// Set to Transport TLS by default per RFC 8314
|
|
||||||
this.value.method = Geary.TlsNegotiationMethod.TRANSPORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class Accounts.OutgoingAuthRow :
|
|
||||||
LabelledEditorRow<EditorAddPane,OutgoingAuthComboBox> {
|
|
||||||
|
|
||||||
public OutgoingAuthRow() {
|
|
||||||
OutgoingAuthComboBox value = new OutgoingAuthComboBox();
|
|
||||||
base(value.label, value);
|
|
||||||
|
|
||||||
this.activatable = false;
|
|
||||||
this.value.source = Geary.Credentials.Requirement.USE_INCOMING;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,9 @@
|
||||||
* An account editor pane for editing a specific account's preferences.
|
* An account editor pane for editing a specific account's preferences.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_edit_pane.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_edit_pane.ui")]
|
||||||
internal class Accounts.EditorEditPane :
|
internal class Accounts.EditorEditPane : EditorPane, AccountPane, CommandPane {
|
||||||
Gtk.Grid, EditorPane, AccountPane, CommandPane {
|
|
||||||
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
internal Gtk.Widget initial_widget {
|
|
||||||
get { return this.details_list.get_row_at_index(0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal Geary.AccountInformation account { get ; protected set; }
|
internal Geary.AccountInformation account { get ; protected set; }
|
||||||
|
|
||||||
|
|
@ -27,32 +21,28 @@ internal class Accounts.EditorEditPane :
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal bool is_operation_running { get; protected set; default = false; }
|
internal override bool is_operation_running { get; protected set; default = false; }
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal GLib.Cancellable? op_cancellable {
|
internal override Cancellable? op_cancellable {
|
||||||
get; protected set; default = null;
|
get; protected set; default = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
protected weak Accounts.Editor editor { get; set; }
|
protected override weak Accounts.Editor editor { get; set; }
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.HeaderBar header;
|
[GtkChild] private unowned Adw.HeaderBar header;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid pane_content;
|
[GtkChild] private unowned Adw.EntryRow display_name_row;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Adjustment pane_adjustment;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox details_list;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox senders_list;
|
[GtkChild] private unowned Gtk.ListBox senders_list;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Frame signature_frame;
|
[GtkChild] private unowned Adw.PreferencesGroup signature_bin;
|
||||||
|
|
||||||
private SignatureWebView signature_preview;
|
private SignatureWebView signature_preview;
|
||||||
private bool signature_changed = false;
|
private bool signature_changed = false;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox settings_list;
|
[GtkChild] private unowned Adw.ComboRow email_prefetch_row;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Button undo_button;
|
[GtkChild] private unowned Gtk.Button undo_button;
|
||||||
|
|
||||||
|
|
@ -63,24 +53,15 @@ internal class Accounts.EditorEditPane :
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
this.account = account;
|
this.account = account;
|
||||||
|
|
||||||
this.pane_content.set_focus_vadjustment(this.pane_adjustment);
|
update_display_name();
|
||||||
|
|
||||||
this.details_list.set_header_func(Editor.seperator_headers);
|
|
||||||
this.details_list.add(
|
|
||||||
new DisplayNameRow(account, this.commands, this.op_cancellable)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.senders_list.set_header_func(Editor.seperator_headers);
|
|
||||||
foreach (Geary.RFC822.MailboxAddress sender in
|
foreach (Geary.RFC822.MailboxAddress sender in
|
||||||
account.sender_mailboxes) {
|
account.sender_mailboxes) {
|
||||||
this.senders_list.add(new_mailbox_row(sender));
|
this.senders_list.append(new_mailbox_row(sender));
|
||||||
}
|
}
|
||||||
this.senders_list.add(new AddMailboxRow());
|
|
||||||
|
|
||||||
this.signature_preview = new SignatureWebView(editor.application.config);
|
this.signature_preview = new SignatureWebView(editor.application.config);
|
||||||
this.signature_preview.events = (
|
this.signature_preview.add_css_class("card");
|
||||||
this.signature_preview.events | Gdk.EventType.FOCUS_CHANGE
|
|
||||||
);
|
|
||||||
this.signature_preview.content_loaded.connect(() => {
|
this.signature_preview.content_loaded.connect(() => {
|
||||||
// Only enable editability after the content has fully
|
// Only enable editability after the content has fully
|
||||||
// loaded to avoid the WebProcess crashing.
|
// loaded to avoid the WebProcess crashing.
|
||||||
|
|
@ -91,33 +72,30 @@ internal class Accounts.EditorEditPane :
|
||||||
this.signature_preview.document_modified.connect(() => {
|
this.signature_preview.document_modified.connect(() => {
|
||||||
this.signature_changed = true;
|
this.signature_changed = true;
|
||||||
});
|
});
|
||||||
this.signature_preview.focus_out_event.connect(() => {
|
var focus_controller = new Gtk.EventControllerFocus();
|
||||||
// This event will also be fired if the top-level
|
focus_controller.leave.connect(() => {
|
||||||
// window loses focus, e.g. if the user alt-tabs away,
|
// This event will also be fired if the top-level
|
||||||
// so don't execute the command if the signature web
|
// window loses focus, e.g. if the user alt-tabs away,
|
||||||
// view no longer the focus widget
|
// so don't execute the command if the signature web
|
||||||
if (!this.signature_preview.is_focus &&
|
// view no longer the focus widget
|
||||||
this.signature_changed) {
|
if (!this.signature_preview.is_focus() &&
|
||||||
this.commands.execute.begin(
|
this.signature_changed) {
|
||||||
new SignatureChangedCommand(
|
this.commands.execute.begin(
|
||||||
this.signature_preview, account
|
new SignatureChangedCommand(
|
||||||
),
|
this.signature_preview, account
|
||||||
this.op_cancellable
|
),
|
||||||
);
|
this.op_cancellable
|
||||||
}
|
);
|
||||||
return Gdk.EVENT_PROPAGATE;
|
}
|
||||||
});
|
});
|
||||||
|
this.signature_preview.add_controller(focus_controller);
|
||||||
|
|
||||||
|
this.signature_bin.add(this.signature_preview);
|
||||||
|
|
||||||
this.signature_preview.show();
|
|
||||||
this.signature_preview.load_html(
|
this.signature_preview.load_html(
|
||||||
Geary.HTML.smart_escape(account.signature)
|
Geary.HTML.smart_escape(account.signature)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.signature_frame.add(this.signature_preview);
|
|
||||||
|
|
||||||
this.settings_list.set_header_func(Editor.seperator_headers);
|
|
||||||
this.settings_list.add(new EmailPrefetchRow(this));
|
|
||||||
|
|
||||||
this.remove_button.set_visible(
|
this.remove_button.set_visible(
|
||||||
!this.editor.accounts.is_goa_account(account)
|
!this.editor.accounts.is_goa_account(account)
|
||||||
);
|
);
|
||||||
|
|
@ -131,8 +109,12 @@ internal class Accounts.EditorEditPane :
|
||||||
disconnect_command_signals();
|
disconnect_command_signals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void update_display_name() {
|
||||||
|
this.display_name_row.text = this.account.display_name;
|
||||||
|
}
|
||||||
|
|
||||||
internal string? get_default_name() {
|
internal string? get_default_name() {
|
||||||
string? name = account.primary_mailbox.name;
|
string? name = this.account.primary_mailbox.name;
|
||||||
|
|
||||||
if (Geary.String.is_empty_or_whitespace(name)) {
|
if (Geary.String.is_empty_or_whitespace(name)) {
|
||||||
name = this.editor.accounts.get_account_name();
|
name = this.editor.accounts.get_account_name();
|
||||||
|
|
@ -141,15 +123,11 @@ internal class Accounts.EditorEditPane :
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
internal Gtk.HeaderBar get_header() {
|
|
||||||
return this.header;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal MailboxRow new_mailbox_row(Geary.RFC822.MailboxAddress sender) {
|
internal MailboxRow new_mailbox_row(Geary.RFC822.MailboxAddress sender) {
|
||||||
MailboxRow row = new MailboxRow(this.account, sender);
|
MailboxRow row = new MailboxRow(this.account, sender, this);
|
||||||
row.move_to.connect(on_sender_row_moved);
|
//XXX GTK4
|
||||||
row.dropped.connect(on_sender_row_dropped);
|
// row.move_to.connect(on_sender_row_moved);
|
||||||
|
// row.dropped.connect(on_sender_row_dropped);
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,85 +170,96 @@ internal class Accounts.EditorEditPane :
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
|
||||||
private void on_setting_activated(Gtk.ListBoxRow row) {
|
|
||||||
EditorRow<EditorEditPane>? setting = row as EditorRow<EditorEditPane>;
|
|
||||||
if (setting != null) {
|
|
||||||
setting.activated(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_server_settings_clicked() {
|
private void on_server_settings_clicked() {
|
||||||
this.editor.push(new EditorServersPane(this.editor, this.account));
|
this.editor.push_pane(new EditorServersPane(this.editor, this.account));
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_remove_account_clicked() {
|
private void on_remove_account_clicked() {
|
||||||
if (!this.editor.accounts.is_goa_account(account)) {
|
if (!this.editor.accounts.is_goa_account(account)) {
|
||||||
var button = new Gtk.Button.with_mnemonic(_("Remove Account"));
|
var dialog = new Adw.AlertDialog(
|
||||||
button.get_style_context().add_class(Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);
|
_("Remove Account: %s").printf(account.primary_mailbox.address),
|
||||||
button.show();
|
_("This will remove it from Geary and delete locally cached email data from your computer. Nothing will be deleted from your service provider.")
|
||||||
|
);
|
||||||
|
dialog.add_css_class("warning");
|
||||||
|
|
||||||
var dialog = new Gtk.MessageDialog(this.editor,
|
dialog.add_response("cancel", _("_Cancel"));
|
||||||
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
dialog.close_response = "cancel";
|
||||||
Gtk.MessageType.WARNING,
|
dialog.add_response("remove", _("_Remove Account"));
|
||||||
Gtk.ButtonsType.NONE,
|
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE);
|
||||||
_("Remove Account: %s"),
|
|
||||||
account.primary_mailbox.address);
|
|
||||||
dialog.secondary_text = _("This will remove it from Geary and delete locally cached email data from your computer. Nothing will be deleted from your service provider.");
|
|
||||||
|
|
||||||
dialog.add_button (_("_Cancel"), Gtk.ResponseType.CANCEL);
|
dialog.choose.begin(this, null, (obj, res) => {
|
||||||
dialog.add_action_widget(button, Gtk.ResponseType.ACCEPT);
|
string response = dialog.choose.end(res);
|
||||||
|
if (response == "remove")
|
||||||
dialog.response.connect((response_id) => {
|
|
||||||
if (response_id == Gtk.ResponseType.ACCEPT)
|
|
||||||
this.editor.remove_account(this.account);
|
this.editor.remove_account(this.account);
|
||||||
|
|
||||||
dialog.destroy();
|
|
||||||
});
|
});
|
||||||
dialog.show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_back_button_clicked() {
|
private void on_add_mailbox_clicked(Gtk.Button add_button) {
|
||||||
this.editor.pop();
|
var dialog = new MailboxEditorDialog.for_new(get_default_name());
|
||||||
|
dialog.apply.connect((dialog, mailbox) => {
|
||||||
|
this.commands.execute.begin(
|
||||||
|
new AppendMailboxCommand(
|
||||||
|
this.senders_list,
|
||||||
|
new_mailbox_row(mailbox)
|
||||||
|
),
|
||||||
|
this.op_cancellable
|
||||||
|
);
|
||||||
|
dialog.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.present(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private bool on_list_keynav_failed(Gtk.Widget widget,
|
private static string period_to_string(Adw.EnumListItem item,
|
||||||
Gtk.DirectionType direction) {
|
Accounts.PrefetchPeriod period) {
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
return period.to_string();
|
||||||
Gtk.Container? next = null;
|
|
||||||
if (direction == Gtk.DirectionType.DOWN) {
|
|
||||||
if (widget == this.details_list) {
|
|
||||||
next = this.senders_list;
|
|
||||||
} else if (widget == this.senders_list) {
|
|
||||||
this.signature_preview.grab_focus();
|
|
||||||
} else if (widget == this.signature_preview) {
|
|
||||||
next = this.settings_list;
|
|
||||||
}
|
|
||||||
} else if (direction == Gtk.DirectionType.UP) {
|
|
||||||
if (widget == this.settings_list) {
|
|
||||||
this.signature_preview.grab_focus();
|
|
||||||
} else if (widget == this.signature_preview) {
|
|
||||||
next = this.senders_list;
|
|
||||||
} else if (widget == this.senders_list) {
|
|
||||||
next = this.details_list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next != null) {
|
|
||||||
next.child_focus(direction);
|
|
||||||
ret = Gdk.EVENT_STOP;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum to describe the possible values for the "Download Mail" option
|
||||||
|
*/
|
||||||
|
public enum Accounts.PrefetchPeriod {
|
||||||
|
|
||||||
|
2_WEEKS = 14,
|
||||||
|
1_MONTH = 30,
|
||||||
|
3_MONTHS = 90,
|
||||||
|
6_MONTHS = 180,
|
||||||
|
1_YEAR = 365,
|
||||||
|
2_YEARS = 720,
|
||||||
|
4_YEARS = 1461,
|
||||||
|
EVERYTHING = -1;
|
||||||
|
|
||||||
|
public unowned string to_string() {
|
||||||
|
switch (this) {
|
||||||
|
case 2_WEEKS:
|
||||||
|
return _("2 weeks back");
|
||||||
|
case 1_MONTH:
|
||||||
|
return _("1 month back");
|
||||||
|
case 3_MONTHS:
|
||||||
|
return _("3 months back");
|
||||||
|
case 6_MONTHS:
|
||||||
|
return _("6 months back");
|
||||||
|
case 1_YEAR:
|
||||||
|
return _("1 year back");
|
||||||
|
case 2_YEARS:
|
||||||
|
return _("2 years back");
|
||||||
|
case 4_YEARS:
|
||||||
|
return _("4 years back");
|
||||||
|
case EVERYTHING:
|
||||||
|
return _("Everything");
|
||||||
|
}
|
||||||
|
|
||||||
|
return_val_if_reached("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
|
private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -299,7 +288,9 @@ private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
|
||||||
// undoable
|
// undoable
|
||||||
this.value_undo = new Components.EntryUndo(this.value);
|
this.value_undo = new Components.EntryUndo(this.value);
|
||||||
|
|
||||||
this.value.focus_out_event.connect(on_focus_out);
|
var focus_controller = new Gtk.EventControllerFocus();
|
||||||
|
focus_controller.leave.connect(on_focus_out);
|
||||||
|
this.value.add_controller(focus_controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void update() {
|
public override void update() {
|
||||||
|
|
@ -337,231 +328,75 @@ private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_focus_out() {
|
private void on_focus_out() {
|
||||||
commit();
|
commit();
|
||||||
return Gdk.EVENT_PROPAGATE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class Accounts.AddMailboxRow : AddRow<EditorEditPane> {
|
private class Accounts.MailboxRow : Adw.ActionRow {
|
||||||
|
|
||||||
|
public Geary.AccountInformation account { get; construct set; }
|
||||||
|
|
||||||
public AddMailboxRow() {
|
public Geary.RFC822.MailboxAddress mailbox { get; construct set; }
|
||||||
// Translators: Tooltip for adding a new email sender/from
|
|
||||||
// address's address to an account
|
|
||||||
this.set_tooltip_text(_("Add a new sender email address"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void activated(EditorEditPane pane) {
|
public unowned Accounts.EditorEditPane pane { get; construct set; }
|
||||||
MailboxEditorPopover popover = new MailboxEditorPopover(
|
|
||||||
pane.get_default_name() ?? "", "", false
|
|
||||||
);
|
|
||||||
popover.activated.connect(() => {
|
|
||||||
pane.commands.execute.begin(
|
|
||||||
new AppendMailboxCommand(
|
|
||||||
(Gtk.ListBox) get_parent(),
|
|
||||||
pane.new_mailbox_row(
|
|
||||||
new Geary.RFC822.MailboxAddress(
|
|
||||||
popover.display_name,
|
|
||||||
popover.address
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
pane.op_cancellable
|
|
||||||
);
|
|
||||||
popover.popdown();
|
|
||||||
});
|
|
||||||
|
|
||||||
popover.set_relative_to(this);
|
|
||||||
popover.popup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class Accounts.MailboxRow : AccountRow<EditorEditPane,Gtk.Label> {
|
|
||||||
|
|
||||||
|
|
||||||
internal Geary.RFC822.MailboxAddress mailbox;
|
|
||||||
|
|
||||||
|
|
||||||
public MailboxRow(Geary.AccountInformation account,
|
public MailboxRow(Geary.AccountInformation account,
|
||||||
Geary.RFC822.MailboxAddress mailbox) {
|
Geary.RFC822.MailboxAddress mailbox,
|
||||||
var label = new Gtk.Label("");
|
Accounts.EditorEditPane pane) {
|
||||||
label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
|
Object(
|
||||||
label.set_line_wrap(true);
|
account: account,
|
||||||
base(account, "", label);
|
mailbox: mailbox,
|
||||||
this.mailbox = mailbox;
|
pane: pane,
|
||||||
enable_drag();
|
activatable: true
|
||||||
|
);
|
||||||
|
|
||||||
|
//XXX GTK4 do this again
|
||||||
|
// enable_drag();
|
||||||
|
|
||||||
|
//XXX GTK4 also on notify
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void activated(EditorEditPane pane) {
|
public override void activate() {
|
||||||
MailboxEditorPopover popover = new MailboxEditorPopover(
|
var dialog = new MailboxEditorDialog.for_existing(
|
||||||
this.mailbox.name ?? "",
|
this.mailbox,
|
||||||
this.mailbox.address,
|
|
||||||
this.account.has_sender_aliases
|
this.account.has_sender_aliases
|
||||||
);
|
);
|
||||||
popover.activated.connect(() => {
|
|
||||||
pane.commands.execute.begin(
|
|
||||||
new UpdateMailboxCommand(
|
|
||||||
this,
|
|
||||||
new Geary.RFC822.MailboxAddress(
|
|
||||||
popover.display_name,
|
|
||||||
popover.address
|
|
||||||
)
|
|
||||||
),
|
|
||||||
pane.op_cancellable
|
|
||||||
);
|
|
||||||
popover.popdown();
|
|
||||||
});
|
|
||||||
popover.remove_clicked.connect(() => {
|
|
||||||
pane.commands.execute.begin(
|
|
||||||
new RemoveMailboxCommand(this),
|
|
||||||
pane.op_cancellable
|
|
||||||
);
|
|
||||||
popover.popdown();
|
|
||||||
});
|
|
||||||
|
|
||||||
popover.set_relative_to(this);
|
dialog.apply.connect((dialog, mailbox) => {
|
||||||
popover.popup();
|
this.pane.commands.execute.begin(
|
||||||
|
new UpdateMailboxCommand(this, mailbox),
|
||||||
|
this.pane.op_cancellable
|
||||||
|
);
|
||||||
|
dialog.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.remove.connect((dialog) => {
|
||||||
|
this.pane.commands.execute.begin(
|
||||||
|
new RemoveMailboxCommand(this),
|
||||||
|
this.pane.op_cancellable
|
||||||
|
);
|
||||||
|
dialog.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.present(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void update() {
|
private void update() {
|
||||||
|
this.title = mailbox.address.strip();
|
||||||
|
|
||||||
string? name = this.mailbox.name;
|
string? name = this.mailbox.name;
|
||||||
if (Geary.String.is_empty_or_whitespace(name)) {
|
if (Geary.String.is_empty_or_whitespace(name)) {
|
||||||
// Translators: Label used to indicate the user has
|
// Translators: Label used to indicate the user has
|
||||||
// provided no display name for one of their sender
|
// provided no display name for one of their sender
|
||||||
// email addresses in their account settings.
|
// email addresses in their account settings.
|
||||||
name = _("Name not set");
|
name = _("Name not set");
|
||||||
set_dim_label(true);
|
|
||||||
} else {
|
|
||||||
set_dim_label(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.label.set_text(name);
|
|
||||||
this.value.set_text(mailbox.address.strip());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class Accounts.MailboxEditorPopover : EditorPopover {
|
|
||||||
|
|
||||||
|
|
||||||
public string display_name { get; private set; }
|
|
||||||
public string address { get; private set; }
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
public signal void activated();
|
|
||||||
public signal void remove_clicked();
|
|
||||||
|
|
||||||
|
|
||||||
public MailboxEditorPopover(string? display_name,
|
|
||||||
string? address,
|
|
||||||
bool can_remove) {
|
|
||||||
this.display_name = display_name;
|
|
||||||
this.address = address;
|
|
||||||
|
|
||||||
this.name_entry.set_text(display_name ?? "");
|
|
||||||
this.name_entry.set_placeholder_text(
|
|
||||||
// Translators: This is used as a placeholder for the
|
|
||||||
// display name for an email address when editing a user's
|
|
||||||
// sender address preferences for an account.
|
|
||||||
_("Sender Name")
|
|
||||||
);
|
|
||||||
this.name_entry.set_width_chars(20);
|
|
||||||
this.name_entry.changed.connect(on_name_changed);
|
|
||||||
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(
|
|
||||||
// Translators: This is used as a placeholder for the
|
|
||||||
// address part of an email address when editing a user's
|
|
||||||
// sender address preferences for an account.
|
|
||||||
_("person@example.com")
|
|
||||||
);
|
|
||||||
this.address_entry.set_width_chars(20);
|
|
||||||
this.address_entry.changed.connect(on_address_changed);
|
|
||||||
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);
|
|
||||||
|
|
||||||
this.remove_button = new Gtk.Button.with_label(_("Remove"));
|
|
||||||
this.remove_button.halign = Gtk.Align.END;
|
|
||||||
this.remove_button.get_style_context().add_class(
|
|
||||||
"geary-setting-remove"
|
|
||||||
);
|
|
||||||
this.remove_button.get_style_context().add_class(
|
|
||||||
Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION
|
|
||||||
);
|
|
||||||
this.remove_button.clicked.connect(on_remove_clicked);
|
|
||||||
this.remove_button.show();
|
|
||||||
|
|
||||||
add_labelled_row(
|
|
||||||
// Translators: Label used for the display name part of an
|
|
||||||
// email address when editing a user's sender address
|
|
||||||
// preferences for an account.
|
|
||||||
_("Sender name"),
|
|
||||||
this.name_entry
|
|
||||||
);
|
|
||||||
add_labelled_row(
|
|
||||||
// Translators: Label used for the address part of an
|
|
||||||
// email address when editing a user's sender address
|
|
||||||
// preferences for an account.
|
|
||||||
_("Email address"),
|
|
||||||
this.address_entry
|
|
||||||
);
|
|
||||||
|
|
||||||
if (can_remove) {
|
|
||||||
this.layout.attach(this.remove_button, 0, 2, 2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.popup_focus = this.name_entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
~MailboxEditorPopover() {
|
|
||||||
this.name_entry.changed.disconnect(on_name_changed);
|
|
||||||
this.name_entry.activate.disconnect(on_activate);
|
|
||||||
|
|
||||||
this.address_entry.changed.disconnect(on_address_changed);
|
|
||||||
this.address_entry.activate.disconnect(on_activate);
|
|
||||||
|
|
||||||
this.remove_button.clicked.disconnect(on_remove_clicked);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_name_changed() {
|
|
||||||
this.display_name = this.name_entry.get_text().strip();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_address_changed() {
|
|
||||||
this.address = this.address_entry.get_text().strip();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_remove_clicked() {
|
|
||||||
remove_clicked();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_activate() {
|
|
||||||
if (this.address_validator.state == Components.Validator.Validity.INDETERMINATE || this.address_validator.is_valid) {
|
|
||||||
activated();
|
|
||||||
}
|
}
|
||||||
|
this.subtitle = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
* An account editor pane for listing all known accounts.
|
* An account editor pane for listing all known accounts.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_list_pane.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_list_pane.ui")]
|
||||||
internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
internal class Accounts.EditorListPane : Accounts.EditorPane, CommandPane {
|
||||||
|
|
||||||
|
|
||||||
private static int ordinal_sort(Gtk.ListBoxRow a, Gtk.ListBoxRow b) {
|
private static int ordinal_sort(Gtk.ListBoxRow a, Gtk.ListBoxRow b) {
|
||||||
|
|
@ -29,29 +29,22 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal Gtk.Widget initial_widget {
|
internal override Application.CommandStack commands {
|
||||||
get {
|
|
||||||
return this.accounts_list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
internal Application.CommandStack commands {
|
|
||||||
get; protected set; default = new Application.CommandStack();
|
get; protected set; default = new Application.CommandStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal bool is_operation_running { get; protected set; default = false; }
|
internal override bool is_operation_running { get; protected set; default = false; }
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal GLib.Cancellable? op_cancellable {
|
internal override Cancellable? op_cancellable {
|
||||||
get; protected set; default = null;
|
get; protected set; default = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Manager accounts { get; private set; }
|
internal Manager accounts { get; private set; }
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
protected weak Accounts.Editor editor { get; set; }
|
protected override weak Accounts.Editor editor { get; set; }
|
||||||
|
|
||||||
private bool show_welcome {
|
private bool show_welcome {
|
||||||
get {
|
get {
|
||||||
|
|
@ -59,11 +52,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.HeaderBar header;
|
[GtkChild] private unowned Adw.HeaderBar header;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid pane_content;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Adjustment pane_adjustment;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid welcome_panel;
|
[GtkChild] private unowned Gtk.Grid welcome_panel;
|
||||||
|
|
||||||
|
|
@ -71,7 +60,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox accounts_list;
|
[GtkChild] private unowned Gtk.ListBox accounts_list;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Frame accounts_list_frame;
|
[GtkChild] private unowned Gtk.ScrolledWindow accounts_list_scrolled;
|
||||||
|
|
||||||
private Gee.Map<Geary.AccountInformation,EditorEditPane> edit_pane_cache =
|
private Gee.Map<Geary.AccountInformation,EditorEditPane> edit_pane_cache =
|
||||||
new Gee.HashMap<Geary.AccountInformation,EditorEditPane>();
|
new Gee.HashMap<Geary.AccountInformation,EditorEditPane>();
|
||||||
|
|
@ -85,9 +74,6 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
// without worrying about the editor's lifecycle
|
// without worrying about the editor's lifecycle
|
||||||
this.accounts = editor.accounts;
|
this.accounts = editor.accounts;
|
||||||
|
|
||||||
this.pane_content.set_focus_vadjustment(this.pane_adjustment);
|
|
||||||
|
|
||||||
this.accounts_list.set_header_func(Editor.seperator_headers);
|
|
||||||
this.accounts_list.set_sort_func(ordinal_sort);
|
this.accounts_list.set_sort_func(ordinal_sort);
|
||||||
foreach (Geary.AccountInformation account in this.accounts.iterable()) {
|
foreach (Geary.AccountInformation account in this.accounts.iterable()) {
|
||||||
add_account(account, this.accounts.get_status(account));
|
add_account(account, this.accounts.get_status(account));
|
||||||
|
|
@ -104,22 +90,22 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
update_welcome_panel();
|
update_welcome_panel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void destroy() {
|
public override void dispose() {
|
||||||
this.commands.executed.disconnect(on_execute);
|
this.commands.executed.disconnect(on_execute);
|
||||||
this.commands.undone.disconnect(on_undo);
|
this.commands.undone.disconnect(on_undo);
|
||||||
this.commands.redone.disconnect(on_execute);
|
this.commands.redone.disconnect(on_execute);
|
||||||
disconnect_command_signals();
|
disconnect_command_signals();
|
||||||
|
|
||||||
this.accounts.account_added.disconnect(on_account_added);
|
this.accounts.account_added.disconnect(on_account_added);
|
||||||
this.accounts.account_status_changed.disconnect(on_account_status_changed);
|
this.accounts.account_status_changed.disconnect(on_account_status_changed);
|
||||||
this.accounts.account_removed.disconnect(on_account_removed);
|
this.accounts.account_removed.disconnect(on_account_removed);
|
||||||
|
|
||||||
this.edit_pane_cache.clear();
|
this.edit_pane_cache.clear();
|
||||||
base.destroy();
|
base.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void show_new_account() {
|
internal void show_new_account() {
|
||||||
this.editor.push(new EditorAddPane(this.editor));
|
this.editor.push_pane(new EditorAddPane(this.editor));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void show_existing_account(Geary.AccountInformation account) {
|
internal void show_existing_account(Geary.AccountInformation account) {
|
||||||
|
|
@ -128,7 +114,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
edit_pane = new EditorEditPane(this.editor, account);
|
edit_pane = new EditorEditPane(this.editor, account);
|
||||||
this.edit_pane_cache.set(account, edit_pane);
|
this.edit_pane_cache.set(account, edit_pane);
|
||||||
}
|
}
|
||||||
this.editor.push(edit_pane);
|
this.editor.push_pane(edit_pane);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Removes an account from the list. */
|
/** Removes an account from the list. */
|
||||||
|
|
@ -142,17 +128,12 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
internal Gtk.HeaderBar get_header() {
|
|
||||||
return this.header;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add_account(Geary.AccountInformation account,
|
private void add_account(Geary.AccountInformation account,
|
||||||
Manager.Status status) {
|
Manager.Status status) {
|
||||||
AccountListRow row = new AccountListRow(account, status);
|
AccountListRow row = new AccountListRow(account, status);
|
||||||
row.move_to.connect(on_editor_row_moved);
|
row.moved.connect(on_account_row_moved);
|
||||||
row.dropped.connect(on_editor_row_dropped);
|
row.dropped.connect(on_editor_row_dropped);
|
||||||
this.accounts_list.add(row);
|
this.accounts_list.append(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_welcome_panel() {
|
private void update_welcome_panel() {
|
||||||
|
|
@ -160,24 +141,24 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
// No accounts are available, so show only the welcome
|
// No accounts are available, so show only the welcome
|
||||||
// pane and service list.
|
// pane and service list.
|
||||||
this.welcome_panel.show();
|
this.welcome_panel.show();
|
||||||
this.accounts_list_frame.hide();
|
this.accounts_list_scrolled.hide();
|
||||||
} else {
|
} else {
|
||||||
// There are some accounts available, so show them and
|
// There are some accounts available, so show them and
|
||||||
// the full add service UI.
|
// the full add service UI.
|
||||||
this.welcome_panel.hide();
|
this.welcome_panel.hide();
|
||||||
this.accounts_list_frame.show();
|
this.accounts_list_scrolled.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccountListRow? get_account_row(Geary.AccountInformation account) {
|
private AccountListRow? get_account_row(Geary.AccountInformation account) {
|
||||||
AccountListRow? row = null;
|
for (int i = 0; true; i++) {
|
||||||
this.accounts_list.foreach((child) => {
|
unowned var row = this.accounts_list.get_row_at_index(i) as AccountListRow;
|
||||||
AccountListRow? account_row = child as AccountListRow;
|
if (row == null)
|
||||||
if (account_row != null && account_row.account == account) {
|
break;
|
||||||
row = account_row;
|
if (row.account == account)
|
||||||
}
|
return row;
|
||||||
});
|
}
|
||||||
return row;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_account_added(Geary.AccountInformation account,
|
private void on_account_added(Geary.AccountInformation account,
|
||||||
|
|
@ -194,19 +175,17 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_editor_row_moved(EditorRow source, int new_position) {
|
private void on_account_row_moved(AccountListRow source, int new_position) {
|
||||||
this.commands.execute.begin(
|
this.commands.execute.begin(
|
||||||
new ReorderAccountCommand(
|
new ReorderAccountCommand(source, new_position, this.accounts),
|
||||||
(AccountListRow) source, new_position, this.accounts
|
|
||||||
),
|
|
||||||
this.op_cancellable
|
this.op_cancellable
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_editor_row_dropped(EditorRow source, EditorRow target) {
|
private void on_editor_row_dropped(AccountListRow source, AccountListRow target) {
|
||||||
this.commands.execute.begin(
|
this.commands.execute.begin(
|
||||||
new ReorderAccountCommand(
|
new ReorderAccountCommand(
|
||||||
(AccountListRow) source, target.get_index(), this.accounts
|
source, target.get_index(), this.accounts
|
||||||
),
|
),
|
||||||
this.op_cancellable
|
this.op_cancellable
|
||||||
);
|
);
|
||||||
|
|
@ -222,34 +201,51 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
|
|
||||||
private void on_execute(Application.Command command) {
|
private void on_execute(Application.Command command) {
|
||||||
if (command.executed_label != null) {
|
if (command.executed_label != null) {
|
||||||
uint notification_time =
|
var toast = new Adw.Toast(command.executed_label);
|
||||||
Components.InAppNotification.DEFAULT_DURATION;
|
toast.button_label = _("Undo");
|
||||||
if (command.executed_notification_brief) {
|
toast.action_name = Action.Edit.prefix(Action.Edit.UNDO);
|
||||||
notification_time =
|
if (command.executed_notification_brief)
|
||||||
this.editor.application.config.brief_notification_duration;
|
toast.timeout = this.editor.application.config.brief_notification_duration;
|
||||||
}
|
this.editor.add_toast(toast);
|
||||||
Components.InAppNotification ian = new Components.InAppNotification(
|
|
||||||
command.executed_label, notification_time
|
|
||||||
);
|
|
||||||
ian.set_button(_("Undo"), Action.Edit.prefix(Action.Edit.UNDO));
|
|
||||||
this.editor.add_notification(ian);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_undo(Application.Command command) {
|
private void on_undo(Application.Command command) {
|
||||||
if (command.undone_label != null) {
|
if (command.undone_label != null) {
|
||||||
Components.InAppNotification ian =
|
var toast = new Adw.Toast(command.undone_label);
|
||||||
new Components.InAppNotification(command.undone_label);
|
toast.button_label = _("Redo");
|
||||||
ian.set_button(_("Redo"), Action.Edit.prefix(Action.Edit.REDO));
|
toast.action_name = Action.Edit.prefix(Action.Edit.REDO);
|
||||||
this.editor.add_notification(ian);
|
this.editor.add_toast(toast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_row_activated(Gtk.ListBoxRow row) {
|
private void on_row_activated(Gtk.ListBoxRow row) {
|
||||||
EditorRow<EditorListPane>? setting = row as EditorRow<EditorListPane>;
|
unowned var account_row = row as AccountListRow;
|
||||||
if (setting != null) {
|
if (account_row == null)
|
||||||
setting.activated(this);
|
return;
|
||||||
|
|
||||||
|
Manager manager = this.accounts;
|
||||||
|
if (manager.is_goa_account(account_row.account) &&
|
||||||
|
manager.get_status(account_row.account) != Manager.Status.ENABLED) {
|
||||||
|
// GOA account but it's disabled, so just take people
|
||||||
|
// directly to the GOA panel
|
||||||
|
manager.show_goa_account.begin(
|
||||||
|
account_row.account, this.op_cancellable,
|
||||||
|
(obj, res) => {
|
||||||
|
try {
|
||||||
|
manager.show_goa_account.end(res);
|
||||||
|
} catch (GLib.Error err) {
|
||||||
|
// XXX display an error to the user
|
||||||
|
debug(
|
||||||
|
"Failed to show GOA account \"%s\": %s",
|
||||||
|
account_row.account.id,
|
||||||
|
err.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
show_existing_account(account_row.account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,28 +256,29 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
|
[GtkTemplate (ui = "/org/gnome/Geary/accounts-editor-account-list-row.ui")]
|
||||||
|
private class Accounts.AccountListRow : Adw.ActionRow {
|
||||||
|
|
||||||
|
public Geary.AccountInformation account { get; construct set; }
|
||||||
|
|
||||||
private Gtk.Label service_label = new Gtk.Label("");
|
[GtkChild] private unowned Gtk.Image drag_icon;
|
||||||
private Gtk.Image unavailable_icon = new Gtk.Image.from_icon_name(
|
[GtkChild] private unowned Gtk.Image unavailable_icon;
|
||||||
"dialog-warning-symbolic", Gtk.IconSize.BUTTON
|
|
||||||
);
|
private bool drag_picked_up = false;
|
||||||
|
private double drag_x;
|
||||||
|
private double drag_y;
|
||||||
|
|
||||||
|
public signal void moved(int new_position);
|
||||||
|
public signal void dropped(AccountListRow target);
|
||||||
|
|
||||||
|
construct {
|
||||||
|
this.account.changed.connect(on_account_changed);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
public AccountListRow(Geary.AccountInformation account,
|
public AccountListRow(Geary.AccountInformation account,
|
||||||
Manager.Status status) {
|
Manager.Status status) {
|
||||||
base(account, "", new Gtk.Grid());
|
Object(account: account);
|
||||||
enable_drag();
|
|
||||||
|
|
||||||
this.value.add(this.unavailable_icon);
|
|
||||||
this.value.add(this.service_label);
|
|
||||||
|
|
||||||
this.service_label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
|
|
||||||
this.service_label.set_line_wrap(true);
|
|
||||||
this.service_label.show();
|
|
||||||
|
|
||||||
this.account.changed.connect(on_account_changed);
|
|
||||||
update();
|
|
||||||
update_status(status);
|
update_status(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,37 +286,12 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
|
||||||
this.account.changed.disconnect(on_account_changed);
|
this.account.changed.disconnect(on_account_changed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void activated(EditorListPane pane) {
|
public void update() {
|
||||||
Manager manager = pane.accounts;
|
|
||||||
if (manager.is_goa_account(this.account) &&
|
|
||||||
manager.get_status(this.account) != Manager.Status.ENABLED) {
|
|
||||||
// GOA account but it's disabled, so just take people
|
|
||||||
// directly to the GOA panel
|
|
||||||
manager.show_goa_account.begin(
|
|
||||||
account, pane.op_cancellable,
|
|
||||||
(obj, res) => {
|
|
||||||
try {
|
|
||||||
manager.show_goa_account.end(res);
|
|
||||||
} catch (GLib.Error err) {
|
|
||||||
// XXX display an error to the user
|
|
||||||
debug(
|
|
||||||
"Failed to show GOA account \"%s\": %s",
|
|
||||||
account.id,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
pane.show_existing_account(this.account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void update() {
|
|
||||||
string name = this.account.display_name;
|
string name = this.account.display_name;
|
||||||
if (Geary.String.is_empty(name)) {
|
if (Geary.String.is_empty(name)) {
|
||||||
name = account.primary_mailbox.to_address_display("", "");
|
name = account.primary_mailbox.to_address_display("", "");
|
||||||
}
|
}
|
||||||
this.label.set_text(name);
|
this.title = name;
|
||||||
|
|
||||||
string? details = this.account.service_label;
|
string? details = this.account.service_label;
|
||||||
switch (account.service_provider) {
|
switch (account.service_provider) {
|
||||||
|
|
@ -335,32 +307,31 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
|
||||||
// no-op: Use the generated label
|
// no-op: Use the generated label
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.service_label.set_text(details);
|
this.subtitle = details;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update_status(Manager.Status status) {
|
public void update_status(Manager.Status status) {
|
||||||
bool enabled = false;
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ENABLED:
|
case ENABLED:
|
||||||
enabled = true;
|
remove_css_class("dim-label");
|
||||||
this.set_tooltip_text("");
|
this.tooltip_text = "";
|
||||||
|
this.unavailable_icon.visible = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DISABLED:
|
case DISABLED:
|
||||||
this.set_tooltip_text(
|
// Translators: Tooltip for accounts that have been
|
||||||
// Translators: Tooltip for accounts that have been
|
// loaded but disabled by the user.
|
||||||
// loaded but disabled by the user.
|
this.tooltip_text = _("This account has been disabled");
|
||||||
_("This account has been disabled")
|
add_css_class("dim-label");
|
||||||
);
|
this.unavailable_icon.visible = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UNAVAILABLE:
|
case UNAVAILABLE:
|
||||||
this.set_tooltip_text(
|
// Translators: Tooltip for accounts that have been loaded but
|
||||||
// Translators: Tooltip for accounts that have been
|
// because of some error are not able to be used.
|
||||||
// loaded but because of some error are not able to be
|
this.tooltip_text = _("This account has encountered a problem and is unavailable");
|
||||||
// used.
|
add_css_class("dim-label");
|
||||||
_("This account has encountered a problem and is unavailable")
|
this.unavailable_icon.visible = true;
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case REMOVED:
|
case REMOVED:
|
||||||
|
|
@ -368,23 +339,6 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.unavailable_icon.set_visible(!enabled);
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
this.label.get_style_context().remove_class(
|
|
||||||
Gtk.STYLE_CLASS_DIM_LABEL
|
|
||||||
);
|
|
||||||
this.service_label.get_style_context().remove_class(
|
|
||||||
Gtk.STYLE_CLASS_DIM_LABEL
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.label.get_style_context().add_class(
|
|
||||||
Gtk.STYLE_CLASS_DIM_LABEL
|
|
||||||
);
|
|
||||||
this.service_label.get_style_context().add_class(
|
|
||||||
Gtk.STYLE_CLASS_DIM_LABEL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_account_changed() {
|
private void on_account_changed() {
|
||||||
|
|
@ -395,6 +349,129 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private bool on_key_pressed(Gtk.EventControllerKey key_controller,
|
||||||
|
uint keyval,
|
||||||
|
uint keycode,
|
||||||
|
Gdk.ModifierType state) {
|
||||||
|
if (state != Gdk.ModifierType.CONTROL_MASK)
|
||||||
|
return Gdk.EVENT_PROPAGATE;
|
||||||
|
|
||||||
|
int index = get_index();
|
||||||
|
if (keyval == Gdk.Key.Up) {
|
||||||
|
index--;
|
||||||
|
if (index >= 0) {
|
||||||
|
moved(index);
|
||||||
|
return Gdk.EVENT_STOP;
|
||||||
|
}
|
||||||
|
} else if (keyval == Gdk.Key.Down) {
|
||||||
|
index++;
|
||||||
|
if (get_next_sibling() != null) {
|
||||||
|
moved(index);
|
||||||
|
return Gdk.EVENT_STOP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Gdk.EVENT_PROPAGATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DND
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_drag_source_begin(Gtk.DragSource drag_source,
|
||||||
|
Gdk.Drag drag) {
|
||||||
|
|
||||||
|
// Show our row while dragging
|
||||||
|
var drag_widget = new Gtk.ListBox();
|
||||||
|
drag_widget.opacity = 0.8;
|
||||||
|
|
||||||
|
Gtk.Allocation allocation;
|
||||||
|
get_allocation(out allocation);
|
||||||
|
drag_widget.set_size_request(allocation.width, allocation.height);
|
||||||
|
|
||||||
|
var drag_row = new AccountListRow(this.account, Manager.Status.ENABLED);
|
||||||
|
drag_widget.append(drag_row);
|
||||||
|
drag_widget.drag_highlight_row(drag_row);
|
||||||
|
|
||||||
|
var drag_icon = (Gtk.DragIcon) Gtk.DragIcon.get_for_drag(drag);
|
||||||
|
drag_icon.child = drag_widget;
|
||||||
|
drag.set_hotspot((int) this.drag_x, (int) this.drag_y);
|
||||||
|
|
||||||
|
// Set a visual hint that the row is being dragged
|
||||||
|
add_css_class("geary-drag-source");
|
||||||
|
this.drag_picked_up = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_drag_source_end(Gtk.DragSource drag_source,
|
||||||
|
Gdk.Drag drag,
|
||||||
|
bool delete_data) {
|
||||||
|
remove_css_class("geary-drag-source");
|
||||||
|
this.drag_picked_up = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private Gdk.ContentProvider on_drag_source_prepare(Gtk.DragSource drag_source,
|
||||||
|
double x,
|
||||||
|
double y) {
|
||||||
|
Graphene.Point p = { (float) x, (float) y };
|
||||||
|
Graphene.Point p_row;
|
||||||
|
this.drag_icon.compute_point(this, p, out p_row);
|
||||||
|
this.drag_x = p_row.x;
|
||||||
|
this.drag_y = p_row.y;
|
||||||
|
|
||||||
|
GLib.Value val = GLib.Value(typeof(int));
|
||||||
|
val.set_int(get_index());
|
||||||
|
return new Gdk.ContentProvider.for_value(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private Gdk.DragAction on_drop_target_enter(Gtk.DropTarget drop_target,
|
||||||
|
double x,
|
||||||
|
double y) {
|
||||||
|
// Don't highlight the same row that was picked up
|
||||||
|
if (!this.drag_picked_up) {
|
||||||
|
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
|
||||||
|
if (parent != null) {
|
||||||
|
parent.drag_highlight_row(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Gdk.DragAction.MOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_drop_target_leave(Gtk.DropTarget drop_target) {
|
||||||
|
if (!this.drag_picked_up) {
|
||||||
|
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
|
||||||
|
if (parent != null) {
|
||||||
|
parent.drag_unhighlight_row();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private bool on_drop_target_drop(Gtk.DropTarget drop_target,
|
||||||
|
GLib.Value val,
|
||||||
|
double x,
|
||||||
|
double y) {
|
||||||
|
if (!val.holds(typeof(int))) {
|
||||||
|
warning("Can't deal with non-int row value");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int drag_index = val.get_int();
|
||||||
|
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
|
||||||
|
if (parent != null) {
|
||||||
|
var drag_row = parent.get_row_at_index(drag_index) as AccountListRow;
|
||||||
|
if (drag_row != null && drag_row != this) {
|
||||||
|
drag_row.dropped(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,20 +8,14 @@
|
||||||
|
|
||||||
internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
|
internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
|
||||||
|
|
||||||
private const string DND_ATOM = "geary-editor-row";
|
|
||||||
private const Gtk.TargetEntry[] DRAG_ENTRIES = {
|
|
||||||
{ DND_ATOM, Gtk.TargetFlags.SAME_APP, 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
protected Gtk.Box layout {
|
protected Gtk.Box layout {
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
default = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5); }
|
default = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5); }
|
||||||
|
|
||||||
private Gtk.Container drag_handle;
|
private Gtk.Image drag_handle;
|
||||||
private bool drag_picked_up = false;
|
private bool drag_picked_up = false;
|
||||||
private bool drag_entered = false;
|
|
||||||
|
|
||||||
|
|
||||||
public signal void move_to(int new_position);
|
public signal void move_to(int new_position);
|
||||||
|
|
@ -29,66 +23,54 @@ internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
|
||||||
|
|
||||||
|
|
||||||
public EditorRow() {
|
public EditorRow() {
|
||||||
|
add_css_class("geary-settings");
|
||||||
get_style_context().add_class("geary-settings");
|
add_css_class("geary-labelled-row");
|
||||||
get_style_context().add_class("geary-labelled-row");
|
|
||||||
|
|
||||||
// We'd like to add the drag handle only when needed, but
|
|
||||||
// GNOME/gtk#1495 prevents us from doing so.
|
|
||||||
Gtk.EventBox drag_box = new Gtk.EventBox();
|
|
||||||
drag_box.add(
|
|
||||||
new Gtk.Image.from_icon_name(
|
|
||||||
"list-drag-handle-symbolic", Gtk.IconSize.BUTTON
|
|
||||||
)
|
|
||||||
);
|
|
||||||
this.drag_handle = new Gtk.Grid();
|
|
||||||
this.drag_handle.valign = Gtk.Align.CENTER;
|
|
||||||
this.drag_handle.add(drag_box);
|
|
||||||
this.drag_handle.show_all();
|
|
||||||
this.drag_handle.hide();
|
|
||||||
// Translators: Tooltip for dragging list items
|
|
||||||
this.drag_handle.set_tooltip_text(_("Drag to move this item"));
|
|
||||||
|
|
||||||
var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
|
var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
|
||||||
box.add(drag_handle);
|
this.child = box;
|
||||||
box.add(this.layout);
|
|
||||||
box.show();
|
|
||||||
add(box);
|
|
||||||
|
|
||||||
this.layout.show();
|
|
||||||
this.show();
|
|
||||||
|
|
||||||
this.size_allocate.connect((allocation) => {
|
var breakpoint_bin = new Adw.BreakpointBin();
|
||||||
if (allocation.width < 500) {
|
box.append(breakpoint_bin);
|
||||||
if (this.layout.orientation == Gtk.Orientation.HORIZONTAL) {
|
breakpoint_bin.child = this.layout;
|
||||||
this.layout.orientation = Gtk.Orientation.VERTICAL;
|
|
||||||
}
|
var breakpoint = new Adw.Breakpoint(Adw.BreakpointCondition.parse("max-width: 500px"));
|
||||||
} else if (this.layout.orientation == Gtk.Orientation.VERTICAL) {
|
breakpoint.add_setters(this.layout, "orientation", Gtk.Orientation.VERTICAL);
|
||||||
this.layout.orientation = Gtk.Orientation.HORIZONTAL;
|
breakpoint_bin.add_breakpoint(breakpoint);
|
||||||
}
|
|
||||||
});
|
this.drag_handle = new Gtk.Image.from_icon_name("list-drag-handle-symbolic");
|
||||||
|
this.drag_handle.valign = Gtk.Align.CENTER;
|
||||||
|
this.drag_handle.visible = false;
|
||||||
|
// Translators: Tooltip for dragging list items
|
||||||
|
this.drag_handle.set_tooltip_text(_("Drag to move this item"));
|
||||||
|
box.append(this.drag_handle);
|
||||||
|
|
||||||
|
var key_controller = new Gtk.EventControllerKey();
|
||||||
|
key_controller.key_pressed.connect(on_key_pressed);
|
||||||
|
add_controller(key_controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void activated(PaneType pane) {
|
public virtual void activated(PaneType pane) {
|
||||||
// No-op by default
|
// No-op by default
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool key_press_event(Gdk.EventKey event) {
|
private bool on_key_pressed(Gtk.EventControllerKey key_controller, uint keyval, uint keycode, Gdk.ModifierType state) {
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
bool ret = Gdk.EVENT_PROPAGATE;
|
||||||
|
|
||||||
if (event.state == Gdk.ModifierType.CONTROL_MASK) {
|
if (state == Gdk.ModifierType.CONTROL_MASK) {
|
||||||
int index = get_index();
|
int index = get_index();
|
||||||
if (event.keyval == Gdk.Key.Up) {
|
if (keyval == Gdk.Key.Up) {
|
||||||
index -= 1;
|
index -= 1;
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
move_to(index);
|
move_to(index);
|
||||||
ret = Gdk.EVENT_STOP;
|
ret = Gdk.EVENT_STOP;
|
||||||
}
|
}
|
||||||
} else if (event.keyval == Gdk.Key.Down) {
|
} else if (keyval == Gdk.Key.Down) {
|
||||||
index += 1;
|
index += 1;
|
||||||
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
|
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
|
||||||
if (parent != null &&
|
if (parent != null &&
|
||||||
index < parent.get_children().length() &&
|
//XXX GTK4 - I *think* we don't need this anymore
|
||||||
|
// index < parent.get_children().length() &&
|
||||||
!(parent.get_row_at_index(index) is AddRow)) {
|
!(parent.get_row_at_index(index) is AddRow)) {
|
||||||
move_to(index);
|
move_to(index);
|
||||||
ret = Gdk.EVENT_STOP;
|
ret = Gdk.EVENT_STOP;
|
||||||
|
|
@ -96,127 +78,113 @@ internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret != Gdk.EVENT_STOP) {
|
|
||||||
ret = base.key_press_event(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adds a drag handle to the row and enables drag signals. */
|
/** Adds a drag handle to the row and enables drag signals. */
|
||||||
protected void enable_drag() {
|
protected void enable_drag() {
|
||||||
Gtk.drag_source_set(
|
//XXX GTK4 - is this activated on click?
|
||||||
this.drag_handle,
|
Gtk.DragSource drag_source = new Gtk.DragSource();
|
||||||
Gdk.ModifierType.BUTTON1_MASK,
|
drag_source.drag_begin.connect(on_drag_source_begin);
|
||||||
DRAG_ENTRIES,
|
drag_source.drag_end.connect(on_drag_source_end);
|
||||||
Gdk.DragAction.MOVE
|
drag_source.prepare.connect(on_drag_source_prepare);
|
||||||
);
|
this.drag_handle.add_controller(drag_source);
|
||||||
|
|
||||||
Gtk.drag_dest_set(
|
Gtk.DropTarget drop_target = new Gtk.DropTarget(typeof(int), Gdk.DragAction.MOVE);
|
||||||
this,
|
drop_target.enter.connect(on_drop_target_enter);
|
||||||
// No highlight, we'll take care of that ourselves so we
|
drop_target.leave.connect(on_drop_target_leave);
|
||||||
// can avoid highlighting the row that was picked up
|
drop_target.drop.connect(on_drop_target_drop);
|
||||||
Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP,
|
this.drag_handle.add_controller(drop_target);
|
||||||
DRAG_ENTRIES,
|
|
||||||
Gdk.DragAction.MOVE
|
|
||||||
);
|
|
||||||
|
|
||||||
this.drag_handle.drag_begin.connect(on_drag_begin);
|
//XXX GTK4 - Disable highlight by default, so we can avoid highlighting the row that was picked up
|
||||||
this.drag_handle.drag_end.connect(on_drag_end);
|
this.drag_handle.add_css_class("geary-drag-handle");
|
||||||
this.drag_handle.drag_data_get.connect(on_drag_data_get);
|
this.drag_handle.visible = true;
|
||||||
|
|
||||||
this.drag_motion.connect(on_drag_motion);
|
add_css_class("geary-draggable");
|
||||||
this.drag_leave.connect(on_drag_leave);
|
|
||||||
this.drag_data_received.connect(on_drag_data_received);
|
|
||||||
|
|
||||||
this.drag_handle.get_style_context().add_class("geary-drag-handle");
|
|
||||||
this.drag_handle.show();
|
|
||||||
|
|
||||||
get_style_context().add_class("geary-draggable");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void on_drag_begin(Gdk.DragContext context) {
|
private void on_drag_source_begin(Gtk.DragSource drag_source, Gdk.Drag drag) {
|
||||||
// Draw a nice drag icon
|
// Draw a nice drag icon
|
||||||
Gtk.Allocation alloc = Gtk.Allocation();
|
Gtk.Allocation alloc = Gtk.Allocation();
|
||||||
this.get_allocation(out alloc);
|
this.get_allocation(out alloc);
|
||||||
|
|
||||||
Cairo.ImageSurface surface = new Cairo.ImageSurface(
|
//XXX GTK4 lol, let's just make this a proper drag icon at some point
|
||||||
Cairo.Format.ARGB32, alloc.width, alloc.height
|
// Cairo.ImageSurface surface = new Cairo.ImageSurface(
|
||||||
);
|
// Cairo.Format.ARGB32, alloc.width, alloc.height
|
||||||
Cairo.Context paint = new Cairo.Context(surface);
|
// );
|
||||||
|
// Cairo.Context paint = new Cairo.Context(surface);
|
||||||
|
|
||||||
|
|
||||||
Gtk.StyleContext style = get_style_context();
|
// add_css_class("geary-drag-icon");
|
||||||
style.add_class("geary-drag-icon");
|
// draw(paint);
|
||||||
draw(paint);
|
// remove_css_class("geary-drag-icon");
|
||||||
style.remove_class("geary-drag-icon");
|
|
||||||
|
|
||||||
int x, y;
|
// drag_source.set_icon(surface, 0, 0);
|
||||||
this.drag_handle.translate_coordinates(this, 0, 0, out x, out y);
|
|
||||||
surface.set_device_offset(-x, -y);
|
|
||||||
Gtk.drag_set_icon_surface(context, surface);
|
|
||||||
|
|
||||||
// Set a visual hint that the row is being dragged
|
// Set a visual hint that the row is being dragged
|
||||||
style.add_class("geary-drag-source");
|
add_css_class("geary-drag-source");
|
||||||
this.drag_picked_up = true;
|
this.drag_picked_up = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_drag_end(Gdk.DragContext context) {
|
private void on_drag_source_end(Gtk.DragSource drag_source,
|
||||||
get_style_context().remove_class("geary-drag-source");
|
Gdk.Drag drag,
|
||||||
|
bool delete_data) {
|
||||||
|
remove_css_class("geary-drag-source");
|
||||||
this.drag_picked_up = false;
|
this.drag_picked_up = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_drag_motion(Gdk.DragContext context,
|
private Gdk.DragAction on_drop_target_enter(Gtk.DropTarget drop_target,
|
||||||
int x, int y,
|
double x,
|
||||||
uint time_) {
|
double y) {
|
||||||
if (!this.drag_entered) {
|
// Don't highlight the same row that was picked up
|
||||||
this.drag_entered = true;
|
if (!this.drag_picked_up) {
|
||||||
|
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
|
||||||
// Don't highlight the same row that was picked up
|
if (parent != null) {
|
||||||
if (!this.drag_picked_up) {
|
parent.drag_highlight_row(this);
|
||||||
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
|
|
||||||
if (parent != null) {
|
|
||||||
parent.drag_highlight_row(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return Gdk.DragAction.MOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_drag_leave(Gdk.DragContext context,
|
private void on_drop_target_leave(Gtk.DropTarget drop_target) {
|
||||||
uint time_) {
|
|
||||||
if (!this.drag_picked_up) {
|
if (!this.drag_picked_up) {
|
||||||
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
|
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent.drag_unhighlight_row();
|
parent.drag_unhighlight_row();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.drag_entered = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_drag_data_get(Gdk.DragContext context,
|
private Gdk.ContentProvider on_drag_source_prepare(Gtk.DragSource drag_source,
|
||||||
Gtk.SelectionData selection_data,
|
double x,
|
||||||
uint info, uint time_) {
|
double y) {
|
||||||
selection_data.set(
|
GLib.Value val = GLib.Value(typeof(int));
|
||||||
Gdk.Atom.intern_static_string(DND_ATOM), 8,
|
val.set_int(get_index());
|
||||||
get_index().to_string().data
|
return new Gdk.ContentProvider.for_value(val);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_drag_data_received(Gdk.DragContext context,
|
private bool on_drop_target_drop(Gtk.DropTarget drop_target,
|
||||||
int x, int y,
|
GLib.Value val,
|
||||||
Gtk.SelectionData selection_data,
|
double x,
|
||||||
uint info, uint time_) {
|
double y) {
|
||||||
int drag_index = int.parse((string) selection_data.get_data());
|
if (!val.holds(typeof(int))) {
|
||||||
|
warning("Can't deal with non-uint row value");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int drag_index = val.get_int();
|
||||||
Gtk.ListBox? parent = this.get_parent() as Gtk.ListBox;
|
Gtk.ListBox? parent = this.get_parent() as Gtk.ListBox;
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
EditorRow? drag_row = parent.get_row_at_index(drag_index) as EditorRow;
|
EditorRow? drag_row = parent.get_row_at_index(drag_index) as EditorRow;
|
||||||
if (drag_row != null && drag_row != this) {
|
if (drag_row != null && drag_row != this) {
|
||||||
drag_row.dropped(this);
|
drag_row.dropped(this);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -233,11 +201,10 @@ internal class Accounts.LabelledEditorRow<PaneType,V> : EditorRow<PaneType> {
|
||||||
this.label.halign = Gtk.Align.START;
|
this.label.halign = Gtk.Align.START;
|
||||||
this.label.valign = Gtk.Align.CENTER;
|
this.label.valign = Gtk.Align.CENTER;
|
||||||
this.label.hexpand = true;
|
this.label.hexpand = true;
|
||||||
this.label.set_text(label);
|
this.label.label = label;
|
||||||
this.label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
|
this.label.wrap_mode = Pango.WrapMode.WORD_CHAR;
|
||||||
this.label.set_line_wrap(true);
|
this.label.wrap = true;
|
||||||
this.label.show();
|
this.layout.append(this.label);
|
||||||
this.layout.add(this.label);
|
|
||||||
|
|
||||||
bool expand_label = true;
|
bool expand_label = true;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
|
@ -250,14 +217,13 @@ internal class Accounts.LabelledEditorRow<PaneType,V> : EditorRow<PaneType> {
|
||||||
}
|
}
|
||||||
Gtk.Label? vlabel = value as Gtk.Label;
|
Gtk.Label? vlabel = value as Gtk.Label;
|
||||||
if (vlabel != null) {
|
if (vlabel != null) {
|
||||||
vlabel.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
|
vlabel.wrap_mode = Pango.WrapMode.WORD_CHAR;
|
||||||
vlabel.set_line_wrap(true);
|
vlabel.wrap = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.halign = Gtk.Align.START;
|
widget.halign = Gtk.Align.START;
|
||||||
widget.valign = Gtk.Align.CENTER;
|
widget.valign = Gtk.Align.CENTER;
|
||||||
widget.show();
|
this.layout.append(widget);
|
||||||
this.layout.add(widget);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.label.hexpand = expand_label;
|
this.label.hexpand = expand_label;
|
||||||
|
|
@ -265,9 +231,9 @@ internal class Accounts.LabelledEditorRow<PaneType,V> : EditorRow<PaneType> {
|
||||||
|
|
||||||
public void set_dim_label(bool is_dim) {
|
public void set_dim_label(bool is_dim) {
|
||||||
if (is_dim) {
|
if (is_dim) {
|
||||||
this.label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
this.label.add_css_class("dim-label");
|
||||||
} else {
|
} else {
|
||||||
this.label.get_style_context().remove_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
this.label.remove_css_class("dim-label");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,51 +244,11 @@ internal class Accounts.AddRow<PaneType> : EditorRow<PaneType> {
|
||||||
|
|
||||||
|
|
||||||
public AddRow() {
|
public AddRow() {
|
||||||
get_style_context().add_class("geary-add-row");
|
add_css_class("geary-add-row");
|
||||||
Gtk.Image add_icon = new Gtk.Image.from_icon_name(
|
var add_icon = new Gtk.Image.from_icon_name("list-add-symbolic");
|
||||||
"list-add-symbolic", Gtk.IconSize.BUTTON
|
|
||||||
);
|
|
||||||
add_icon.set_hexpand(true);
|
add_icon.set_hexpand(true);
|
||||||
add_icon.show();
|
|
||||||
|
|
||||||
this.layout.add(add_icon);
|
this.layout.append(add_icon);
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal class Accounts.ServiceProviderRow<PaneType> :
|
|
||||||
LabelledEditorRow<PaneType,Gtk.Label> {
|
|
||||||
|
|
||||||
|
|
||||||
public ServiceProviderRow(Geary.ServiceProvider provider,
|
|
||||||
string other_type_label) {
|
|
||||||
string? label = null;
|
|
||||||
switch (provider) {
|
|
||||||
case Geary.ServiceProvider.GMAIL:
|
|
||||||
label = _("Gmail");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Geary.ServiceProvider.OUTLOOK:
|
|
||||||
label = _("Outlook.com");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Geary.ServiceProvider.OTHER:
|
|
||||||
label = other_type_label;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
base(
|
|
||||||
// Translators: Label describes the service provider
|
|
||||||
// hosting the email account, e.g. Gmail, Yahoo, or some
|
|
||||||
// other generic IMAP service.
|
|
||||||
_("Service provider"),
|
|
||||||
new Gtk.Label(label)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Can't change this, so deactivate and dim out
|
|
||||||
set_activatable(false);
|
|
||||||
this.value.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -390,7 +316,7 @@ private abstract class Accounts.ServiceRow<PaneType,V> : AccountRow<PaneType,V>
|
||||||
Gtk.Widget? widget = value as Gtk.Widget;
|
Gtk.Widget? widget = value as Gtk.Widget;
|
||||||
if (widget != null && !is_editable) {
|
if (widget != null && !is_editable) {
|
||||||
if (widget is Gtk.Label) {
|
if (widget is Gtk.Label) {
|
||||||
widget.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
widget.add_css_class("dim-label");
|
||||||
} else {
|
} else {
|
||||||
widget.set_sensitive(false);
|
widget.set_sensitive(false);
|
||||||
}
|
}
|
||||||
|
|
@ -533,7 +459,6 @@ internal class Accounts.TlsComboBox : Gtk.ComboBox {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal class Accounts.OutgoingAuthComboBox : Gtk.ComboBox {
|
internal class Accounts.OutgoingAuthComboBox : Gtk.ComboBox {
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -626,13 +551,12 @@ internal class Accounts.EditorPopover : Gtk.Popover {
|
||||||
|
|
||||||
|
|
||||||
public EditorPopover() {
|
public EditorPopover() {
|
||||||
get_style_context().add_class("geary-editor");
|
add_css_class("geary-editor");
|
||||||
|
|
||||||
this.layout.orientation = Gtk.Orientation.VERTICAL;
|
this.layout.orientation = Gtk.Orientation.VERTICAL;
|
||||||
this.layout.set_row_spacing(6);
|
this.layout.set_row_spacing(6);
|
||||||
this.layout.set_column_spacing(12);
|
this.layout.set_column_spacing(12);
|
||||||
this.layout.show();
|
this.child = this.layout;
|
||||||
add(this.layout);
|
|
||||||
|
|
||||||
this.closed.connect_after(on_closed);
|
this.closed.connect_after(on_closed);
|
||||||
}
|
}
|
||||||
|
|
@ -641,39 +565,12 @@ internal class Accounts.EditorPopover : Gtk.Popover {
|
||||||
this.closed.disconnect(on_closed);
|
this.closed.disconnect(on_closed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
public new void popup() {
|
|
||||||
// Work-around GTK+ issue #1138
|
|
||||||
Gtk.Widget target = get_relative_to();
|
|
||||||
|
|
||||||
Gtk.Allocation content_area;
|
|
||||||
target.get_allocation(out content_area);
|
|
||||||
|
|
||||||
Gtk.StyleContext style = target.get_style_context();
|
|
||||||
Gtk.StateFlags flags = style.get_state();
|
|
||||||
Gtk.Border margin = style.get_margin(flags);
|
|
||||||
|
|
||||||
content_area.x = margin.left;
|
|
||||||
content_area.y = margin.bottom;
|
|
||||||
content_area.width -= (content_area.x + margin.right);
|
|
||||||
content_area.height -= (content_area.y + margin.top);
|
|
||||||
|
|
||||||
set_pointing_to(content_area);
|
|
||||||
|
|
||||||
base.popup();
|
|
||||||
|
|
||||||
if (this.popup_focus != null) {
|
|
||||||
this.popup_focus.grab_focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add_labelled_row(string label, Gtk.Widget value) {
|
public void add_labelled_row(string label, Gtk.Widget value) {
|
||||||
Gtk.Label label_widget = new Gtk.Label(label);
|
Gtk.Label label_widget = new Gtk.Label(label);
|
||||||
label_widget.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
label_widget.add_css_class("dim-label");
|
||||||
label_widget.halign = Gtk.Align.END;
|
label_widget.halign = Gtk.Align.END;
|
||||||
label_widget.show();
|
|
||||||
|
|
||||||
this.layout.add(label_widget);
|
this.layout.attach_next_to(label_widget, null, Gtk.PositionType.BOTTOM);
|
||||||
this.layout.attach_next_to(value, label_widget, Gtk.PositionType.RIGHT);
|
this.layout.attach_next_to(value, label_widget, Gtk.PositionType.RIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,11 @@
|
||||||
* An account editor pane for editing server details for an account.
|
* An account editor pane for editing server details for an account.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_servers_pane.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_servers_pane.ui")]
|
||||||
internal class Accounts.EditorServersPane :
|
internal class Accounts.EditorServersPane : EditorPane, AccountPane, CommandPane {
|
||||||
Gtk.Grid, EditorPane, AccountPane, CommandPane {
|
|
||||||
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal weak Accounts.Editor editor { get; set; }
|
internal override weak Accounts.Editor editor { get; set; }
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal Geary.AccountInformation account { get ; protected set; }
|
internal Geary.AccountInformation account { get ; protected set; }
|
||||||
|
|
@ -26,200 +25,67 @@ internal class Accounts.EditorServersPane :
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal Gtk.Widget initial_widget {
|
internal override bool is_operation_running {
|
||||||
get { return this.details_list; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
|
||||||
internal bool is_operation_running {
|
|
||||||
get { return !this.sensitive; }
|
get { return !this.sensitive; }
|
||||||
protected set { update_operation_ui(value); }
|
protected set { update_operation_ui(value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
internal GLib.Cancellable? op_cancellable {
|
internal override Cancellable? op_cancellable {
|
||||||
get; protected set; default = new GLib.Cancellable();
|
get; protected set; default = new GLib.Cancellable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Geary.Engine engine;
|
private Geary.Engine engine;
|
||||||
|
|
||||||
// These are copies of the originals that can be updated before
|
public Components.ValidatorGroup validators { get; construct set; }
|
||||||
// validating on apply, without breaking anything.
|
|
||||||
private Geary.ServiceInformation incoming_mutable;
|
|
||||||
private Geary.ServiceInformation outgoing_mutable;
|
|
||||||
|
|
||||||
private Gee.List<Components.Validator> validators =
|
|
||||||
new Gee.LinkedList<Components.Validator>();
|
|
||||||
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.HeaderBar header;
|
[GtkChild] private unowned Adw.ActionRow account_provider_row;
|
||||||
|
[GtkChild] private unowned Adw.ActionRow service_provider_row;
|
||||||
|
[GtkChild] private unowned Adw.SwitchRow save_drafts_row;
|
||||||
|
[GtkChild] private unowned Adw.SwitchRow save_sent_row;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid pane_content;
|
[GtkChild] private unowned ServiceInformationWidget receiving_service_widget;
|
||||||
|
[GtkChild] private unowned ServiceInformationWidget sending_service_widget;
|
||||||
[GtkChild] private unowned Gtk.Adjustment pane_adjustment;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox details_list;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox receiving_list;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox sending_list;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Button apply_button;
|
[GtkChild] private unowned Gtk.Button apply_button;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Spinner apply_spinner;
|
[GtkChild] private unowned Gtk.Spinner apply_spinner;
|
||||||
|
|
||||||
private SaveDraftsRow save_drafts;
|
|
||||||
private SaveSentRow save_sent;
|
|
||||||
|
|
||||||
private ServiceLoginRow incoming_login;
|
static construct {
|
||||||
private ServicePasswordRow incoming_password;
|
typeof(ServiceInformationWidget).ensure();
|
||||||
|
|
||||||
private ServiceOutgoingAuthRow outgoing_auth;
|
install_action("apply", null, (Gtk.WidgetActionActivateFunc) action_apply);
|
||||||
private ServiceLoginRow outgoing_login;
|
}
|
||||||
private ServicePasswordRow outgoing_password;
|
|
||||||
|
|
||||||
|
|
||||||
public EditorServersPane(Editor editor, Geary.AccountInformation account) {
|
public EditorServersPane(Editor editor, Geary.AccountInformation account) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.engine = editor.application.engine;
|
this.engine = editor.application.engine;
|
||||||
this.incoming_mutable = new Geary.ServiceInformation.copy(account.incoming);
|
|
||||||
this.outgoing_mutable = new Geary.ServiceInformation.copy(account.outgoing);
|
|
||||||
|
|
||||||
this.pane_content.set_focus_vadjustment(this.pane_adjustment);
|
|
||||||
|
|
||||||
// Details
|
// Details
|
||||||
|
fill_in_account_provider(editor.accounts);
|
||||||
|
fill_in_service_provider();
|
||||||
|
|
||||||
this.details_list.set_header_func(Editor.seperator_headers);
|
this.receiving_service_widget.service = account.incoming;
|
||||||
// Only add an account provider if it is esoteric enough.
|
this.sending_service_widget.service = account.outgoing;
|
||||||
if (this.account.mediator is GoaMediator) {
|
|
||||||
this.details_list.add(
|
|
||||||
new AccountProviderRow(editor.accounts, this.account)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ServiceProviderRow<EditorServersPane> service_provider =
|
|
||||||
new ServiceProviderRow<EditorServersPane>(
|
|
||||||
this.account.service_provider,
|
|
||||||
this.account.service_label
|
|
||||||
);
|
|
||||||
service_provider.set_dim_label(true);
|
|
||||||
service_provider.activatable = false;
|
|
||||||
add_row(this.details_list, service_provider);
|
|
||||||
|
|
||||||
this.save_drafts = new SaveDraftsRow(
|
bool services_editable = !(account.mediator is GoaMediator);
|
||||||
this.account, this.commands, this.op_cancellable
|
this.receiving_service_widget.set_editable(services_editable);
|
||||||
);
|
this.sending_service_widget.set_editable(services_editable);
|
||||||
add_row(this.details_list, this.save_drafts);
|
|
||||||
|
|
||||||
this.save_sent = new SaveSentRow(
|
//XXX GTK4 Make sure we update save_drafts and save_sent
|
||||||
this.account, this.commands, this.op_cancellable
|
|
||||||
);
|
|
||||||
switch (account.service_provider) {
|
|
||||||
case OTHER:
|
|
||||||
add_row(this.details_list, this.save_sent);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// XXX GMail and Outlook auto-save sent mail so don't
|
|
||||||
// include save sent option, but we shouldn't be
|
|
||||||
// hard-coding visible rows like this
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receiving
|
// XXX GMail and Outlook auto-save sent mail so don't include save sent
|
||||||
|
// option, but we shouldn't be hard-coding visible rows like this
|
||||||
this.receiving_list.set_header_func(Editor.seperator_headers);
|
this.save_sent_row.visible = (account.service_provider == OTHER);
|
||||||
add_row(
|
|
||||||
this.receiving_list,
|
|
||||||
new ServiceHostRow(
|
|
||||||
account,
|
|
||||||
this.incoming_mutable,
|
|
||||||
this.commands,
|
|
||||||
this.op_cancellable
|
|
||||||
)
|
|
||||||
);
|
|
||||||
add_row(
|
|
||||||
this.receiving_list,
|
|
||||||
new ServiceSecurityRow(
|
|
||||||
account,
|
|
||||||
this.incoming_mutable,
|
|
||||||
this.commands,
|
|
||||||
this.op_cancellable
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.incoming_password = new ServicePasswordRow(
|
|
||||||
account,
|
|
||||||
this.incoming_mutable,
|
|
||||||
this.commands,
|
|
||||||
this.op_cancellable
|
|
||||||
);
|
|
||||||
|
|
||||||
this.incoming_login = new ServiceLoginRow(
|
|
||||||
account,
|
|
||||||
this.incoming_mutable,
|
|
||||||
this.commands,
|
|
||||||
this.op_cancellable,
|
|
||||||
this.incoming_password
|
|
||||||
);
|
|
||||||
|
|
||||||
add_row(this.receiving_list, this.incoming_login);
|
|
||||||
add_row(this.receiving_list, this.incoming_password);
|
|
||||||
|
|
||||||
// Sending
|
|
||||||
|
|
||||||
this.sending_list.set_header_func(Editor.seperator_headers);
|
|
||||||
add_row(
|
|
||||||
this.sending_list,
|
|
||||||
new ServiceHostRow(
|
|
||||||
account,
|
|
||||||
this.outgoing_mutable,
|
|
||||||
this.commands,
|
|
||||||
this.op_cancellable
|
|
||||||
)
|
|
||||||
);
|
|
||||||
add_row(
|
|
||||||
this.sending_list,
|
|
||||||
new ServiceSecurityRow(
|
|
||||||
account,
|
|
||||||
this.outgoing_mutable,
|
|
||||||
this.commands,
|
|
||||||
this.op_cancellable
|
|
||||||
)
|
|
||||||
);
|
|
||||||
this.outgoing_auth = new ServiceOutgoingAuthRow(
|
|
||||||
account,
|
|
||||||
this.outgoing_mutable,
|
|
||||||
this.incoming_mutable,
|
|
||||||
this.commands,
|
|
||||||
this.op_cancellable
|
|
||||||
);
|
|
||||||
this.outgoing_auth.value.changed.connect(on_outgoing_auth_changed);
|
|
||||||
add_row(this.sending_list, this.outgoing_auth);
|
|
||||||
|
|
||||||
this.outgoing_password = new ServicePasswordRow(
|
|
||||||
account,
|
|
||||||
this.outgoing_mutable,
|
|
||||||
this.commands,
|
|
||||||
this.op_cancellable
|
|
||||||
);
|
|
||||||
|
|
||||||
this.outgoing_login = new ServiceLoginRow(
|
|
||||||
account,
|
|
||||||
this.outgoing_mutable,
|
|
||||||
this.commands,
|
|
||||||
this.op_cancellable,
|
|
||||||
this.outgoing_password
|
|
||||||
);
|
|
||||||
|
|
||||||
add_row(this.sending_list, this.outgoing_login);
|
|
||||||
add_row(this.sending_list, this.outgoing_password);
|
|
||||||
|
|
||||||
// Misc plumbing
|
// Misc plumbing
|
||||||
|
|
||||||
connect_account_signals();
|
connect_account_signals();
|
||||||
connect_command_signals();
|
connect_command_signals();
|
||||||
|
|
||||||
update_outgoing_auth();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~EditorServersPane() {
|
~EditorServersPane() {
|
||||||
|
|
@ -227,9 +93,48 @@ internal class Accounts.EditorServersPane :
|
||||||
disconnect_command_signals();
|
disconnect_command_signals();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
private void fill_in_account_provider(Manager accounts) {
|
||||||
internal Gtk.HeaderBar get_header() {
|
if (this.account.mediator is GoaMediator) {
|
||||||
return this.header;
|
this.account_provider_row.subtitle = _("GNOME Online Accounts");
|
||||||
|
|
||||||
|
var button = new Gtk.Button.from_icon_name("external-link-symbolic");
|
||||||
|
button.valign = Gtk.Align.CENTER;
|
||||||
|
button.clicked.connect((button) => {
|
||||||
|
if (accounts.is_goa_account(this.account)) {
|
||||||
|
accounts.show_goa_account.begin(
|
||||||
|
account, this.op_cancellable,
|
||||||
|
(obj, res) => {
|
||||||
|
try {
|
||||||
|
accounts.show_goa_account.end(res);
|
||||||
|
} catch (GLib.Error err) {
|
||||||
|
// XXX display an error to the user
|
||||||
|
debug(
|
||||||
|
"Failed to show GOA account \"%s\": %s",
|
||||||
|
account.id,
|
||||||
|
err.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.account_provider_row.add_suffix(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fill_in_service_provider() {
|
||||||
|
switch (this.account.service_provider) {
|
||||||
|
case Geary.ServiceProvider.GMAIL:
|
||||||
|
this.service_provider_row.subtitle = _("Gmail");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Geary.ServiceProvider.OUTLOOK:
|
||||||
|
this.service_provider_row.subtitle = _("Outlook.com");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Geary.ServiceProvider.OTHER:
|
||||||
|
this.service_provider_row.subtitle = this.account.service_label;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
|
|
@ -238,11 +143,8 @@ internal class Accounts.EditorServersPane :
|
||||||
this.apply_button.set_sensitive(this.commands.can_undo);
|
this.apply_button.set_sensitive(this.commands.can_undo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool is_valid() {
|
|
||||||
return Geary.traverse(this.validators).all((v) => v.is_valid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void save(GLib.Cancellable? cancellable) {
|
private async void save(GLib.Cancellable? cancellable) {
|
||||||
|
#if 0
|
||||||
this.is_operation_running = true;
|
this.is_operation_running = true;
|
||||||
|
|
||||||
// Only need to validate if a generic, local account since
|
// Only need to validate if a generic, local account since
|
||||||
|
|
@ -278,18 +180,19 @@ internal class Accounts.EditorServersPane :
|
||||||
this.account.changed();
|
this.account.changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.pop();
|
this.editor.pop_pane();
|
||||||
} else {
|
} else {
|
||||||
// Re-enable apply so that the same config can be re-tried
|
// Re-enable apply so that the same config can be re-tried
|
||||||
// in the face of transient errors, without having to
|
// in the face of transient errors, without having to
|
||||||
// change something to re-enable it
|
// change something to re-enable it
|
||||||
this.apply_button.set_sensitive(true);
|
this.apply_button.sensitive = true;
|
||||||
|
|
||||||
// Undo these manually since it would have been updated
|
// Undo these manually since it would have been updated
|
||||||
// already by the command
|
// already by the command
|
||||||
this.account.save_drafts = this.save_drafts.initial_value;
|
this.account.save_drafts = this.save_drafts.initial_value;
|
||||||
this.account.save_sent = this.save_sent.initial_value;
|
this.account.save_sent = this.save_sent.initial_value;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private async bool validate(GLib.Cancellable? cancellable) {
|
private async bool validate(GLib.Cancellable? cancellable) {
|
||||||
|
|
@ -301,9 +204,12 @@ internal class Accounts.EditorServersPane :
|
||||||
|
|
||||||
string? message = null;
|
string? message = null;
|
||||||
bool imap_valid = false;
|
bool imap_valid = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
yield this.engine.validate_imap(
|
yield this.engine.validate_imap(
|
||||||
local_account, this.incoming_mutable, cancellable
|
local_account,
|
||||||
|
this.receiving_service_widget.service_mutable,
|
||||||
|
cancellable
|
||||||
);
|
);
|
||||||
imap_valid = true;
|
imap_valid = true;
|
||||||
} catch (Geary.ImapError.UNAUTHENTICATED err) {
|
} catch (Geary.ImapError.UNAUTHENTICATED err) {
|
||||||
|
|
@ -331,8 +237,8 @@ internal class Accounts.EditorServersPane :
|
||||||
try {
|
try {
|
||||||
yield this.engine.validate_smtp(
|
yield this.engine.validate_smtp(
|
||||||
local_account,
|
local_account,
|
||||||
this.outgoing_mutable,
|
this.sending_service_widget.service_mutable,
|
||||||
this.incoming_mutable.credentials,
|
this.receiving_service_widget.service_mutable.credentials,
|
||||||
cancellable
|
cancellable
|
||||||
);
|
);
|
||||||
smtp_valid = true;
|
smtp_valid = true;
|
||||||
|
|
@ -341,7 +247,8 @@ internal class Accounts.EditorServersPane :
|
||||||
// There was an SMTP auth error, but IMAP already
|
// There was an SMTP auth error, but IMAP already
|
||||||
// succeeded, so the user probably needs to
|
// succeeded, so the user probably needs to
|
||||||
// specify custom creds here
|
// specify custom creds here
|
||||||
this.outgoing_auth.value.source = Geary.Credentials.Requirement.CUSTOM;
|
//XXX GTK4
|
||||||
|
// this.outgoing_auth.value.source = Geary.Credentials.Requirement.CUSTOM;
|
||||||
// Translators: In-app notification label
|
// Translators: In-app notification label
|
||||||
message = _("Check your sending login and password");
|
message = _("Check your sending login and password");
|
||||||
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
|
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
|
||||||
|
|
@ -366,8 +273,8 @@ internal class Accounts.EditorServersPane :
|
||||||
debug("Validation complete, is valid: %s", is_valid.to_string());
|
debug("Validation complete, is valid: %s", is_valid.to_string());
|
||||||
|
|
||||||
if (!is_valid && message != null) {
|
if (!is_valid && message != null) {
|
||||||
this.editor.add_notification(
|
this.editor.add_toast(
|
||||||
new Components.InAppNotification(
|
new Adw.Toast(
|
||||||
// Translators: In-app notification label, the
|
// Translators: In-app notification label, the
|
||||||
// string substitution is a more detailed reason.
|
// string substitution is a more detailed reason.
|
||||||
_("Account not updated: %s").printf(message)
|
_("Account not updated: %s").printf(message)
|
||||||
|
|
@ -381,6 +288,8 @@ internal class Accounts.EditorServersPane :
|
||||||
private async bool update_service(Geary.ServiceInformation existing,
|
private async bool update_service(Geary.ServiceInformation existing,
|
||||||
Geary.ServiceInformation copy,
|
Geary.ServiceInformation copy,
|
||||||
GLib.Cancellable cancellable) {
|
GLib.Cancellable cancellable) {
|
||||||
|
return true;
|
||||||
|
#if 0
|
||||||
bool has_changed = !existing.equal_to(copy);
|
bool has_changed = !existing.equal_to(copy);
|
||||||
if (has_changed) {
|
if (has_changed) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -410,40 +319,28 @@ internal class Accounts.EditorServersPane :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return has_changed;
|
return has_changed;
|
||||||
}
|
#endif
|
||||||
|
|
||||||
private void add_row(Gtk.ListBox list, EditorRow<EditorServersPane> row) {
|
|
||||||
list.add(row);
|
|
||||||
ValidatingRow? validating = row as ValidatingRow;
|
|
||||||
if (validating != null) {
|
|
||||||
validating.changed.connect(on_validator_changed);
|
|
||||||
validating.validator.activated.connect_after(on_validator_activated);
|
|
||||||
this.validators.add(validating.validator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_outgoing_auth() {
|
|
||||||
this.outgoing_login.set_visible(
|
|
||||||
this.outgoing_auth.value.source == CUSTOM
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_operation_ui(bool is_running) {
|
private void update_operation_ui(bool is_running) {
|
||||||
this.apply_spinner.visible = is_running;
|
this.apply_spinner.visible = is_running;
|
||||||
this.apply_spinner.active = is_running;
|
|
||||||
this.apply_button.sensitive = !is_running;
|
this.apply_button.sensitive = !is_running;
|
||||||
this.sensitive = !is_running;
|
this.sensitive = !is_running;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_validator_changed() {
|
// [GtkCallback]
|
||||||
this.apply_button.set_sensitive(is_valid());
|
// private void on_validators_changed(Components.ValidatorGroup validators,
|
||||||
}
|
// Components.Validator validator) {
|
||||||
|
// action_set_enabled("apply", validators.is_valid());
|
||||||
|
// }
|
||||||
|
|
||||||
private void on_validator_activated() {
|
// [GtkCallback]
|
||||||
if (is_valid()) {
|
// private void on_validators_activated(Components.ValidatorGroup validators,
|
||||||
this.apply_button.clicked();
|
// Components.Validator validator) {
|
||||||
}
|
// if (validators.is_valid()) {
|
||||||
}
|
// activate_action("apply", null);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
private void on_untrusted_host(Geary.AccountInformation account,
|
private void on_untrusted_host(Geary.AccountInformation account,
|
||||||
Geary.ServiceInformation service,
|
Geary.ServiceInformation service,
|
||||||
|
|
@ -465,132 +362,39 @@ internal class Accounts.EditorServersPane :
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX GTK4 we don't have a cancel button anymore
|
||||||
|
#if 0
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_cancel_button_clicked() {
|
private void on_cancel_button_clicked() {
|
||||||
if (this.is_operation_running) {
|
if (this.is_operation_running) {
|
||||||
cancel_operation();
|
cancel_operation();
|
||||||
} else {
|
} else {
|
||||||
this.editor.pop();
|
this.editor.pop_pane();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
[GtkCallback]
|
private void action_apply(string action_name, Variant? param) {
|
||||||
private void on_apply_button_clicked() {
|
|
||||||
this.save.begin(this.op_cancellable);
|
this.save.begin(this.op_cancellable);
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
}
|
||||||
private bool on_list_keynav_failed(Gtk.Widget widget,
|
|
||||||
Gtk.DirectionType direction) {
|
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
|
||||||
Gtk.Container? next = null;
|
|
||||||
if (direction == Gtk.DirectionType.DOWN) {
|
|
||||||
if (widget == this.details_list) {
|
|
||||||
next = this.receiving_list;
|
|
||||||
} else if (widget == this.receiving_list) {
|
|
||||||
next = this.sending_list;
|
|
||||||
}
|
|
||||||
} else if (direction == Gtk.DirectionType.UP) {
|
|
||||||
if (widget == this.sending_list) {
|
|
||||||
next = this.receiving_list;
|
|
||||||
} else if (widget == this.receiving_list) {
|
|
||||||
next = this.details_list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next != null) {
|
|
||||||
next.child_focus(direction);
|
|
||||||
ret = Gdk.EVENT_STOP;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_outgoing_auth_changed() {
|
|
||||||
update_outgoing_auth();
|
|
||||||
}
|
|
||||||
|
|
||||||
[GtkCallback]
|
|
||||||
private void on_activate(Gtk.ListBoxRow row) {
|
|
||||||
Accounts.EditorRow<EditorServersPane> server_row =
|
|
||||||
row as Accounts.EditorRow<EditorServersPane>;
|
|
||||||
if (server_row != null) {
|
|
||||||
server_row.activated(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private struct Accounts.InitialConfiguration {
|
||||||
|
bool save_drafts;
|
||||||
|
bool save_sent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class Accounts.AccountProviderRow :
|
#if 0
|
||||||
AccountRow<EditorServersPane,Gtk.Label> {
|
private class zccounts.SaveDraftsRow : Adw.SwitchRow {
|
||||||
|
|
||||||
private Manager accounts;
|
|
||||||
|
|
||||||
public AccountProviderRow(Manager accounts,
|
|
||||||
Geary.AccountInformation account) {
|
|
||||||
base(
|
|
||||||
account,
|
|
||||||
// Translators: This label describes the program that
|
|
||||||
// created the account, e.g. an SSO service like GOA, or
|
|
||||||
// locally by Geary.
|
|
||||||
_("Account source"),
|
|
||||||
new Gtk.Label("")
|
|
||||||
);
|
|
||||||
|
|
||||||
this.accounts = accounts;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void update() {
|
|
||||||
string? source = null;
|
|
||||||
bool enabled = false;
|
|
||||||
if (this.account.mediator is GoaMediator) {
|
|
||||||
source = _("GNOME Online Accounts");
|
|
||||||
enabled = true;
|
|
||||||
} else {
|
|
||||||
source = _("Geary");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value.set_text(source);
|
|
||||||
this.set_activatable(enabled);
|
|
||||||
Gtk.StyleContext style = this.value.get_style_context();
|
|
||||||
if (enabled) {
|
|
||||||
style.remove_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
|
||||||
} else {
|
|
||||||
style.add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void activated(EditorServersPane pane) {
|
|
||||||
if (this.accounts.is_goa_account(this.account)) {
|
|
||||||
this.accounts.show_goa_account.begin(
|
|
||||||
account, pane.op_cancellable,
|
|
||||||
(obj, res) => {
|
|
||||||
try {
|
|
||||||
this.accounts.show_goa_account.end(res);
|
|
||||||
} catch (GLib.Error err) {
|
|
||||||
// XXX display an error to the user
|
|
||||||
debug(
|
|
||||||
"Failed to show GOA account \"%s\": %s",
|
|
||||||
account.id,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class Accounts.SaveDraftsRow :
|
|
||||||
AccountRow<EditorServersPane,Gtk.Switch> {
|
|
||||||
|
|
||||||
|
public Geary.AccountInformation account { get; construct set; }
|
||||||
|
|
||||||
public bool value_changed {
|
public bool value_changed {
|
||||||
get { return this.initial_value != this.value.state; }
|
get { return this.initial_value != this.value.state; }
|
||||||
}
|
}
|
||||||
public bool initial_value { get; private set; }
|
public bool initial_value { get; construct set; }
|
||||||
|
|
||||||
private Application.CommandStack commands;
|
private Application.CommandStack commands;
|
||||||
private GLib.Cancellable? cancellable;
|
private GLib.Cancellable? cancellable;
|
||||||
|
|
@ -599,25 +403,22 @@ private class Accounts.SaveDraftsRow :
|
||||||
public SaveDraftsRow(Geary.AccountInformation account,
|
public SaveDraftsRow(Geary.AccountInformation account,
|
||||||
Application.CommandStack commands,
|
Application.CommandStack commands,
|
||||||
GLib.Cancellable? cancellable) {
|
GLib.Cancellable? cancellable) {
|
||||||
Gtk.Switch value = new Gtk.Switch();
|
Object(
|
||||||
base(
|
account: account,
|
||||||
account,
|
initial_value: account.save_drafts
|
||||||
// Translators: This label describes an account
|
|
||||||
// preference.
|
|
||||||
_("Save draft email on server"),
|
|
||||||
value
|
|
||||||
);
|
);
|
||||||
update();
|
|
||||||
this.commands = commands;
|
this.commands = commands;
|
||||||
this.cancellable = cancellable;
|
this.cancellable = cancellable;
|
||||||
this.activatable = false;
|
this.account.notify["save-drafts"].connect(update);
|
||||||
this.initial_value = this.account.save_drafts;
|
this.notify["active"].connect(on_activate);
|
||||||
this.account.notify["save-drafts"].connect(on_account_changed);
|
update();
|
||||||
this.value.notify["active"].connect(on_activate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void update() {
|
private void update() {
|
||||||
this.value.state = this.account.save_drafts;
|
//XXX GTK4 I think we need to guard this with an if to not activate the
|
||||||
|
// switch again
|
||||||
|
this.active = this.account.save_drafts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_activate() {
|
private void on_activate() {
|
||||||
|
|
@ -630,11 +431,6 @@ private class Accounts.SaveDraftsRow :
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_account_changed() {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -697,10 +493,6 @@ private class Accounts.ServiceHostRow :
|
||||||
ServiceRow<EditorServersPane,Gtk.Entry>, ValidatingRow<EditorServersPane> {
|
ServiceRow<EditorServersPane,Gtk.Entry>, ValidatingRow<EditorServersPane> {
|
||||||
|
|
||||||
|
|
||||||
public Components.Validator validator {
|
|
||||||
get; protected set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool has_changed {
|
public bool has_changed {
|
||||||
get {
|
get {
|
||||||
return this.value.text.strip() != get_entry_text();
|
return this.value.text.strip() != get_entry_text();
|
||||||
|
|
@ -850,11 +642,6 @@ private class Accounts.ServiceSecurityRow :
|
||||||
private class Accounts.ServiceLoginRow :
|
private class Accounts.ServiceLoginRow :
|
||||||
ServiceRow<EditorServersPane,Gtk.Entry>, ValidatingRow<EditorServersPane> {
|
ServiceRow<EditorServersPane,Gtk.Entry>, ValidatingRow<EditorServersPane> {
|
||||||
|
|
||||||
|
|
||||||
public Components.Validator validator {
|
|
||||||
get; protected set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool has_changed {
|
public bool has_changed {
|
||||||
get {
|
get {
|
||||||
return this.value.text.strip() != get_entry_text();
|
return this.value.text.strip() != get_entry_text();
|
||||||
|
|
@ -936,10 +723,9 @@ private class Accounts.ServiceLoginRow :
|
||||||
string? label = null;
|
string? label = null;
|
||||||
if (this.service.credentials != null) {
|
if (this.service.credentials != null) {
|
||||||
string method = "%s";
|
string method = "%s";
|
||||||
Gtk.StyleContext value_style = this.value.get_style_context();
|
|
||||||
switch (this.service.credentials.supported_method) {
|
switch (this.service.credentials.supported_method) {
|
||||||
case Geary.Credentials.Method.PASSWORD:
|
case Geary.Credentials.Method.PASSWORD:
|
||||||
value_style.remove_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
this.value.remove_css_class("dim-label");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Geary.Credentials.Method.OAUTH2:
|
case Geary.Credentials.Method.OAUTH2:
|
||||||
|
|
@ -951,7 +737,7 @@ private class Accounts.ServiceLoginRow :
|
||||||
// the service's login name.
|
// the service's login name.
|
||||||
method = _("%s using OAuth2");
|
method = _("%s using OAuth2");
|
||||||
|
|
||||||
value_style.add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
this.value.add_css_class("dim-label");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -975,10 +761,6 @@ private class Accounts.ServicePasswordRow :
|
||||||
ServiceRow<EditorServersPane,Gtk.Entry>, ValidatingRow<EditorServersPane> {
|
ServiceRow<EditorServersPane,Gtk.Entry>, ValidatingRow<EditorServersPane> {
|
||||||
|
|
||||||
|
|
||||||
public Components.Validator validator {
|
|
||||||
get; protected set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool has_changed {
|
public bool has_changed {
|
||||||
get {
|
get {
|
||||||
return this.value.text.strip() != get_entry_text();
|
return this.value.text.strip() != get_entry_text();
|
||||||
|
|
@ -1116,3 +898,4 @@ private class Accounts.ServiceOutgoingAuthRow :
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
* management, account management and other common code for the panes.
|
* management, account management and other common code for the panes.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor.ui")]
|
||||||
public class Accounts.Editor : Gtk.Dialog {
|
public class Accounts.Editor : Adw.Dialog {
|
||||||
|
|
||||||
|
|
||||||
private const ActionEntry[] EDIT_ACTIONS = {
|
private const ActionEntry[] EDIT_ACTIONS = {
|
||||||
|
|
@ -35,10 +35,7 @@ public class Accounts.Editor : Gtk.Dialog {
|
||||||
|
|
||||||
|
|
||||||
/** Returns the editor's associated client application instance. */
|
/** Returns the editor's associated client application instance. */
|
||||||
public new Application.Client application {
|
public Application.Client application { get; private set; }
|
||||||
get { return (Application.Client) base.get_application(); }
|
|
||||||
set { base.set_application(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Manager accounts { get; private set; }
|
internal Manager accounts { get; private set; }
|
||||||
|
|
||||||
|
|
@ -48,49 +45,35 @@ public class Accounts.Editor : Gtk.Dialog {
|
||||||
|
|
||||||
private GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
private GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Overlay notifications_pane;
|
[GtkChild] private unowned Adw.ToastOverlay toast_overlay;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Stack editor_panes;
|
[GtkChild] private unowned Adw.NavigationView view;
|
||||||
|
|
||||||
private EditorListPane editor_list_pane;
|
private EditorListPane editor_list_pane;
|
||||||
|
|
||||||
private Gee.LinkedList<EditorPane> editor_pane_stack =
|
|
||||||
new Gee.LinkedList<EditorPane>();
|
|
||||||
|
|
||||||
|
public Editor(Application.Client application) {
|
||||||
public Editor(Application.Client application, Gtk.Window parent) {
|
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.transient_for = parent;
|
|
||||||
this.icon_name = Config.APP_ID;
|
|
||||||
|
|
||||||
this.accounts = application.controller.account_manager;
|
this.accounts = application.controller.account_manager;
|
||||||
this.certificates = application.controller.certificate_manager;
|
this.certificates = application.controller.certificate_manager;
|
||||||
|
|
||||||
// Can't set this in Glade 3.22.1 :(
|
|
||||||
this.get_content_area().border_width = 0;
|
|
||||||
|
|
||||||
this.accounts = application.controller.account_manager;
|
this.accounts = application.controller.account_manager;
|
||||||
|
|
||||||
this.edit_actions.add_action_entries(EDIT_ACTIONS, this);
|
this.edit_actions.add_action_entries(EDIT_ACTIONS, this);
|
||||||
insert_action_group(Action.Edit.GROUP_NAME, this.edit_actions);
|
insert_action_group(Action.Edit.GROUP_NAME, this.edit_actions);
|
||||||
|
|
||||||
this.editor_list_pane = new EditorListPane(this);
|
this.editor_list_pane = new EditorListPane(this);
|
||||||
push(this.editor_list_pane);
|
push_pane(this.editor_list_pane);
|
||||||
|
|
||||||
update_command_actions();
|
update_command_actions();
|
||||||
|
|
||||||
if (this.accounts.size > 1) {
|
|
||||||
this.default_height = 650;
|
|
||||||
this.default_width = 800;
|
|
||||||
} else {
|
|
||||||
// Welcome dialog
|
|
||||||
this.default_width = 600;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool key_press_event(Gdk.EventKey event) {
|
[GtkCallback]
|
||||||
|
private bool on_key_pressed(uint keyval, uint keycode, Gdk.ModifierType mod_state) {
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
bool ret = Gdk.EVENT_PROPAGATE;
|
||||||
|
|
||||||
|
// XXX GTK4 - we'll need to disable the esc behavio in adwnavigationview and then do it manually here
|
||||||
// Allow the user to use Esc, Back and Alt+arrow keys to
|
// Allow the user to use Esc, Back and Alt+arrow keys to
|
||||||
// navigate between panes. If a pane is executing a long
|
// navigate between panes. If a pane is executing a long
|
||||||
// running operation, only allow Esc and use it to cancel the
|
// running operation, only allow Esc and use it to cancel the
|
||||||
|
|
@ -98,51 +81,15 @@ public class Accounts.Editor : Gtk.Dialog {
|
||||||
EditorPane? current_pane = get_current_pane();
|
EditorPane? current_pane = get_current_pane();
|
||||||
if (current_pane != null &&
|
if (current_pane != null &&
|
||||||
current_pane != this.editor_list_pane) {
|
current_pane != this.editor_list_pane) {
|
||||||
Gdk.ModifierType state = (
|
|
||||||
event.state & Gtk.accelerator_get_default_mod_mask()
|
|
||||||
);
|
|
||||||
bool is_ltr = (get_direction() == Gtk.TextDirection.LTR);
|
|
||||||
|
|
||||||
switch (event.keyval) {
|
if (keyval == Gdk.Key.Escape) {
|
||||||
case Gdk.Key.Escape:
|
|
||||||
if (current_pane.is_operation_running) {
|
if (current_pane.is_operation_running) {
|
||||||
current_pane.cancel_operation();
|
current_pane.cancel_operation();
|
||||||
} else {
|
} else {
|
||||||
pop();
|
pop_pane();
|
||||||
}
|
}
|
||||||
ret = Gdk.EVENT_STOP;
|
ret = Gdk.EVENT_STOP;
|
||||||
break;
|
|
||||||
|
|
||||||
case Gdk.Key.Back:
|
|
||||||
if (!current_pane.is_operation_running) {
|
|
||||||
pop();
|
|
||||||
ret = Gdk.EVENT_STOP;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Gdk.Key.Left:
|
|
||||||
if (!current_pane.is_operation_running &&
|
|
||||||
state == Gdk.ModifierType.MOD1_MASK &&
|
|
||||||
is_ltr) {
|
|
||||||
pop();
|
|
||||||
ret = Gdk.EVENT_STOP;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Gdk.Key.Right:
|
|
||||||
if (!current_pane.is_operation_running &&
|
|
||||||
state == Gdk.ModifierType.MOD1_MASK &&
|
|
||||||
!is_ltr) {
|
|
||||||
pop();
|
|
||||||
ret = Gdk.EVENT_STOP;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret != Gdk.EVENT_STOP) {
|
|
||||||
ret = base.key_press_event(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -151,41 +98,20 @@ public class Accounts.Editor : Gtk.Dialog {
|
||||||
/**
|
/**
|
||||||
* Adds and shows a new pane in the editor.
|
* Adds and shows a new pane in the editor.
|
||||||
*/
|
*/
|
||||||
internal void push(EditorPane pane) {
|
internal void push_pane(EditorPane pane) {
|
||||||
// Since we keep old, already-popped panes around (see pop for
|
this.view.push(pane);
|
||||||
// details), when a new pane is pushed on they need to be
|
|
||||||
// truncated.
|
|
||||||
EditorPane current = get_current_pane();
|
|
||||||
int target_length = this.editor_pane_stack.index_of(current) + 1;
|
|
||||||
while (target_length < this.editor_pane_stack.size) {
|
|
||||||
EditorPane old = this.editor_pane_stack.remove_at(target_length);
|
|
||||||
this.editor_panes.remove(old);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now push the new pane on
|
|
||||||
this.editor_pane_stack.add(pane);
|
|
||||||
this.editor_panes.add(pane);
|
|
||||||
this.editor_panes.set_visible_child(pane);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the current pane from the editor, showing the last one.
|
* Removes the current pane from the editor, showing the last one.
|
||||||
*/
|
*/
|
||||||
internal void pop() {
|
internal bool pop_pane() {
|
||||||
// One can't simply remove old panes for the GTK stack since
|
return this.view.pop();
|
||||||
// there won't be any transition between them - the old one
|
|
||||||
// will simply disappear. So we need to keep old, popped panes
|
|
||||||
// around until a new one is pushed on.
|
|
||||||
EditorPane current = get_current_pane();
|
|
||||||
int prev_index = this.editor_pane_stack.index_of(current) - 1;
|
|
||||||
EditorPane prev = this.editor_pane_stack.get(prev_index);
|
|
||||||
this.editor_panes.set_visible_child(prev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Displays an in-app notification in the dialog. */
|
/** Displays an in-app notification in the dialog. */
|
||||||
internal void add_notification(Components.InAppNotification notification) {
|
internal void add_toast(Adw.Toast toast) {
|
||||||
this.notifications_pane.add_overlay(notification);
|
this.toast_overlay.add_toast(toast);
|
||||||
notification.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -202,14 +128,14 @@ public class Accounts.Editor : Gtk.Dialog {
|
||||||
throws Application.CertificateManagerError {
|
throws Application.CertificateManagerError {
|
||||||
try {
|
try {
|
||||||
yield this.certificates.prompt_pin_certificate(
|
yield this.certificates.prompt_pin_certificate(
|
||||||
this, account, service, endpoint, true, cancellable
|
get_root() as Gtk.Window, account, service, endpoint, true, cancellable
|
||||||
);
|
);
|
||||||
} catch (Application.CertificateManagerError.UNTRUSTED err) {
|
} catch (Application.CertificateManagerError.UNTRUSTED err) {
|
||||||
throw err;
|
throw err;
|
||||||
} catch (Application.CertificateManagerError.STORE_FAILED err) {
|
} catch (Application.CertificateManagerError.STORE_FAILED err) {
|
||||||
// XXX show error info bar rather than a notification?
|
// XXX show error info bar rather than a notification?
|
||||||
add_notification(
|
add_toast(
|
||||||
new Components.InAppNotification(
|
new Adw.Toast(
|
||||||
// Translators: In-app notification label, when
|
// Translators: In-app notification label, when
|
||||||
// the app had a problem pinning an otherwise
|
// the app had a problem pinning an otherwise
|
||||||
// untrusted TLS certificate
|
// untrusted TLS certificate
|
||||||
|
|
@ -225,7 +151,7 @@ public class Accounts.Editor : Gtk.Dialog {
|
||||||
|
|
||||||
/** Removes an account from the editor. */
|
/** Removes an account from the editor. */
|
||||||
internal void remove_account(Geary.AccountInformation account) {
|
internal void remove_account(Geary.AccountInformation account) {
|
||||||
this.editor_panes.set_visible_child(this.editor_list_pane);
|
this.view.pop_to_page(this.editor_list_pane);
|
||||||
this.editor_list_pane.remove_account(account);
|
this.editor_list_pane.remove_account(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,7 +170,7 @@ public class Accounts.Editor : Gtk.Dialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline EditorPane? get_current_pane() {
|
private inline EditorPane? get_current_pane() {
|
||||||
return this.editor_panes.get_visible_child() as EditorPane;
|
return this.view.visible_page as EditorPane;
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline GLib.SimpleAction get_action(string name) {
|
private inline GLib.SimpleAction get_action(string name) {
|
||||||
|
|
@ -264,51 +190,18 @@ public class Accounts.Editor : Gtk.Dialog {
|
||||||
pane.redo();
|
pane.redo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
|
||||||
private void on_pane_changed() {
|
|
||||||
EditorPane? visible = get_current_pane();
|
|
||||||
Gtk.Widget? header = null;
|
|
||||||
if (visible != null) {
|
|
||||||
// Do this in an idle callback since it's not 100%
|
|
||||||
// reliable to just call it here for some reason. :(
|
|
||||||
GLib.Idle.add(() => {
|
|
||||||
visible.initial_widget.grab_focus();
|
|
||||||
return GLib.Source.REMOVE;
|
|
||||||
});
|
|
||||||
header = visible.get_header();
|
|
||||||
}
|
|
||||||
set_titlebar(header);
|
|
||||||
update_command_actions();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// XXX I'd really like to make EditorPane an abstract class,
|
|
||||||
// AccountPane an abstract class extending that, and the four concrete
|
|
||||||
// panes extend those, but the GTK+ Builder XML template system
|
|
||||||
// requires a template class to designate its immediate parent
|
|
||||||
// class. I.e. if accounts-editor-list-pane.ui specifies GtkGrid as
|
|
||||||
// the parent of EditorListPane, then it much exactly be that and not
|
|
||||||
// an instance of EditorPane, even if that extends GtkGrid. As a
|
|
||||||
// result, both EditorPane and AccountPane must both be interfaces so
|
|
||||||
// that the concrete pane classes can derive from GtkGrid directly,
|
|
||||||
// and everything becomes horrible. See GTK+ Issue #1151:
|
|
||||||
// https://gitlab.gnome.org/GNOME/gtk/issues/1151
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base interface for panes that can be shown by the accounts editor.
|
* Base interface for panes that can be shown by the accounts editor.
|
||||||
*/
|
*/
|
||||||
internal interface Accounts.EditorPane : Gtk.Grid {
|
internal abstract class Accounts.EditorPane : Adw.NavigationPage {
|
||||||
|
|
||||||
|
|
||||||
/** The editor displaying this pane. */
|
/** The editor displaying this pane. */
|
||||||
internal abstract weak Accounts.Editor editor { get; set; }
|
internal abstract weak Accounts.Editor editor { get; set; }
|
||||||
|
|
||||||
/** The editor displaying this pane. */
|
|
||||||
internal abstract Gtk.Widget initial_widget { get; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if a long running operation is being executed.
|
* Determines if a long running operation is being executed.
|
||||||
*
|
*
|
||||||
|
|
@ -327,9 +220,6 @@ internal interface Accounts.EditorPane : Gtk.Grid {
|
||||||
*/
|
*/
|
||||||
internal abstract GLib.Cancellable? op_cancellable { get; protected set; }
|
internal abstract GLib.Cancellable? op_cancellable { get; protected set; }
|
||||||
|
|
||||||
/** The GTK header bar to display for this pane. */
|
|
||||||
internal abstract Gtk.HeaderBar get_header();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels this pane's current operation, any.
|
* Cancels this pane's current operation, any.
|
||||||
*
|
*
|
||||||
|
|
@ -376,21 +266,13 @@ internal interface Accounts.AccountPane : EditorPane {
|
||||||
this.account.changed.disconnect(on_account_changed);
|
this.account.changed.disconnect(on_account_changed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void on_account_changed() {
|
||||||
* Called when an account has changed.
|
|
||||||
*
|
|
||||||
* By default, updates the editor's header subtitle.
|
|
||||||
*/
|
|
||||||
private void account_changed() {
|
|
||||||
update_header();
|
update_header();
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline void update_header() {
|
private inline void update_header() {
|
||||||
get_header().subtitle = this.account.display_name;
|
// XXX GTK4 - this was subtitle before, will need to make the title subtitle
|
||||||
}
|
this.title = this.account.display_name;
|
||||||
|
|
||||||
private void on_account_changed() {
|
|
||||||
account_changed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
128
src/client/accounts/accounts-mailbox-editor-dialog.vala
Normal file
128
src/client/accounts/accounts-mailbox-editor-dialog.vala
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018-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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple dialog that allows adding/editing Mailboxes (e.g. when configuring
|
||||||
|
* sender addresses).
|
||||||
|
*/
|
||||||
|
[GtkTemplate (ui = "/org/gnome/Geary/accounts-mailbox-editor-dialog.ui")]
|
||||||
|
internal class Accounts.MailboxEditorDialog : Adw.Dialog {
|
||||||
|
|
||||||
|
|
||||||
|
[GtkChild] private unowned Adw.EntryRow name_row;
|
||||||
|
[GtkChild] private unowned Adw.EntryRow address_row;
|
||||||
|
[GtkChild] private unowned Gtk.Button apply_button;
|
||||||
|
[GtkChild] private unowned Gtk.Button remove_button;
|
||||||
|
private Components.EmailValidator address_validator;
|
||||||
|
|
||||||
|
private bool changed = false;
|
||||||
|
|
||||||
|
|
||||||
|
/** The display name for the address */
|
||||||
|
public string display_name { get; construct set; default = ""; }
|
||||||
|
|
||||||
|
/** The raw email address */
|
||||||
|
public string address { get; construct set; default = ""; }
|
||||||
|
|
||||||
|
|
||||||
|
/** Fired if the user pressed "Add"/"Apply" with the new details */
|
||||||
|
public signal void apply(Geary.RFC822.MailboxAddress mailbox);
|
||||||
|
|
||||||
|
/** Fired if the user requested to remove the address */
|
||||||
|
public signal void remove();
|
||||||
|
|
||||||
|
|
||||||
|
static construct {
|
||||||
|
install_action("apply", null, (Gtk.WidgetActionActivateFunc) action_apply);
|
||||||
|
install_action("remove", null, (Gtk.WidgetActionActivateFunc) action_remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
construct {
|
||||||
|
this.name_row.text = this.display_name;
|
||||||
|
this.address_row.text = this.address;
|
||||||
|
this.changed = false;
|
||||||
|
|
||||||
|
this.address_validator =
|
||||||
|
new Components.EmailValidator(this.address_row);
|
||||||
|
this.address_validator.changed.connect((validator) => {
|
||||||
|
action_set_enabled("add", this.changed && input_is_valid());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a MailboxEditorDialog for creating a new mailbox.
|
||||||
|
* @param display_name A suggestion for the name
|
||||||
|
*/
|
||||||
|
public MailboxEditorDialog.for_new(string? display_name) {
|
||||||
|
Object(
|
||||||
|
display_name: display_name ?? "",
|
||||||
|
address: ""
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cange "Apply" to "Add" in this case, since that matches better
|
||||||
|
this.apply_button.label = _("_Add");
|
||||||
|
|
||||||
|
// Can't remove an address that doesn't exist yet
|
||||||
|
action_set_enabled("remove", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailboxEditorDialog.for_existing(Geary.RFC822.MailboxAddress mailbox,
|
||||||
|
bool can_remove) {
|
||||||
|
Object(
|
||||||
|
display_name: mailbox.name ?? "",
|
||||||
|
address: mailbox.address
|
||||||
|
);
|
||||||
|
|
||||||
|
action_set_enabled("remove", can_remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_name_changed(Gtk.Editable editable) {
|
||||||
|
var new_name = this.name_row.text.strip();
|
||||||
|
if (new_name != this.display_name) {
|
||||||
|
this.display_name = new_name;
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_address_changed(Gtk.Editable editable) {
|
||||||
|
this.address = this.address_row.text.strip();
|
||||||
|
var new_address = this.address_row.text.strip();
|
||||||
|
if (new_address != this.address) {
|
||||||
|
this.address = new_address;
|
||||||
|
this.changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_entry_activate() {
|
||||||
|
activate_action("add", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool input_is_valid() {
|
||||||
|
return this.address_validator.state == Components.Validator.Validity.INDETERMINATE
|
||||||
|
|| this.address_validator.is_valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void action_apply(string action_name, Variant? param) {
|
||||||
|
if (!input_is_valid()) {
|
||||||
|
debug("Tried to add mailbox, but email was invalid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(
|
||||||
|
new Geary.RFC822.MailboxAddress(this.display_name, this.address)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void action_remove(string action_name, Variant? param) {
|
||||||
|
remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
173
src/client/accounts/accounts-service-information-widget.vala
Normal file
173
src/client/accounts/accounts-service-information-widget.vala
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||||
|
* Copyright 2018-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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A widget for editing a {@link Geary.ServiceInformation} object.
|
||||||
|
*/
|
||||||
|
[GtkTemplate (ui = "/org/gnome/Geary/accounts-service-information-widget.ui")]
|
||||||
|
internal class Accounts.ServiceInformationWidget : Adw.PreferencesGroup {
|
||||||
|
|
||||||
|
public Geary.ServiceInformation service {
|
||||||
|
get { return this._service; }
|
||||||
|
set {
|
||||||
|
this._service = value;
|
||||||
|
this.service_mutable = new Geary.ServiceInformation.copy(value);
|
||||||
|
update_details();
|
||||||
|
value.notify.connect((obj, pspec) => { update_details(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Geary.ServiceInformation _service;
|
||||||
|
|
||||||
|
// A copy of the original that can be without breaking the original
|
||||||
|
public Geary.ServiceInformation service_mutable { get ; private set; }
|
||||||
|
|
||||||
|
public Components.ValidatorGroup validators { get; construct set; }
|
||||||
|
|
||||||
|
[GtkChild] private unowned Adw.EntryRow host_row;
|
||||||
|
[GtkChild] private unowned TlsComboRow security_row;
|
||||||
|
[GtkChild] private unowned Adw.ComboRow credentials_requirement_row;
|
||||||
|
[GtkChild] private unowned Adw.EntryRow login_name_row;
|
||||||
|
[GtkChild] private unowned Adw.PasswordEntryRow password_row;
|
||||||
|
|
||||||
|
|
||||||
|
static construct {
|
||||||
|
typeof(TlsComboRow).ensure();
|
||||||
|
typeof(Components.ValidatorGroup).ensure();
|
||||||
|
typeof(Components.Validator).ensure();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether editing the information is possible
|
||||||
|
*/
|
||||||
|
public void set_editable(bool editable) {
|
||||||
|
this.sensitive = editable;
|
||||||
|
update_details();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_details() {
|
||||||
|
update_host_row(this.host_row, this.service_mutable);
|
||||||
|
this.security_row.method = this.service_mutable.transport_security;
|
||||||
|
update_auth(this.service_mutable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_host_row(Adw.EntryRow row, Geary.ServiceInformation service) {
|
||||||
|
row.title = host_label_for_protocol(service.protocol);
|
||||||
|
|
||||||
|
row.text = service.host ?? "";
|
||||||
|
if (!Geary.String.is_empty(service.host)) {
|
||||||
|
// Only show the port if it not the appropriate default port
|
||||||
|
uint16 port = service.port;
|
||||||
|
if (port != service.get_default_port()) {
|
||||||
|
row.text = "%s:%d".printf(service.host, service.port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string host_label_for_protocol(Geary.Protocol protocol) {
|
||||||
|
switch (protocol) {
|
||||||
|
case Geary.Protocol.IMAP:
|
||||||
|
// Translators: This label describes the host name or IP
|
||||||
|
// address and port used by an account's IMAP service.
|
||||||
|
return _("IMAP Server");
|
||||||
|
|
||||||
|
case Geary.Protocol.SMTP:
|
||||||
|
// Translators: This label describes the host name or IP
|
||||||
|
// address and port used by an account's SMTP service.
|
||||||
|
return _("SMTP Server");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _("Unknown Protocol");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_login_name_row(Adw.EntryRow row,
|
||||||
|
Geary.ServiceInformation service) {
|
||||||
|
// Translators: Label used when no auth scheme is used
|
||||||
|
// by an account's IMAP or SMTP service.
|
||||||
|
row.text = _("None");
|
||||||
|
|
||||||
|
// If we have credentials, we can do better
|
||||||
|
if (service.credentials != null) {
|
||||||
|
switch (service.credentials.supported_method) {
|
||||||
|
case Geary.Credentials.Method.PASSWORD:
|
||||||
|
row.text = service.credentials.user;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Geary.Credentials.Method.OAUTH2:
|
||||||
|
// Add a suffix for OAuth2 auth so people know they
|
||||||
|
// shouldn't expect to be prompted for a password
|
||||||
|
|
||||||
|
// Translators: Label used when an account's IMAP or
|
||||||
|
// SMTP service uses OAuth2. The string replacement is
|
||||||
|
// the service's login name.
|
||||||
|
row.text = _("%s using OAuth2").printf(service.credentials.user ?? "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we rely on the credentials of the incoming server, notify the user of that
|
||||||
|
if (service.protocol == Geary.Protocol.SMTP &&
|
||||||
|
service.credentials_requirement ==
|
||||||
|
Geary.Credentials.Requirement.USE_INCOMING) {
|
||||||
|
row.text = _("Use receiving server login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_password_row(Adw.PasswordEntryRow row,
|
||||||
|
Geary.ServiceInformation service) {
|
||||||
|
if (service.credentials != null) {
|
||||||
|
row.text = service.credentials.token ?? "";
|
||||||
|
} else {
|
||||||
|
row.text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not enabled, the "Show Password" button is insensitive too
|
||||||
|
// so just hide the row
|
||||||
|
if (!this.sensitive)
|
||||||
|
row.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_host_row_changed(Gtk.Editable editable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_auth(Geary.ServiceInformation service) {
|
||||||
|
bool is_smtp = (service.protocol == Geary.Protocol.SMTP);
|
||||||
|
this.credentials_requirement_row.visible = is_smtp;
|
||||||
|
|
||||||
|
if (is_smtp) {
|
||||||
|
this.credentials_requirement_row.selected = service.credentials_requirement;
|
||||||
|
|
||||||
|
bool needs_login =
|
||||||
|
(service.credentials_requirement == Geary.Credentials.Requirement.CUSTOM);
|
||||||
|
this.login_name_row.visible = needs_login;
|
||||||
|
this.password_row.visible = needs_login;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_login_name_row(this.login_name_row, this.service_mutable);
|
||||||
|
update_password_row(this.password_row, this.service_mutable);
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private static string outgoing_auth_to_string(Adw.EnumListItem item,
|
||||||
|
Geary.Credentials.Requirement requirement) {
|
||||||
|
return requirement.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_validators_changed(Components.ValidatorGroup validators,
|
||||||
|
Components.Validator validator) {
|
||||||
|
//XXX what do we do here?
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_validators_activated(Components.ValidatorGroup validators,
|
||||||
|
Components.Validator validator) {
|
||||||
|
//XXX what do we do here?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,8 +22,9 @@ public class Accounts.SignatureWebView : Components.WebView {
|
||||||
|
|
||||||
|
|
||||||
public SignatureWebView(Application.Configuration config) {
|
public SignatureWebView(Application.Configuration config) {
|
||||||
base(config);
|
base(config, null);
|
||||||
this.user_content_manager.add_script(SignatureWebView.app_script);
|
this.user_content_manager.add_script(SignatureWebView.app_script);
|
||||||
|
add_css_class("geary-signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
55
src/client/accounts/accounts-tls-combo-row.vala
Normal file
55
src/client/accounts/accounts-tls-combo-row.vala
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 Niels De Graef <nielsdegraef@gmail.com>
|
||||||
|
*
|
||||||
|
* This software is licensed under the GNU Lesser General Public License
|
||||||
|
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[GtkTemplate (ui = "/org/gnome/Geary/accounts-tls-combo-row.ui")]
|
||||||
|
internal class Accounts.TlsComboRow : Adw.ComboRow {
|
||||||
|
|
||||||
|
private const string INSECURE_ICON = "channel-insecure-symbolic";
|
||||||
|
private const string SECURE_ICON = "channel-secure-symbolic";
|
||||||
|
|
||||||
|
|
||||||
|
public Geary.TlsNegotiationMethod method {
|
||||||
|
get { return ((Adw.EnumListItem) this.selected_item).value; }
|
||||||
|
set { this.selected = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_factory_setup(Gtk.SignalListItemFactory factory,
|
||||||
|
GLib.Object object) {
|
||||||
|
unowned var item = (Gtk.ListItem) object;
|
||||||
|
|
||||||
|
var image = new Gtk.Image();
|
||||||
|
|
||||||
|
var label = new Gtk.Label(null);
|
||||||
|
label.xalign = 1.0f;
|
||||||
|
|
||||||
|
var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
|
||||||
|
box.append(image);
|
||||||
|
box.append(label);
|
||||||
|
|
||||||
|
item.child = box;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_factory_bind(Gtk.SignalListItemFactory factory,
|
||||||
|
GLib.Object object) {
|
||||||
|
unowned var item = (Gtk.ListItem) object;
|
||||||
|
unowned var enum_item = (Adw.EnumListItem) item.item;
|
||||||
|
var method = (Geary.TlsNegotiationMethod) enum_item.get_value();
|
||||||
|
|
||||||
|
unowned var box = (Gtk.Box) item.child;
|
||||||
|
|
||||||
|
unowned var image = (Gtk.Image) box.get_first_child();
|
||||||
|
if (method == Geary.TlsNegotiationMethod.NONE)
|
||||||
|
image.icon_name = "channel-insecure-symbolic";
|
||||||
|
else
|
||||||
|
image.icon_name = "channel-secure-symbolic";
|
||||||
|
|
||||||
|
unowned var label = (Gtk.Label) image.get_next_sibling();
|
||||||
|
label.label = method.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -84,67 +84,70 @@ public class Application.AttachmentManager : GLib.Object {
|
||||||
* else false.
|
* else false.
|
||||||
*/
|
*/
|
||||||
public async bool save_buffer(string display_name,
|
public async bool save_buffer(string display_name,
|
||||||
Geary.Memory.Buffer buffer,
|
Geary.Memory.Buffer buffer,
|
||||||
GLib.Cancellable? cancellable) {
|
GLib.Cancellable? cancellable) {
|
||||||
Gtk.FileChooserNative dialog = new_save_chooser(SAVE);
|
var dialog = new Gtk.FileDialog();
|
||||||
dialog.set_current_name(display_name);
|
dialog.initial_name = display_name;
|
||||||
|
dialog.initial_folder = download_dir();
|
||||||
|
|
||||||
string? destination_uri = null;
|
File? destination = null;
|
||||||
if (dialog.run() == Gtk.ResponseType.ACCEPT) {
|
try {
|
||||||
destination_uri = dialog.get_uri();
|
destination = yield dialog.save(this.parent, cancellable);
|
||||||
|
} catch (Error err) {
|
||||||
|
//XXX GTK4 check if cancelled is accidentally caught here as well
|
||||||
|
warning("Couldn't select file to save attachment: %s", err.message);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
dialog.destroy();
|
|
||||||
|
|
||||||
bool succeeded = false;
|
return yield check_and_write(buffer, destination, cancellable);
|
||||||
if (!Geary.String.is_empty_or_whitespace(destination_uri)) {
|
|
||||||
succeeded = yield check_and_write(
|
|
||||||
buffer, GLib.File.new_for_uri(destination_uri), cancellable
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return succeeded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async bool save_all(Gee.Collection<Geary.Attachment> attachments,
|
private async bool save_all(Gee.Collection<Geary.Attachment> attachments,
|
||||||
GLib.Cancellable? cancellable) {
|
GLib.Cancellable? cancellable) {
|
||||||
var dialog = new_save_chooser(SELECT_FOLDER);
|
var dialog = new Gtk.FileDialog();
|
||||||
string? destination_uri = null;
|
dialog.initial_file = download_dir();
|
||||||
if (dialog.run() == Gtk.ResponseType.ACCEPT) {
|
|
||||||
destination_uri = dialog.get_uri();
|
File? destination_dir = null;
|
||||||
|
try {
|
||||||
|
destination_dir = yield dialog.select_folder(this.parent, cancellable);
|
||||||
|
} catch (Error err) {
|
||||||
|
//XXX GTK4 check if cancelled is accidentally caught here as well
|
||||||
|
warning("Couldn't select folder for saving attachments: %s", err.message);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
dialog.destroy();
|
|
||||||
|
if (destination_dir == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
bool succeeded = false;
|
bool succeeded = false;
|
||||||
if (!Geary.String.is_empty_or_whitespace(destination_uri)) {
|
foreach (Geary.Attachment attachment in attachments) {
|
||||||
var destination_dir = GLib.File.new_for_uri(destination_uri);
|
GLib.File? destination = null;
|
||||||
foreach (Geary.Attachment attachment in attachments) {
|
try {
|
||||||
GLib.File? destination = null;
|
destination = destination_dir.get_child_for_display_name(
|
||||||
try {
|
yield attachment.get_safe_file_name(
|
||||||
destination = destination_dir.get_child_for_display_name(
|
AttachmentManager.untitled_file_name
|
||||||
yield attachment.get_safe_file_name(
|
)
|
||||||
AttachmentManager.untitled_file_name
|
);
|
||||||
)
|
} catch (GLib.IOError.CANCELLED err) {
|
||||||
);
|
// Everything is going to fail from now on, so get
|
||||||
} catch (GLib.IOError.CANCELLED err) {
|
// out of here
|
||||||
// Everything is going to fail from now on, so get
|
succeeded = false;
|
||||||
// out of here
|
break;
|
||||||
succeeded = false;
|
} catch (GLib.Error err) {
|
||||||
break;
|
warning(
|
||||||
} catch (GLib.Error err) {
|
"Error determining file system name for \"%s\": %s",
|
||||||
warning(
|
attachment.file.get_uri(), err.message
|
||||||
"Error determining file system name for \"%s\": %s",
|
);
|
||||||
attachment.file.get_uri(), err.message
|
handle_error(err);
|
||||||
);
|
}
|
||||||
handle_error(err);
|
var content = yield open_buffer(attachment, cancellable);
|
||||||
}
|
if (content != null &&
|
||||||
var content = yield open_buffer(attachment, cancellable);
|
destination != null) {
|
||||||
if (content != null &&
|
succeeded &= yield check_and_write(
|
||||||
destination != null) {
|
content, destination, cancellable
|
||||||
succeeded &= yield check_and_write(
|
);
|
||||||
content, destination, cancellable
|
} else {
|
||||||
);
|
succeeded = false;
|
||||||
} else {
|
|
||||||
succeeded = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return succeeded;
|
return succeeded;
|
||||||
|
|
@ -229,14 +232,18 @@ public class Application.AttachmentManager : GLib.Object {
|
||||||
"The file already exists in “%s”. Replacing it will overwrite its contents."
|
"The file already exists in “%s”. Replacing it will overwrite its contents."
|
||||||
).printf(parent_name);
|
).printf(parent_name);
|
||||||
|
|
||||||
ConfirmationDialog dialog = new ConfirmationDialog(
|
var dialog = new Adw.AlertDialog(primary, secondary);
|
||||||
this.parent,
|
dialog.add_responses(
|
||||||
primary,
|
"replace", _("_Replace"),
|
||||||
secondary,
|
"cancel", _("_Cancel"),
|
||||||
_("_Replace"),
|
null
|
||||||
"destructive-action"
|
|
||||||
);
|
);
|
||||||
return (dialog.run() == Gtk.ResponseType.OK);
|
dialog.default_response = "cancel";
|
||||||
|
dialog.close_response = "cancel";
|
||||||
|
dialog.set_response_appearance("replace", Adw.ResponseAppearance.DESTRUCTIVE);
|
||||||
|
string response = yield dialog.choose(this.parent, cancellable);
|
||||||
|
|
||||||
|
return (response == "replace");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void write_buffer_to_file(Geary.Memory.Buffer buffer,
|
private async void write_buffer_to_file(Geary.Memory.Buffer buffer,
|
||||||
|
|
@ -263,20 +270,11 @@ public class Application.AttachmentManager : GLib.Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline Gtk.FileChooserNative new_save_chooser(Gtk.FileChooserAction action) {
|
private File? download_dir() {
|
||||||
Gtk.FileChooserNative dialog = new Gtk.FileChooserNative(
|
|
||||||
null,
|
|
||||||
this.parent,
|
|
||||||
action,
|
|
||||||
Stock._SAVE,
|
|
||||||
Stock._CANCEL
|
|
||||||
);
|
|
||||||
var download_dir = GLib.Environment.get_user_special_dir(DOWNLOAD);
|
var download_dir = GLib.Environment.get_user_special_dir(DOWNLOAD);
|
||||||
if (!Geary.String.is_empty_or_whitespace(download_dir)) {
|
if (Geary.String.is_empty_or_whitespace(download_dir))
|
||||||
dialog.set_current_folder(download_dir);
|
return null;
|
||||||
}
|
return File.new_for_path(download_dir);
|
||||||
dialog.set_local_only(false);
|
|
||||||
return dialog;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline void handle_error(GLib.Error error) {
|
private inline void handle_error(GLib.Error error) {
|
||||||
|
|
|
||||||
|
|
@ -118,11 +118,11 @@ public class Application.CertificateManager : GLib.Object {
|
||||||
GLib.Cancellable? cancellable)
|
GLib.Cancellable? cancellable)
|
||||||
throws CertificateManagerError {
|
throws CertificateManagerError {
|
||||||
CertificateWarningDialog dialog = new CertificateWarningDialog(
|
CertificateWarningDialog dialog = new CertificateWarningDialog(
|
||||||
parent, account, service, endpoint, is_validation
|
account, service, endpoint, is_validation
|
||||||
);
|
);
|
||||||
|
|
||||||
bool save = false;
|
bool save = false;
|
||||||
switch (dialog.run()) {
|
switch (yield dialog.run(parent)) {
|
||||||
case CertificateWarningDialog.Result.TRUST:
|
case CertificateWarningDialog.Result.TRUST:
|
||||||
// noop
|
// noop
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
/**
|
/**
|
||||||
* The client application's main point of entry and desktop integration.
|
* The client application's main point of entry and desktop integration.
|
||||||
*/
|
*/
|
||||||
public class Application.Client : Gtk.Application {
|
public class Application.Client : Adw.Application {
|
||||||
|
|
||||||
public const string NAME = "Geary" + Config.NAME_SUFFIX;
|
public const string NAME = "Geary" + Config.NAME_SUFFIX;
|
||||||
public const string RESOURCE_BASE_PATH = "/org/gnome/Geary";
|
public const string RESOURCE_BASE_PATH = "/org/gnome/Geary";
|
||||||
|
|
@ -222,7 +222,6 @@ public class Application.Client : Gtk.Application {
|
||||||
|
|
||||||
private File exec_dir;
|
private File exec_dir;
|
||||||
private string binary;
|
private string binary;
|
||||||
private Gtk.CssProvider single_key_shortcuts = new Gtk.CssProvider();
|
|
||||||
private GLib.Cancellable controller_cancellable = new GLib.Cancellable();
|
private GLib.Cancellable controller_cancellable = new GLib.Cancellable();
|
||||||
private Components.Inspector? inspector = null;
|
private Components.Inspector? inspector = null;
|
||||||
private Geary.Nonblocking.Mutex controller_mutex = new Geary.Nonblocking.Mutex();
|
private Geary.Nonblocking.Mutex controller_mutex = new Geary.Nonblocking.Mutex();
|
||||||
|
|
@ -348,9 +347,6 @@ public class Application.Client : Gtk.Application {
|
||||||
|
|
||||||
// Calls Gtk.init(), amongst other things
|
// Calls Gtk.init(), amongst other things
|
||||||
base.startup();
|
base.startup();
|
||||||
Hdy.init();
|
|
||||||
Hdy.StyleManager.get_default().set_color_scheme(
|
|
||||||
Hdy.ColorScheme.PREFER_LIGHT);
|
|
||||||
|
|
||||||
this.engine = new Geary.Engine(get_resource_directory());
|
this.engine = new Geary.Engine(get_resource_directory());
|
||||||
this.config = new Configuration(SCHEMA_ID);
|
this.config = new Configuration(SCHEMA_ID);
|
||||||
|
|
@ -378,27 +374,21 @@ public class Application.Client : Gtk.Application {
|
||||||
add_edit_accelerators(Action.Edit.REDO, { "<Ctrl><Shift>Z" });
|
add_edit_accelerators(Action.Edit.REDO, { "<Ctrl><Shift>Z" });
|
||||||
add_edit_accelerators(Action.Edit.UNDO, { "<Ctrl>Z" });
|
add_edit_accelerators(Action.Edit.UNDO, { "<Ctrl>Z" });
|
||||||
|
|
||||||
// Load Geary GTK CSS
|
//XXX GTK4 key shortcut themes aren't supported yet: https://gitlab.gnome.org/GNOME/gtk/-/issues/1669#note_1735942
|
||||||
var provider = new Gtk.CssProvider();
|
#if 0
|
||||||
Gtk.StyleContext.add_provider_for_screen(
|
// Load Geary CSS for single key shortcuts
|
||||||
Gdk.Display.get_default().get_default_screen(),
|
|
||||||
provider,
|
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
|
||||||
);
|
|
||||||
load_css(provider,
|
|
||||||
"resource:///org/gnome/Geary/geary.css");
|
|
||||||
load_css(this.single_key_shortcuts,
|
load_css(this.single_key_shortcuts,
|
||||||
"resource:///org/gnome/Geary/single-key-shortcuts.css");
|
"resource:///org/gnome/Geary/single-key-shortcuts.css");
|
||||||
update_single_key_shortcuts();
|
update_single_key_shortcuts();
|
||||||
this.config.notify[Configuration.SINGLE_KEY_SHORTCUTS].connect(
|
this.config.notify[Configuration.SINGLE_KEY_SHORTCUTS].connect(
|
||||||
on_single_key_shortcuts_toggled
|
on_single_key_shortcuts_toggled
|
||||||
);
|
);
|
||||||
|
#endif
|
||||||
|
|
||||||
MainWindow.add_accelerators(this);
|
MainWindow.add_accelerators(this);
|
||||||
Composer.Editor.add_accelerators(this);
|
Composer.Editor.add_accelerators(this);
|
||||||
Composer.Widget.add_accelerators(this);
|
Composer.Widget.add_accelerators(this);
|
||||||
Components.Inspector.add_accelerators(this);
|
Components.Inspector.add_accelerators(this);
|
||||||
Components.PreferencesWindow.add_accelerators(this);
|
|
||||||
Dialogs.ProblemDetailsDialog.add_accelerators(this);
|
Dialogs.ProblemDetailsDialog.add_accelerators(this);
|
||||||
|
|
||||||
// Manually place a hold on the application otherwise the
|
// Manually place a hold on the application otherwise the
|
||||||
|
|
@ -432,7 +422,7 @@ public class Application.Client : Gtk.Application {
|
||||||
// thing down if it takes too long to complete
|
// thing down if it takes too long to complete
|
||||||
int64 start_usec = get_monotonic_time();
|
int64 start_usec = get_monotonic_time();
|
||||||
while (!controller_closed) {
|
while (!controller_closed) {
|
||||||
Gtk.main_iteration();
|
MainContext.default().iteration(false);
|
||||||
|
|
||||||
int64 delta_usec = get_monotonic_time() - start_usec;
|
int64 delta_usec = get_monotonic_time() - start_usec;
|
||||||
if (delta_usec >= FORCE_SHUTDOWN_USEC) {
|
if (delta_usec >= FORCE_SHUTDOWN_USEC) {
|
||||||
|
|
@ -553,12 +543,11 @@ public class Application.Client : Gtk.Application {
|
||||||
public async void show_accounts() {
|
public async void show_accounts() {
|
||||||
yield this.present();
|
yield this.present();
|
||||||
|
|
||||||
Accounts.Editor editor = new Accounts.Editor(
|
Accounts.Editor editor = new Accounts.Editor(this);
|
||||||
this, get_active_main_window()
|
editor.present(get_active_main_window());
|
||||||
);
|
editor.closed.connect((editor) => {
|
||||||
editor.run();
|
this.controller.expunge_accounts.begin();
|
||||||
editor.destroy();
|
});
|
||||||
this.controller.expunge_accounts.begin();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -667,9 +656,10 @@ public class Application.Client : Gtk.Application {
|
||||||
|
|
||||||
if (this.inspector == null) {
|
if (this.inspector == null) {
|
||||||
this.inspector = new Components.Inspector(this);
|
this.inspector = new Components.Inspector(this);
|
||||||
this.inspector.destroy.connect(() => {
|
this.inspector.close_request.connect(() => {
|
||||||
this.inspector = null;
|
this.inspector = null;
|
||||||
});
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
// Create a new window group for the inspector so it is
|
// Create a new window group for the inspector so it is
|
||||||
// not affected by the app's modal dialogs
|
// not affected by the app's modal dialogs
|
||||||
|
|
@ -685,11 +675,11 @@ public class Application.Client : Gtk.Application {
|
||||||
public async void show_preferences() {
|
public async void show_preferences() {
|
||||||
yield this.present();
|
yield this.present();
|
||||||
|
|
||||||
Components.PreferencesWindow prefs = new Components.PreferencesWindow(
|
var prefs = new Components.PreferencesDialog(
|
||||||
get_active_main_window(),
|
this,
|
||||||
this.controller.plugins
|
this.controller.plugins
|
||||||
);
|
);
|
||||||
prefs.show();
|
prefs.present(get_active_main_window());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void new_composer(Geary.RFC822.MailboxAddress? to = null) {
|
public async void new_composer(Geary.RFC822.MailboxAddress? to = null) {
|
||||||
|
|
@ -820,10 +810,9 @@ public class Application.Client : Gtk.Application {
|
||||||
uri_ = "http://" + uri;
|
uri_ = "http://" + uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var launcher = new Gtk.UriLauncher(uri_);
|
||||||
try {
|
try {
|
||||||
Gtk.show_uri_on_window(
|
yield launcher.launch(get_active_window(), null);
|
||||||
get_active_window(), uri_, Gdk.CURRENT_TIME
|
|
||||||
);
|
|
||||||
} catch (GLib.Error err) {
|
} catch (GLib.Error err) {
|
||||||
this.controller.report_problem(new Geary.ProblemReport(err));
|
this.controller.report_problem(new Geary.ProblemReport(err));
|
||||||
}
|
}
|
||||||
|
|
@ -837,8 +826,10 @@ public class Application.Client : Gtk.Application {
|
||||||
* prompted about and if cancelled, will cancel shut-down here.
|
* prompted about and if cancelled, will cancel shut-down here.
|
||||||
*/
|
*/
|
||||||
public new void quit() {
|
public new void quit() {
|
||||||
if (this.controller == null ||
|
//XXX GTK4 this is now async, need to figure out how to do this
|
||||||
this.controller.check_open_composers()) {
|
// if (this.controller == null ||
|
||||||
|
// this.controller.check_open_composers()) {
|
||||||
|
if (this.controller == null) {
|
||||||
this.last_active_main_window = null;
|
this.last_active_main_window = null;
|
||||||
base.quit();
|
base.quit();
|
||||||
}
|
}
|
||||||
|
|
@ -908,7 +899,9 @@ public class Application.Client : Gtk.Application {
|
||||||
private MainWindow new_main_window(bool select_first_inbox) {
|
private MainWindow new_main_window(bool select_first_inbox) {
|
||||||
MainWindow window = new MainWindow(this);
|
MainWindow window = new MainWindow(this);
|
||||||
this.controller.register_window(window);
|
this.controller.register_window(window);
|
||||||
window.focus_in_event.connect(on_main_window_focus_in);
|
Gtk.EventControllerFocus focus_controller = new Gtk.EventControllerFocus();
|
||||||
|
focus_controller.enter.connect(on_main_window_focus_enter);
|
||||||
|
((Gtk.Widget) window).add_controller(focus_controller);
|
||||||
if (select_first_inbox) {
|
if (select_first_inbox) {
|
||||||
if (!window.select_first_inbox(true)) {
|
if (!window.select_first_inbox(true)) {
|
||||||
// The first inbox wasn't selected, so the account is
|
// The first inbox wasn't selected, so the account is
|
||||||
|
|
@ -958,11 +951,10 @@ public class Application.Client : Gtk.Application {
|
||||||
open_failed = true;
|
open_failed = true;
|
||||||
warning("Error creating controller: %s", err.message);
|
warning("Error creating controller: %s", err.message);
|
||||||
var dialog = new Dialogs.ProblemDetailsDialog(
|
var dialog = new Dialogs.ProblemDetailsDialog(
|
||||||
null,
|
|
||||||
this,
|
this,
|
||||||
new Geary.ProblemReport(err)
|
new Geary.ProblemReport(err)
|
||||||
);
|
);
|
||||||
dialog.show();
|
dialog.present(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mutex_token != Geary.Nonblocking.Mutex.INVALID_TOKEN) {
|
if (mutex_token != Geary.Nonblocking.Mutex.INVALID_TOKEN) {
|
||||||
|
|
@ -1100,20 +1092,23 @@ public class Application.Client : Gtk.Application {
|
||||||
set_accels_for_action("app." + action, accelerators);
|
set_accels_for_action("app." + action, accelerators);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX GTK4 key shortcut themes aren't supported yet: https://gitlab.gnome.org/GNOME/gtk/-/issues/1669#note_1735942
|
||||||
|
#if 0
|
||||||
private void update_single_key_shortcuts() {
|
private void update_single_key_shortcuts() {
|
||||||
if (this.config.single_key_shortcuts) {
|
if (this.config.single_key_shortcuts) {
|
||||||
Gtk.StyleContext.add_provider_for_screen(
|
Gtk.StyleContext.add_provider_for_display(
|
||||||
Gdk.Display.get_default().get_default_screen(),
|
Gdk.Display.get_default(),
|
||||||
this.single_key_shortcuts,
|
this.single_key_shortcuts,
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Gtk.StyleContext.remove_provider_for_screen(
|
Gtk.StyleContext.remove_provider_for_display(
|
||||||
Gdk.Display.get_default().get_default_screen(),
|
Gdk.Display.get_default(),
|
||||||
this.single_key_shortcuts
|
this.single_key_shortcuts
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private void load_css(Gtk.CssProvider provider, string resource_uri) {
|
private void load_css(Gtk.CssProvider provider, string resource_uri) {
|
||||||
provider.parsing_error.connect(on_css_parse_error);
|
provider.parsing_error.connect(on_css_parse_error);
|
||||||
|
|
@ -1197,7 +1192,8 @@ public class Application.Client : Gtk.Application {
|
||||||
private void on_activate_help() {
|
private void on_activate_help() {
|
||||||
try {
|
try {
|
||||||
if (this.is_installed) {
|
if (this.is_installed) {
|
||||||
this.show_uri.begin("help:geary");
|
var launcher = new Gtk.UriLauncher("help:geary");
|
||||||
|
launcher.launch.begin(get_active_window(), null);
|
||||||
} else {
|
} else {
|
||||||
Pid pid;
|
Pid pid;
|
||||||
File exec_dir = this.exec_dir;
|
File exec_dir = this.exec_dir;
|
||||||
|
|
@ -1217,17 +1213,10 @@ public class Application.Client : Gtk.Application {
|
||||||
}
|
}
|
||||||
} catch (Error error) {
|
} catch (Error error) {
|
||||||
debug("Error showing help: %s", error.message);
|
debug("Error showing help: %s", error.message);
|
||||||
Gtk.Dialog dialog = new Gtk.Dialog.with_buttons(
|
Adw.AlertDialog dialog = new Adw.AlertDialog("Error",
|
||||||
"Error",
|
"Error showing help: %s".printf(error.message)
|
||||||
get_active_window(),
|
|
||||||
Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
||||||
Stock._CLOSE, Gtk.ResponseType.CLOSE, null);
|
|
||||||
dialog.response.connect(() => { dialog.destroy(); });
|
|
||||||
dialog.get_content_area().add(
|
|
||||||
new Gtk.Label("Error showing help: %s".printf(error.message))
|
|
||||||
);
|
);
|
||||||
dialog.show_all();
|
dialog.present(get_active_window());
|
||||||
dialog.run();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1243,13 +1232,11 @@ public class Application.Client : Gtk.Application {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_main_window_focus_in(Gtk.Widget widget,
|
private void on_main_window_focus_enter(Gtk.EventControllerFocus focus_controller) {
|
||||||
Gdk.EventFocus event) {
|
MainWindow? main = focus_controller.get_widget() as MainWindow;
|
||||||
MainWindow? main = widget as MainWindow;
|
|
||||||
if (main != null) {
|
if (main != null) {
|
||||||
this.last_active_main_window = main;
|
this.last_active_main_window = main;
|
||||||
}
|
}
|
||||||
return Gdk.EVENT_PROPAGATE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_window_removed(Gtk.Window window) {
|
private void on_window_removed(Gtk.Window window) {
|
||||||
|
|
@ -1271,22 +1258,25 @@ public class Application.Client : Gtk.Application {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX GTK4 key shortcut themes aren't supported yet: https://gitlab.gnome.org/GNOME/gtk/-/issues/1669#note_1735942
|
||||||
|
#if 0
|
||||||
private void on_single_key_shortcuts_toggled() {
|
private void on_single_key_shortcuts_toggled() {
|
||||||
update_single_key_shortcuts();
|
update_single_key_shortcuts();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private void on_css_parse_error(Gtk.CssSection section, GLib.Error error) {
|
private void on_css_parse_error(Gtk.CssProvider provider, Gtk.CssSection section, GLib.Error error) {
|
||||||
uint start = section.get_start_line();
|
var start = section.get_start_location();
|
||||||
uint end = section.get_end_line();
|
var end = section.get_end_location();
|
||||||
if (start == end) {
|
if (start.lines == end.lines) {
|
||||||
warning(
|
warning(
|
||||||
"Error parsing %s:%u: %s",
|
"Error parsing %s:%"+size_t.FORMAT+": %s",
|
||||||
section.get_file().get_uri(), start, error.message
|
section.get_file().get_uri(), start.lines, error.message
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
warning(
|
warning(
|
||||||
"Error parsing %s:%u-%u: %s",
|
"Error parsing %s:%"+size_t.FORMAT+"-%"+size_t.FORMAT+": %s",
|
||||||
section.get_file().get_uri(), start, end, error.message
|
section.get_file().get_uri(), start.lines, end.lines, error.message
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,8 @@ public class Application.ContactStore : Geary.BaseObject {
|
||||||
Contact result = yield get_contact(
|
Contact result = yield get_contact(
|
||||||
individual, null, cancellable
|
individual, null, cancellable
|
||||||
);
|
);
|
||||||
foreach (Geary.RFC822.MailboxAddress mailbox
|
for (uint i = 0; i < result.email_addresses.get_n_items(); i++) {
|
||||||
in result.email_addresses) {
|
var mailbox = (Geary.RFC822.MailboxAddress) result.email_addresses.get_item(i);
|
||||||
seen.add(to_cache_key(mailbox.address));
|
seen.add(to_cache_key(mailbox.address));
|
||||||
}
|
}
|
||||||
results.add(result);
|
results.add(result);
|
||||||
|
|
@ -140,8 +140,8 @@ public class Application.ContactStore : Geary.BaseObject {
|
||||||
Contact result = yield get_contact(
|
Contact result = yield get_contact(
|
||||||
individual, null, cancellable
|
individual, null, cancellable
|
||||||
);
|
);
|
||||||
foreach (Geary.RFC822.MailboxAddress mailbox
|
for (uint i = 0; i < result.email_addresses.get_n_items(); i++) {
|
||||||
in result.email_addresses) {
|
var mailbox = (Geary.RFC822.MailboxAddress) result.email_addresses.get_item(i);
|
||||||
seen.add(to_cache_key(mailbox.address));
|
seen.add(to_cache_key(mailbox.address));
|
||||||
}
|
}
|
||||||
results.add(result);
|
results.add(result);
|
||||||
|
|
@ -166,8 +166,8 @@ public class Application.ContactStore : Geary.BaseObject {
|
||||||
Contact result = yield load(
|
Contact result = yield load(
|
||||||
contact.get_rfc822_address(), cancellable
|
contact.get_rfc822_address(), cancellable
|
||||||
);
|
);
|
||||||
foreach (Geary.RFC822.MailboxAddress mailbox
|
for (uint i = 0; i < result.email_addresses.get_n_items(); i++) {
|
||||||
in result.email_addresses) {
|
var mailbox = (Geary.RFC822.MailboxAddress) result.email_addresses.get_item(i);
|
||||||
seen.add(to_cache_key(mailbox.address));
|
seen.add(to_cache_key(mailbox.address));
|
||||||
}
|
}
|
||||||
results.add(result);
|
results.add(result);
|
||||||
|
|
|
||||||
|
|
@ -55,24 +55,23 @@ public class Application.Contact : Geary.BaseObject {
|
||||||
public bool load_remote_resources { get; private set; }
|
public bool load_remote_resources { get; private set; }
|
||||||
|
|
||||||
/** The set of email addresses associated with this contact. */
|
/** The set of email addresses associated with this contact. */
|
||||||
public Gee.Collection<Geary.RFC822.MailboxAddress> email_addresses {
|
public GLib.ListModel email_addresses {
|
||||||
get {
|
get {
|
||||||
Gee.Collection<Geary.RFC822.MailboxAddress>? addrs =
|
if (this._email_addresses == null) {
|
||||||
this._email_addresses;
|
var addrs = new GLib.ListStore(typeof(Geary.RFC822.MailboxAddress));
|
||||||
if (addrs == null) {
|
|
||||||
addrs = new Gee.LinkedList<Geary.RFC822.MailboxAddress>();
|
|
||||||
foreach (Folks.EmailFieldDetails email in
|
foreach (Folks.EmailFieldDetails email in
|
||||||
this.individual.email_addresses) {
|
this.individual.email_addresses) {
|
||||||
addrs.add(new Geary.RFC822.MailboxAddress(
|
var mailbox_addr = new Geary.RFC822.MailboxAddress(
|
||||||
this.display_name, email.value
|
this.display_name, email.value
|
||||||
));
|
);
|
||||||
|
addrs.append(mailbox_addr);
|
||||||
}
|
}
|
||||||
this._email_addresses = addrs;
|
this._email_addresses = addrs;
|
||||||
}
|
}
|
||||||
return this._email_addresses;
|
return this._email_addresses;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private Gee.Collection<Geary.RFC822.MailboxAddress>? _email_addresses = null;
|
private GLib.ListModel? _email_addresses = null;
|
||||||
|
|
||||||
|
|
||||||
/** Fired when the contact has changed in some way. */
|
/** Fired when the contact has changed in some way. */
|
||||||
|
|
@ -142,14 +141,15 @@ public class Application.Contact : Geary.BaseObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.display_name != other.display_name ||
|
if (this.display_name != other.display_name ||
|
||||||
this.email_addresses.size != other.email_addresses.size) {
|
this.email_addresses.get_n_items() != other.email_addresses.get_n_items()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Geary.RFC822.MailboxAddress this_addr in this.email_addresses) {
|
for (uint i = 0; i < this.email_addresses.get_n_items(); i++) {
|
||||||
|
var this_addr = (Geary.RFC822.MailboxAddress) this.email_addresses.get_item(i);
|
||||||
bool found = false;
|
bool found = false;
|
||||||
foreach (Geary.RFC822.MailboxAddress other_addr
|
for (uint j = 0; j < other.email_addresses.get_n_items(); j++) {
|
||||||
in other.email_addresses) {
|
var other_addr = (Geary.RFC822.MailboxAddress) other.email_addresses.get_item(j);
|
||||||
if (this_addr.equal_to(other_addr)) {
|
if (this_addr.equal_to(other_addr)) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
|
|
@ -187,8 +187,8 @@ public class Application.Contact : Geary.BaseObject {
|
||||||
Gee.Set<Folks.EmailFieldDetails> email_addresses =
|
Gee.Set<Folks.EmailFieldDetails> email_addresses =
|
||||||
new Gee.HashSet<Folks.EmailFieldDetails>();
|
new Gee.HashSet<Folks.EmailFieldDetails>();
|
||||||
GLib.Value email_value = GLib.Value(typeof(Gee.Set));
|
GLib.Value email_value = GLib.Value(typeof(Gee.Set));
|
||||||
foreach (Geary.RFC822.MailboxAddress addr
|
for (uint i = 0; i < this.email_addresses.get_n_items(); i++) {
|
||||||
in this.email_addresses) {
|
var addr = (Geary.RFC822.MailboxAddress) this.email_addresses.get_item(i);
|
||||||
email_addresses.add(
|
email_addresses.add(
|
||||||
new Folks.EmailFieldDetails(addr.address)
|
new Folks.EmailFieldDetails(addr.address)
|
||||||
);
|
);
|
||||||
|
|
@ -279,9 +279,9 @@ public class Application.Contact : Geary.BaseObject {
|
||||||
throws GLib.Error {
|
throws GLib.Error {
|
||||||
ContactStore? store = this.store;
|
ContactStore? store = this.store;
|
||||||
if (store != null) {
|
if (store != null) {
|
||||||
Gee.Collection<Geary.Contact> contacts =
|
var contacts = new Gee.LinkedList<Geary.Contact>();
|
||||||
new Gee.LinkedList<Geary.Contact>();
|
for (uint i = 0; i < this.email_addresses.get_n_items(); i++) {
|
||||||
foreach (Geary.RFC822.MailboxAddress mailbox in this.email_addresses) {
|
var mailbox = (Geary.RFC822.MailboxAddress) this.email_addresses.get_item(i);
|
||||||
Geary.Contact? contact = yield store.lookup_engine_contact(
|
Geary.Contact? contact = yield store.lookup_engine_contact(
|
||||||
mailbox, cancellable
|
mailbox, cancellable
|
||||||
);
|
);
|
||||||
|
|
@ -347,7 +347,9 @@ public class Application.Contact : Geary.BaseObject {
|
||||||
|
|
||||||
private void update_from_engine() {
|
private void update_from_engine() {
|
||||||
Geary.RFC822.MailboxAddress mailbox = this.engine.get_rfc822_address();
|
Geary.RFC822.MailboxAddress mailbox = this.engine.get_rfc822_address();
|
||||||
this._email_addresses = Geary.Collection.single(mailbox);
|
var addrs = new GLib.ListStore(typeof(Geary.RFC822.MailboxAddress));
|
||||||
|
addrs.append(mailbox);
|
||||||
|
this._email_addresses = addrs;
|
||||||
this.load_remote_resources = this.engine.flags.always_load_remote_images();
|
this.load_remote_resources = this.engine.flags.always_load_remote_images();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,21 +125,13 @@ internal class Application.Controller :
|
||||||
GLib.File config_dir = application.get_home_config_directory();
|
GLib.File config_dir = application.get_home_config_directory();
|
||||||
GLib.File data_dir = application.get_home_data_directory();
|
GLib.File data_dir = application.get_home_data_directory();
|
||||||
|
|
||||||
// This initializes the IconFactory, important to do before
|
|
||||||
// the actions are created (as they refer to some of Geary's
|
|
||||||
// custom icons)
|
|
||||||
IconFactory.init(application.get_resource_directory());
|
|
||||||
|
|
||||||
// Create DB upgrade dialog.
|
// Create DB upgrade dialog.
|
||||||
this.database_manager = new DatabaseManager(application);
|
this.database_manager = new DatabaseManager(application);
|
||||||
|
|
||||||
// Initialise WebKit and WebViews
|
// Initialise WebKit and WebViews
|
||||||
Components.WebView.init_web_context(
|
Components.WebView.init_web_context(
|
||||||
this.application.config,
|
this.application.config,
|
||||||
this.application.get_web_extensions_dir(),
|
this.application.get_web_extensions_dir()
|
||||||
this.application.get_home_cache_directory().get_child(
|
|
||||||
"web-resources"
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
Components.WebView.load_resources(config_dir);
|
Components.WebView.load_resources(config_dir);
|
||||||
Composer.WebView.load_resources();
|
Composer.WebView.load_resources();
|
||||||
|
|
@ -406,7 +398,7 @@ internal class Application.Controller :
|
||||||
// current window that is either a reply/forward for that
|
// current window that is either a reply/forward for that
|
||||||
// message, or there is a quote to insert into it.
|
// message, or there is a quote to insert into it.
|
||||||
foreach (var existing in this.composer_widgets) {
|
foreach (var existing in this.composer_widgets) {
|
||||||
if (existing.get_toplevel() == main &&
|
if (existing.get_root() == main &&
|
||||||
(existing.current_mode == INLINE ||
|
(existing.current_mode == INLINE ||
|
||||||
existing.current_mode == INLINE_COMPACT) &&
|
existing.current_mode == INLINE_COMPACT) &&
|
||||||
existing.sender_context == send_context &&
|
existing.sender_context == send_context &&
|
||||||
|
|
@ -957,6 +949,10 @@ internal class Application.Controller :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal GLib.File get_web_cache_dir() {
|
||||||
|
return this.application.get_home_cache_directory().get_child("web-resources");
|
||||||
|
}
|
||||||
|
|
||||||
/** Expunges removed accounts while the controller remains open. */
|
/** Expunges removed accounts while the controller remains open. */
|
||||||
internal async void expunge_accounts() {
|
internal async void expunge_accounts() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -1189,25 +1185,27 @@ internal class Application.Controller :
|
||||||
context.authentication_prompting = false;
|
context.authentication_prompting = false;
|
||||||
} else {
|
} else {
|
||||||
context.authentication_prompting = true;
|
context.authentication_prompting = true;
|
||||||
PasswordDialog password_dialog = new PasswordDialog(
|
var password_dialog = new PasswordDialog(
|
||||||
this.application.get_active_window(),
|
this.application.get_active_window(),
|
||||||
account,
|
account,
|
||||||
service,
|
service,
|
||||||
credentials
|
credentials
|
||||||
);
|
);
|
||||||
if (password_dialog.run()) {
|
bool remember;
|
||||||
|
var password = yield password_dialog.get_password(
|
||||||
|
this.application.get_active_window(),
|
||||||
|
out remember
|
||||||
|
);
|
||||||
|
if (password != null) {
|
||||||
// The update the credentials for the service that the
|
// The update the credentials for the service that the
|
||||||
// credentials actually came from
|
// credentials actually came from
|
||||||
Geary.ServiceInformation creds_service =
|
Geary.ServiceInformation creds_service =
|
||||||
(credentials == account.incoming.credentials)
|
(credentials == account.incoming.credentials)
|
||||||
? account.incoming
|
? account.incoming
|
||||||
: account.outgoing;
|
: account.outgoing;
|
||||||
creds_service.credentials = credentials.copy_with_token(
|
creds_service.credentials = credentials.copy_with_token(password);
|
||||||
password_dialog.password
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update the remember password pref if changed
|
// Update the remember password pref if changed
|
||||||
bool remember = password_dialog.remember_password;
|
|
||||||
if (creds_service.remember_password != remember) {
|
if (creds_service.remember_password != remember) {
|
||||||
creds_service.remember_password = remember;
|
creds_service.remember_password = remember;
|
||||||
account.changed();
|
account.changed();
|
||||||
|
|
@ -1301,37 +1299,35 @@ internal class Application.Controller :
|
||||||
|
|
||||||
// Returns true if the caller should try opening the account again
|
// Returns true if the caller should try opening the account again
|
||||||
private async bool account_database_error_async(Geary.Account account) {
|
private async bool account_database_error_async(Geary.Account account) {
|
||||||
bool retry = true;
|
|
||||||
|
|
||||||
// give the user two options: reset the Account local store, or exit Geary. A third
|
// give the user two options: reset the Account local store, or exit Geary. A third
|
||||||
// could be done to leave the Account in an unopened state, but we don't currently
|
// could be done to leave the Account in an unopened state, but we don't currently
|
||||||
// have provisions for that.
|
// have provisions for that.
|
||||||
QuestionDialog dialog = new QuestionDialog(
|
var dialog = new Adw.AlertDialog(
|
||||||
this.application.get_active_main_window(),
|
|
||||||
_("Unable to open the database for %s").printf(account.information.id),
|
_("Unable to open the database for %s").printf(account.information.id),
|
||||||
_("There was an error opening the local mail database for this account. This is possibly due to corruption of the database file in this directory:\n\n%s\n\nGeary can rebuild the database and re-synchronize with the server or exit.\n\nRebuilding the database will destroy all local email and its attachments. <b>The mail on the your server will not be affected.</b>")
|
null);
|
||||||
.printf(account.information.data_dir.get_path()),
|
dialog.format_body_markup(
|
||||||
_("_Rebuild"), _("E_xit"));
|
_("There was an error opening the local mail database for this account. This is possibly due to corruption of the database file in this directory:\n\n%s\n\nGeary can rebuild the database and re-synchronize with the server or exit.\n\nRebuilding the database will destroy all local email and its attachments. <b>The mail on the your server will not be affected.</b>"),
|
||||||
dialog.use_secondary_markup(true);
|
account.information.data_dir.get_path());
|
||||||
switch (dialog.run()) {
|
dialog.add_response("exit", _("E_xit"));
|
||||||
case Gtk.ResponseType.OK:
|
dialog.add_response("rebuild", _("_Rebuild"));
|
||||||
// don't use Cancellable because we don't want to interrupt this process
|
|
||||||
try {
|
|
||||||
yield account.rebuild_async();
|
|
||||||
} catch (Error err) {
|
|
||||||
ErrorDialog errdialog = new ErrorDialog(
|
|
||||||
this.application.get_active_main_window(),
|
|
||||||
_("Unable to rebuild database for “%s”").printf(account.information.id),
|
|
||||||
_("Error during rebuild:\n\n%s").printf(err.message));
|
|
||||||
errdialog.run();
|
|
||||||
|
|
||||||
retry = false;
|
string response = yield dialog.choose(this.application.get_active_main_window(), null);
|
||||||
}
|
if (response != "rebuild") {
|
||||||
break;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
// don't use Cancellable because we don't want to interrupt this process
|
||||||
retry = false;
|
bool retry = true;
|
||||||
break;
|
try {
|
||||||
|
yield account.rebuild_async();
|
||||||
|
} catch (Error err) {
|
||||||
|
var errdialog = new Adw.AlertDialog(
|
||||||
|
_("Unable to rebuild database for “%s”").printf(account.information.id),
|
||||||
|
_("Error during rebuild:\n\n%s").printf(err.message));
|
||||||
|
errdialog.add_css_class("error");
|
||||||
|
errdialog.present(this.application.get_active_main_window());
|
||||||
|
|
||||||
|
retry = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return retry;
|
return retry;
|
||||||
|
|
@ -1454,10 +1450,11 @@ internal class Application.Controller :
|
||||||
composer.present();
|
composer.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool check_open_composers() {
|
internal async bool check_open_composers() {
|
||||||
var do_quit = true;
|
var do_quit = true;
|
||||||
foreach (var composer in this.composer_widgets) {
|
foreach (var composer in this.composer_widgets) {
|
||||||
if (composer.conditional_close(true, true) == CANCELLED) {
|
var status = yield composer.conditional_close(true, true);
|
||||||
|
if (status == CANCELLED) {
|
||||||
do_quit = false;
|
do_quit = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1488,12 +1485,10 @@ internal class Application.Controller :
|
||||||
Geary.Email sent) {
|
Geary.Email sent) {
|
||||||
/// Translators: The label for an in-app notification.
|
/// Translators: The label for an in-app notification.
|
||||||
string message = _("Email sent");
|
string message = _("Email sent");
|
||||||
Components.InAppNotification notification =
|
var toast = new Adw.Toast(message);
|
||||||
new Components.InAppNotification(
|
toast.timeout = application.config.brief_notification_duration;
|
||||||
message, application.config.brief_notification_duration
|
|
||||||
);
|
|
||||||
foreach (MainWindow window in this.application.get_main_windows()) {
|
foreach (MainWindow window in this.application.get_main_windows()) {
|
||||||
window.add_notification(notification);
|
window.add_toast(toast);
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountContext? context = this.accounts.get(service.account);
|
AccountContext? context = this.accounts.get(service.account);
|
||||||
|
|
|
||||||
|
|
@ -63,16 +63,10 @@ internal class Application.DatabaseManager : Geary.BaseObject {
|
||||||
window.sensitive = false;
|
window.sensitive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var spinner = new Gtk.Spinner();
|
var box = new Gtk.Box(Gtk.Orientation.VERTICAL, 6);
|
||||||
spinner.set_size_request(45, 45);
|
box.append(new Adw.Spinner());
|
||||||
spinner.start();
|
|
||||||
|
|
||||||
var grid = new Gtk.Grid();
|
|
||||||
grid.orientation = VERTICAL;
|
|
||||||
grid.add(spinner);
|
|
||||||
/// Translators: Label for account database upgrade dialog
|
/// Translators: Label for account database upgrade dialog
|
||||||
grid.add(new Gtk.Label(_("Account update in progress")));
|
box.append(new Gtk.Label(_("Account update in progress")));
|
||||||
grid.show_all();
|
|
||||||
|
|
||||||
this.dialog = new Gtk.Dialog.with_buttons(
|
this.dialog = new Gtk.Dialog.with_buttons(
|
||||||
/// Translators: Window title for account database upgrade
|
/// Translators: Window title for account database upgrade
|
||||||
|
|
@ -81,15 +75,15 @@ internal class Application.DatabaseManager : Geary.BaseObject {
|
||||||
this.application.get_active_main_window(),
|
this.application.get_active_main_window(),
|
||||||
MODAL
|
MODAL
|
||||||
);
|
);
|
||||||
this.dialog.get_style_context().add_class("geary-upgrade");
|
this.dialog.add_css_class("geary-upgrade");
|
||||||
this.dialog.get_content_area().add(grid);
|
this.dialog.get_content_area().append(box);
|
||||||
this.dialog.deletable = false;
|
this.dialog.deletable = false;
|
||||||
this.dialog.delete_event.connect(this.on_delete_event);
|
this.dialog.close_request.connect(on_close_request);
|
||||||
this.dialog.close.connect(this.on_close);
|
this.dialog.close.connect(this.on_close);
|
||||||
this.dialog.show();
|
this.dialog.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_delete_event() {
|
private bool on_close_request() {
|
||||||
// Don't allow window to close until we're finished.
|
// Don't allow window to close until we're finished.
|
||||||
return !this.monitor.is_in_progress;
|
return !this.monitor.is_in_progress;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -115,9 +115,9 @@ internal class Application.NotificationPluginContext :
|
||||||
folder != null &&
|
folder != null &&
|
||||||
this.folder_information.has_key(folder) && (
|
this.folder_information.has_key(folder) && (
|
||||||
window == null ||
|
window == null ||
|
||||||
!window.has_toplevel_focus ||
|
!window.is_active ||
|
||||||
window.selected_folder != folder ||
|
window.selected_folder != folder ||
|
||||||
window.conversation_list_view.vadjustment.value > 0.0
|
window.conversation_list_view.scrolled_window.vadjustment.value > 0.0
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ public class Application.PluginManager : GLib.Object {
|
||||||
|
|
||||||
Geary.Folder? target = this.globals.folders.to_engine_folder(folder);
|
Geary.Folder? target = this.globals.folders.to_engine_folder(folder);
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
if (!main.prompt_empty_folder(target.used_as)) {
|
if (!yield main.prompt_empty_folder(target.used_as)) {
|
||||||
throw new Plugin.Error.PERMISSION_DENIED(
|
throw new Plugin.Error.PERMISSION_DENIED(
|
||||||
"Permission not granted"
|
"Permission not granted"
|
||||||
);
|
);
|
||||||
|
|
@ -419,7 +419,8 @@ public class Application.PluginManager : GLib.Object {
|
||||||
public void insert_text(string plain_text) {
|
public void insert_text(string plain_text) {
|
||||||
var entry = this.backing.focused_input_widget as Gtk.Entry;
|
var entry = this.backing.focused_input_widget as Gtk.Entry;
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
entry.insert_at_cursor(plain_text);
|
int position = entry.get_position();
|
||||||
|
entry.insert_text(plain_text, plain_text.length, ref position);
|
||||||
} else {
|
} else {
|
||||||
this.backing.editor.body.insert_text(plain_text);
|
this.backing.editor.body.insert_text(plain_text);
|
||||||
}
|
}
|
||||||
|
|
@ -477,7 +478,7 @@ public class Application.PluginManager : GLib.Object {
|
||||||
centre = new Gtk.Box(HORIZONTAL, 0);
|
centre = new Gtk.Box(HORIZONTAL, 0);
|
||||||
this.action_bar.set_center_widget(centre);
|
this.action_bar.set_center_widget(centre);
|
||||||
}
|
}
|
||||||
centre.add(widget);
|
centre.append(widget);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case END:
|
case END:
|
||||||
|
|
@ -487,7 +488,6 @@ public class Application.PluginManager : GLib.Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.action_bar.show_all();
|
|
||||||
this.backing.editor.add_action_bar(this.action_bar);
|
this.backing.editor.add_action_bar(this.action_bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -513,26 +513,24 @@ public class Application.PluginManager : GLib.Object {
|
||||||
if (item_type == typeof(Plugin.ActionBar.MenuItem)) {
|
if (item_type == typeof(Plugin.ActionBar.MenuItem)) {
|
||||||
var menu_item = item as Plugin.ActionBar.MenuItem;
|
var menu_item = item as Plugin.ActionBar.MenuItem;
|
||||||
|
|
||||||
var label = new Gtk.Box(HORIZONTAL, 6);
|
|
||||||
label.add(new Gtk.Label(menu_item.label));
|
|
||||||
label.add(new Gtk.Image.from_icon_name(
|
|
||||||
"pan-up-symbolic", Gtk.IconSize.BUTTON
|
|
||||||
));
|
|
||||||
|
|
||||||
var button = new Gtk.MenuButton();
|
var button = new Gtk.MenuButton();
|
||||||
button.direction = Gtk.ArrowType.UP;
|
button.direction = Gtk.ArrowType.UP;
|
||||||
button.use_popover = true;
|
|
||||||
button.menu_model = menu_item.menu;
|
button.menu_model = menu_item.menu;
|
||||||
button.add(label);
|
|
||||||
|
var content = new Adw.ButtonContent();
|
||||||
|
content.label = menu_item.label;
|
||||||
|
content.icon_name = "pan-up-symbolic";
|
||||||
|
|
||||||
|
button.child = content;
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
if (item_type == typeof(Plugin.ActionBar.GroupItem)) {
|
if (item_type == typeof(Plugin.ActionBar.GroupItem)) {
|
||||||
var group_items = item as Plugin.ActionBar.GroupItem;
|
var group_items = item as Plugin.ActionBar.GroupItem;
|
||||||
var box = new Gtk.Box(HORIZONTAL, 0);
|
var box = new Gtk.Box(HORIZONTAL, 0);
|
||||||
box.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED);
|
box.add_css_class("linked");
|
||||||
foreach (var group_item in group_items.get_items()) {
|
foreach (var group_item in group_items.get_items()) {
|
||||||
box.add(widget_for_item(group_item));
|
box.append(widget_for_item(group_item));
|
||||||
}
|
}
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
* shown will differ slightly based on which is selected.
|
* shown will differ slightly based on which is selected.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-attachment-pane.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/components-attachment-pane.ui")]
|
||||||
public class Components.AttachmentPane : Gtk.Grid {
|
public class Components.AttachmentPane : Gtk.Box {
|
||||||
|
|
||||||
|
|
||||||
private const string GROUP_NAME = "cap";
|
private const string GROUP_NAME = "cap";
|
||||||
|
|
@ -36,24 +36,6 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
{ ACTION_SELECT_ALL, on_select_all },
|
{ ACTION_SELECT_ALL, on_select_all },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// This exists purely to be able to set key bindings on it.
|
|
||||||
private class FlowBox : Gtk.FlowBox {
|
|
||||||
|
|
||||||
/** Keyboard action to open the currently selected attachments. */
|
|
||||||
[Signal (action=true)]
|
|
||||||
public signal void open_attachments();
|
|
||||||
|
|
||||||
/** Keyboard action to save the currently selected attachments. */
|
|
||||||
[Signal (action=true)]
|
|
||||||
public signal void save_attachments();
|
|
||||||
|
|
||||||
/** Keyboard action to remove the currently selected attachments. */
|
|
||||||
[Signal (action=true)]
|
|
||||||
public signal void remove_attachments();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Displays an attachment's icon and details
|
// Displays an attachment's icon and details
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-attachment-view.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/components-attachment-view.ui")]
|
||||||
private class View : Gtk.Grid {
|
private class View : Gtk.Grid {
|
||||||
|
|
@ -112,7 +94,7 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gdk.Pixbuf? pixbuf = null;
|
Gdk.Paintable? paintable = null;
|
||||||
|
|
||||||
// XXX We need to hook up to GtkWidget::style-set and
|
// XXX We need to hook up to GtkWidget::style-set and
|
||||||
// reload the icon when the theme changes.
|
// reload the icon when the theme changes.
|
||||||
|
|
@ -131,26 +113,20 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
Priority.DEFAULT,
|
Priority.DEFAULT,
|
||||||
load_cancelled
|
load_cancelled
|
||||||
);
|
);
|
||||||
pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(
|
var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(
|
||||||
stream, preview_size, preview_size, true, load_cancelled
|
stream, preview_size, preview_size, true, load_cancelled
|
||||||
);
|
);
|
||||||
pixbuf = pixbuf.apply_embedded_orientation();
|
pixbuf = pixbuf.apply_embedded_orientation();
|
||||||
|
paintable = Gdk.Texture.for_pixbuf(pixbuf);
|
||||||
} else {
|
} else {
|
||||||
// Load the icon for this mime type
|
// Load the icon for this mime type
|
||||||
GLib.Icon icon = GLib.ContentType.get_icon(
|
GLib.Icon icon = GLib.ContentType.get_icon(
|
||||||
this.gio_content_type
|
this.gio_content_type
|
||||||
);
|
);
|
||||||
Gtk.IconTheme theme = Gtk.IconTheme.get_default();
|
var theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default());
|
||||||
Gtk.IconLookupFlags flags = Gtk.IconLookupFlags.DIR_LTR;
|
paintable = theme.lookup_by_gicon(
|
||||||
if (get_direction() == Gtk.TextDirection.RTL) {
|
icon, ATTACHMENT_ICON_SIZE, window_scale, get_direction(), 0
|
||||||
flags = Gtk.IconLookupFlags.DIR_RTL;
|
|
||||||
}
|
|
||||||
Gtk.IconInfo? icon_info = theme.lookup_by_gicon_for_scale(
|
|
||||||
icon, ATTACHMENT_ICON_SIZE, window_scale, flags
|
|
||||||
);
|
);
|
||||||
if (icon_info != null) {
|
|
||||||
pixbuf = yield icon_info.load_icon_async(load_cancelled);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (GLib.Error error) {
|
} catch (GLib.Error error) {
|
||||||
debug("Failed to load icon for attachment '%s': %s",
|
debug("Failed to load icon for attachment '%s': %s",
|
||||||
|
|
@ -158,43 +134,14 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
error.message);
|
error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pixbuf != null) {
|
if (paintable != null) {
|
||||||
Cairo.Surface surface = Gdk.cairo_surface_create_from_pixbuf(
|
this.icon.paintable = paintable;
|
||||||
pixbuf, window_scale, get_window()
|
|
||||||
);
|
|
||||||
this.icon.set_from_surface(surface);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static construct {
|
|
||||||
// Set up custom keybindings
|
|
||||||
unowned Gtk.BindingSet bindings = Gtk.BindingSet.by_class(
|
|
||||||
(ObjectClass) typeof(FlowBox).class_ref()
|
|
||||||
);
|
|
||||||
|
|
||||||
Gtk.BindingEntry.add_signal(
|
|
||||||
bindings, Gdk.Key.O, Gdk.ModifierType.CONTROL_MASK, "open-attachments", 0
|
|
||||||
);
|
|
||||||
|
|
||||||
Gtk.BindingEntry.add_signal(
|
|
||||||
bindings, Gdk.Key.S, Gdk.ModifierType.CONTROL_MASK, "save-attachments", 0
|
|
||||||
);
|
|
||||||
|
|
||||||
Gtk.BindingEntry.add_signal(
|
|
||||||
bindings, Gdk.Key.BackSpace, 0, "remove-attachments", 0
|
|
||||||
);
|
|
||||||
Gtk.BindingEntry.add_signal(
|
|
||||||
bindings, Gdk.Key.Delete, 0, "remove-attachments", 0
|
|
||||||
);
|
|
||||||
Gtk.BindingEntry.add_signal(
|
|
||||||
bindings, Gdk.Key.KP_Delete, 0, "remove-attachments", 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Determines if this pane's contents can be modified. */
|
/** Determines if this pane's contents can be modified. */
|
||||||
public bool edit_mode { get; private set; }
|
public bool edit_mode { get; private set; }
|
||||||
|
|
||||||
|
|
@ -205,13 +152,13 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
|
|
||||||
private GLib.SimpleActionGroup actions = new GLib.SimpleActionGroup();
|
private GLib.SimpleActionGroup actions = new GLib.SimpleActionGroup();
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid attachments_container;
|
[GtkChild] private unowned Gtk.Box attachments_container;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Button save_button;
|
[GtkChild] private unowned Gtk.Button save_button;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Button remove_button;
|
[GtkChild] private unowned Gtk.Button remove_button;
|
||||||
|
|
||||||
private FlowBox attachments_view;
|
private Gtk.FlowBox attachments_view;
|
||||||
|
|
||||||
|
|
||||||
public AttachmentPane(bool edit_mode,
|
public AttachmentPane(bool edit_mode,
|
||||||
|
|
@ -225,22 +172,20 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
|
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
|
|
||||||
this.attachments_view = new FlowBox();
|
this.attachments_view = new Gtk.FlowBox();
|
||||||
this.attachments_view.open_attachments.connect(on_open_selected);
|
//XXX GTK4 need to check if shortcuts still work
|
||||||
this.attachments_view.remove_attachments.connect(on_remove_selected);
|
|
||||||
this.attachments_view.save_attachments.connect(on_save_selected);
|
|
||||||
this.attachments_view.child_activated.connect(on_child_activated);
|
this.attachments_view.child_activated.connect(on_child_activated);
|
||||||
this.attachments_view.selected_children_changed.connect(on_selected_changed);
|
this.attachments_view.selected_children_changed.connect(on_selected_changed);
|
||||||
this.attachments_view.button_press_event.connect(on_attachment_button_press);
|
Gtk.GestureClick gesture = new Gtk.GestureClick();
|
||||||
this.attachments_view.popup_menu.connect(on_attachment_popup_menu);
|
gesture.pressed.connect(on_attachment_pressed);
|
||||||
|
this.attachments_view.add_controller(gesture);
|
||||||
this.attachments_view.activate_on_single_click = false;
|
this.attachments_view.activate_on_single_click = false;
|
||||||
this.attachments_view.max_children_per_line = 3;
|
this.attachments_view.max_children_per_line = 3;
|
||||||
this.attachments_view.column_spacing = 6;
|
this.attachments_view.column_spacing = 6;
|
||||||
this.attachments_view.row_spacing = 6;
|
this.attachments_view.row_spacing = 6;
|
||||||
this.attachments_view.selection_mode = Gtk.SelectionMode.MULTIPLE;
|
this.attachments_view.selection_mode = Gtk.SelectionMode.MULTIPLE;
|
||||||
this.attachments_view.hexpand = true;
|
this.attachments_view.hexpand = true;
|
||||||
this.attachments_view.show();
|
this.attachments_container.append(this.attachments_view);
|
||||||
this.attachments_container.add(this.attachments_view);
|
|
||||||
|
|
||||||
this.actions.add_action_entries(action_entries, this);
|
this.actions.add_action_entries(action_entries, this);
|
||||||
insert_action_group(GROUP_NAME, this.actions);
|
insert_action_group(GROUP_NAME, this.actions);
|
||||||
|
|
@ -249,7 +194,7 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
public void add_attachment(Geary.Attachment attachment,
|
public void add_attachment(Geary.Attachment attachment,
|
||||||
GLib.Cancellable? cancellable) {
|
GLib.Cancellable? cancellable) {
|
||||||
View view = new View(attachment);
|
View view = new View(attachment);
|
||||||
this.attachments_view.add(view);
|
this.attachments_view.append(view);
|
||||||
this.attachments.add(attachment);
|
this.attachments.add(attachment);
|
||||||
view.load_icon.begin(cancellable);
|
view.load_icon.begin(cancellable);
|
||||||
|
|
||||||
|
|
@ -257,7 +202,7 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void open_attachment(Geary.Attachment attachment) {
|
public void open_attachment(Geary.Attachment attachment) {
|
||||||
open_attachments(Geary.Collection.single(attachment));
|
open_attachments.begin(Geary.Collection.single(attachment));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save_attachment(Geary.Attachment attachment) {
|
public void save_attachment(Geary.Attachment attachment) {
|
||||||
|
|
@ -270,12 +215,15 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
|
|
||||||
public void remove_attachment(Geary.Attachment attachment) {
|
public void remove_attachment(Geary.Attachment attachment) {
|
||||||
this.attachments.remove(attachment);
|
this.attachments.remove(attachment);
|
||||||
this.attachments_view.foreach(child => {
|
for (int i = 0; true; i++) {
|
||||||
Gtk.FlowBoxChild flow_child = (Gtk.FlowBoxChild) child;
|
unowned var flow_child = this.attachments_view.get_child_at_index(i);
|
||||||
if (((View) flow_child.get_child()).attachment == attachment) {
|
if (flow_child == null)
|
||||||
this.attachments_view.remove(child);
|
break;
|
||||||
}
|
if (((View) flow_child.get_child()).attachment == attachment) {
|
||||||
});
|
this.attachments_view.remove(flow_child);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool save_all() {
|
public bool save_all() {
|
||||||
|
|
@ -317,7 +265,7 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
var selected = get_selected_attachments();
|
var selected = get_selected_attachments();
|
||||||
if (!selected.is_empty) {
|
if (!selected.is_empty) {
|
||||||
open_attachments(selected);
|
open_attachments.begin(selected);
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -362,29 +310,36 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
set_action_enabled(ACTION_SELECT_ALL, len < this.attachments.size);
|
set_action_enabled(ACTION_SELECT_ALL, len < this.attachments.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void open_attachments(Gee.Collection<Geary.Attachment> attachments) {
|
private async void open_attachments(Gee.Collection<Geary.Attachment> attachments) {
|
||||||
var main = this.get_toplevel() as Application.MainWindow;
|
var main = get_root() as Application.MainWindow;
|
||||||
if (main != null) {
|
if (main == null)
|
||||||
Application.Client app = main.application;
|
return;
|
||||||
bool confirmed = true;
|
|
||||||
if (app.config.ask_open_attachment) {
|
|
||||||
QuestionDialog ask_to_open = new QuestionDialog.with_checkbox(
|
|
||||||
main,
|
|
||||||
_("Are you sure you want to open these attachments?"),
|
|
||||||
_("Attachments may cause damage to your system if opened. Only open files from trusted sources."),
|
|
||||||
Stock._OPEN_BUTTON, Stock._CANCEL, _("Don’t _ask me again"), false
|
|
||||||
);
|
|
||||||
if (ask_to_open.run() == Gtk.ResponseType.OK) {
|
|
||||||
app.config.ask_open_attachment = !ask_to_open.is_checked;
|
|
||||||
} else {
|
|
||||||
confirmed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirmed) {
|
Application.Client app = main.application;
|
||||||
foreach (var attachment in attachments) {
|
if (app.config.ask_open_attachment) {
|
||||||
app.show_uri.begin(attachment.file.get_uri());
|
var dialog = new Adw.AlertDialog(
|
||||||
}
|
_("Are you sure you want to open these attachments?"),
|
||||||
|
_("Attachments may cause damage to your system if opened. Only open files from trusted sources."));
|
||||||
|
dialog.add_response("cancel", _("_Cancel"));
|
||||||
|
dialog.add_response("open", _("_Open"));
|
||||||
|
dialog.default_response = "open";
|
||||||
|
dialog.close_response = "cancel";
|
||||||
|
|
||||||
|
var check = new Adw.SwitchRow();
|
||||||
|
check.title = _("Don’t _ask me again");
|
||||||
|
|
||||||
|
string response = yield dialog.choose(main, null);
|
||||||
|
if (response != "open")
|
||||||
|
return;
|
||||||
|
app.config.ask_open_attachment = !check.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var attachment in attachments) {
|
||||||
|
var launcher = new Gtk.FileLauncher(attachment.file);
|
||||||
|
try {
|
||||||
|
yield launcher.launch(get_native() as Gtk.Window, null);
|
||||||
|
} catch (GLib.Error err) {
|
||||||
|
warning("Couldn't show attachment: %s", err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -396,7 +351,7 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void show_popup(View view, Gdk.EventButton? event) {
|
private void show_popup(View view, Gdk.Rectangle? rect) {
|
||||||
Gtk.Builder builder = new Gtk.Builder.from_resource(
|
Gtk.Builder builder = new Gtk.Builder.from_resource(
|
||||||
"/org/gnome/Geary/components-attachment-pane-menus.ui"
|
"/org/gnome/Geary/components-attachment-pane-menus.ui"
|
||||||
);
|
);
|
||||||
|
|
@ -410,21 +365,20 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
GROUP_NAME,
|
GROUP_NAME,
|
||||||
targets
|
targets
|
||||||
);
|
);
|
||||||
Gtk.Menu menu = new Gtk.Menu.from_model(model);
|
Gtk.PopoverMenu menu = new Gtk.PopoverMenu.from_model(model);
|
||||||
menu.attach_to_widget(view, null);
|
menu.set_parent(view);
|
||||||
if (event != null) {
|
if (rect != null) {
|
||||||
menu.popup_at_pointer(event);
|
menu.set_pointing_to(rect);
|
||||||
} else {
|
|
||||||
menu.popup_at_widget(view, CENTER, SOUTH, null);
|
|
||||||
}
|
}
|
||||||
|
menu.popup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beep() {
|
private void beep() {
|
||||||
Gtk.Widget? toplevel = get_toplevel();
|
Gtk.Native? native = get_native();
|
||||||
if (toplevel == null) {
|
if (native == null) {
|
||||||
Gdk.Window? window = toplevel.get_window();
|
Gdk.Surface? surface = native.get_surface();
|
||||||
if (window != null) {
|
if (surface != null) {
|
||||||
window.beep();
|
surface.beep();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -486,32 +440,19 @@ public class Components.AttachmentPane : Gtk.Grid {
|
||||||
update_actions();
|
update_actions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_attachment_popup_menu(Gtk.Widget widget) {
|
private void on_attachment_pressed(Gtk.GestureClick gesture, int n_press, double x, double y) {
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
var event = gesture.get_current_event();
|
||||||
Gtk.Window parent = get_toplevel() as Gtk.Window;
|
if (event.triggers_context_menu()) {
|
||||||
if (parent != null) {
|
|
||||||
Gtk.FlowBoxChild? focus = parent.get_focus() as Gtk.FlowBoxChild;
|
|
||||||
if (focus != null && focus.parent == this.attachments_view) {
|
|
||||||
show_popup((View) focus.get_child(), null);
|
|
||||||
ret = Gdk.EVENT_STOP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool on_attachment_button_press(Gtk.Widget widget,
|
|
||||||
Gdk.EventButton event) {
|
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
|
||||||
if (event.triggers_context_menu()) {
|
|
||||||
Gtk.FlowBoxChild? child = this.attachments_view.get_child_at_pos(
|
Gtk.FlowBoxChild? child = this.attachments_view.get_child_at_pos(
|
||||||
(int) event.x,
|
(int) x,
|
||||||
(int) event.y
|
(int) y
|
||||||
);
|
);
|
||||||
if (child != null) {
|
if (child != null) {
|
||||||
show_popup((View) child.get_child(), event);
|
Gdk.Rectangle rect = { (int) x, (int) y, 1, 1 };
|
||||||
ret = Gdk.EVENT_STOP;
|
show_popup((View) child.get_child(), rect);
|
||||||
|
//XXX GTK4?
|
||||||
|
// ret = Gdk.EVENT_STOP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,25 @@
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-conversation-actions.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/components-conversation-actions.ui")]
|
||||||
public class Components.ConversationActions : Gtk.Box {
|
public class Components.ConversationActions : Gtk.Box {
|
||||||
|
|
||||||
public bool show_conversation_actions { get; construct; }
|
public bool show_conversation_actions {
|
||||||
|
get { return this.action_buttons.visible; }
|
||||||
|
set {
|
||||||
|
if (this.action_buttons.visible == value)
|
||||||
|
return;
|
||||||
|
this.action_buttons.visible = value;
|
||||||
|
notify_property("show-conversation-actions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool show_response_actions { get; construct; }
|
public bool show_response_actions {
|
||||||
|
get { return this.response_buttons.visible; }
|
||||||
|
set {
|
||||||
|
if (this.response_buttons.visible == value)
|
||||||
|
return;
|
||||||
|
this.response_buttons.visible = value;
|
||||||
|
notify_property("show-conversation-actions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool pack_justified { get; construct; }
|
public bool pack_justified { get; construct; }
|
||||||
|
|
||||||
|
|
@ -43,16 +59,12 @@ public class Components.ConversationActions : Gtk.Box {
|
||||||
[GtkChild] private unowned Gtk.MenuButton mark_message_button { get; }
|
[GtkChild] private unowned Gtk.MenuButton mark_message_button { get; }
|
||||||
[GtkChild] private unowned Gtk.MenuButton copy_message_button { get; }
|
[GtkChild] private unowned Gtk.MenuButton copy_message_button { get; }
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Box action_buttons { get; }
|
[GtkChild] private unowned Gtk.Box action_buttons;
|
||||||
[GtkChild] private unowned Gtk.Button archive_button;
|
[GtkChild] private unowned Gtk.Button archive_button;
|
||||||
[GtkChild] private unowned Gtk.Button trash_delete_button;
|
[GtkChild] private unowned Gtk.Button trash_delete_button;
|
||||||
|
|
||||||
private bool show_trash_button = true;
|
private bool show_trash_button = true;
|
||||||
|
|
||||||
// Load these at construction time
|
|
||||||
private Gtk.Image trash_image = new Gtk.Image.from_icon_name("user-trash-symbolic", Gtk.IconSize.MENU);
|
|
||||||
private Gtk.Image delete_image = new Gtk.Image.from_icon_name("edit-delete-symbolic", Gtk.IconSize.MENU);
|
|
||||||
|
|
||||||
static construct {
|
static construct {
|
||||||
set_css_name("components-conversation-actions");
|
set_css_name("components-conversation-actions");
|
||||||
}
|
}
|
||||||
|
|
@ -69,16 +81,13 @@ public class Components.ConversationActions : Gtk.Box {
|
||||||
|
|
||||||
this.notify["selected-conversations"].connect(() => update_conversation_buttons());
|
this.notify["selected-conversations"].connect(() => update_conversation_buttons());
|
||||||
this.notify["service-provider"].connect(() => update_conversation_buttons());
|
this.notify["service-provider"].connect(() => update_conversation_buttons());
|
||||||
this.mark_message_button.popover = new Gtk.Popover.from_model(null, mark_menu);
|
this.mark_message_button.menu_model = mark_menu;
|
||||||
|
|
||||||
this.mark_message_button.toggled.connect((button) => {
|
this.mark_message_button.activate.connect((button) => {
|
||||||
if (button.active)
|
if (button.active)
|
||||||
mark_message_button_toggled();
|
mark_message_button_toggled();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.response_buttons.set_visible(this.show_response_actions);
|
|
||||||
this.action_buttons.set_visible(this.show_conversation_actions);
|
|
||||||
|
|
||||||
if (this.pack_justified) {
|
if (this.pack_justified) {
|
||||||
this.action_buttons.hexpand = true;
|
this.action_buttons.hexpand = true;
|
||||||
this.action_buttons.halign = END;
|
this.action_buttons.halign = END;
|
||||||
|
|
@ -102,14 +111,11 @@ public class Components.ConversationActions : Gtk.Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void show_copy_menu() {
|
public void show_copy_menu() {
|
||||||
this.copy_message_button.clicked();
|
this.copy_message_button.active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_mark_inverted() {
|
public void set_mark_inverted() {
|
||||||
var image = new Gtk.Image.from_icon_name(
|
this.mark_message_button.icon_name = "pan-up-symbolic";
|
||||||
"pan-up-symbolic", Gtk.IconSize.BUTTON
|
|
||||||
);
|
|
||||||
this.mark_message_button.set_image(image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update_trash_button(bool show_trash) {
|
public void update_trash_button(bool show_trash) {
|
||||||
|
|
@ -142,10 +148,7 @@ public class Components.ConversationActions : Gtk.Box {
|
||||||
"Add label to conversations",
|
"Add label to conversations",
|
||||||
this.selected_conversations
|
this.selected_conversations
|
||||||
);
|
);
|
||||||
this.copy_message_button.set_image(
|
this.copy_message_button.icon_name = "tag-symbolic";
|
||||||
new Gtk.Image.from_icon_name(
|
|
||||||
"tag-symbolic", Gtk.IconSize.BUTTON)
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.copy_message_button.tooltip_text = ngettext(
|
this.copy_message_button.tooltip_text = ngettext(
|
||||||
|
|
@ -153,10 +156,7 @@ public class Components.ConversationActions : Gtk.Box {
|
||||||
"Copy conversations",
|
"Copy conversations",
|
||||||
this.selected_conversations
|
this.selected_conversations
|
||||||
);
|
);
|
||||||
this.copy_message_button.set_image(
|
this.copy_message_button.icon_name = "folder-symbolic";
|
||||||
new Gtk.Image.from_icon_name(
|
|
||||||
"folder-symbolic", Gtk.IconSize.BUTTON)
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +165,7 @@ public class Components.ConversationActions : Gtk.Box {
|
||||||
this.trash_delete_button.action_name = Action.Window.prefix(
|
this.trash_delete_button.action_name = Action.Window.prefix(
|
||||||
Application.MainWindow.ACTION_TRASH_CONVERSATION
|
Application.MainWindow.ACTION_TRASH_CONVERSATION
|
||||||
);
|
);
|
||||||
this.trash_delete_button.image = trash_image;
|
this.trash_delete_button.icon_name = "user-trash-symbolic";
|
||||||
this.trash_delete_button.tooltip_text = ngettext(
|
this.trash_delete_button.tooltip_text = ngettext(
|
||||||
"Move conversation to Trash",
|
"Move conversation to Trash",
|
||||||
"Move conversations to Trash",
|
"Move conversations to Trash",
|
||||||
|
|
@ -175,7 +175,7 @@ public class Components.ConversationActions : Gtk.Box {
|
||||||
this.trash_delete_button.action_name = Action.Window.prefix(
|
this.trash_delete_button.action_name = Action.Window.prefix(
|
||||||
Application.MainWindow.ACTION_DELETE_CONVERSATION
|
Application.MainWindow.ACTION_DELETE_CONVERSATION
|
||||||
);
|
);
|
||||||
this.trash_delete_button.image = delete_image;
|
this.trash_delete_button.icon_name = "edit-delete-symbolic";
|
||||||
this.trash_delete_button.tooltip_text = ngettext(
|
this.trash_delete_button.tooltip_text = ngettext(
|
||||||
"Delete conversation",
|
"Delete conversation",
|
||||||
"Delete conversations",
|
"Delete conversations",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides per-GTK Entry undo and redo using a command stack.
|
* Provides per-GTK Editable undo and redo using a command stack.
|
||||||
*/
|
*/
|
||||||
public class Components.EntryUndo : Geary.BaseObject {
|
public class Components.EntryUndo : Geary.BaseObject {
|
||||||
|
|
||||||
|
|
@ -84,13 +84,13 @@ public class Components.EntryUndo : Geary.BaseObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void do_insert(Gtk.Entry target) {
|
private void do_insert(Gtk.Editable target) {
|
||||||
int position = this.position;
|
int position = this.position;
|
||||||
target.insert_text(this.text, -1, ref position);
|
target.insert_text(this.text, -1, ref position);
|
||||||
target.set_position(position);
|
target.set_position(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void do_delete(Gtk.Entry target) {
|
private void do_delete(Gtk.Editable target) {
|
||||||
target.delete_text(
|
target.delete_text(
|
||||||
this.position, this.position + this.text.char_count()
|
this.position, this.position + this.text.char_count()
|
||||||
);
|
);
|
||||||
|
|
@ -100,7 +100,7 @@ public class Components.EntryUndo : Geary.BaseObject {
|
||||||
|
|
||||||
|
|
||||||
/** The entry being managed */
|
/** The entry being managed */
|
||||||
public Gtk.Entry target { get; private set; }
|
public Gtk.Editable target { get; private set; }
|
||||||
|
|
||||||
private Application.CommandStack commands;
|
private Application.CommandStack commands;
|
||||||
private EditType last_edit = NONE;
|
private EditType last_edit = NONE;
|
||||||
|
|
@ -113,7 +113,8 @@ public class Components.EntryUndo : Geary.BaseObject {
|
||||||
private GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
private GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
||||||
|
|
||||||
|
|
||||||
public EntryUndo(Gtk.Entry target) {
|
// XXX GTK4 maybe rename this to EditableUndo?
|
||||||
|
public EntryUndo(Gtk.Editable target) {
|
||||||
this.edit_actions.add_action_entries(EDIT_ACTIONS, this);
|
this.edit_actions.add_action_entries(EDIT_ACTIONS, this);
|
||||||
|
|
||||||
this.target = target;
|
this.target = target;
|
||||||
|
|
@ -157,7 +158,7 @@ public class Components.EntryUndo : Geary.BaseObject {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
while (!complete) {
|
while (!complete) {
|
||||||
Gtk.main_iteration();
|
MainContext.default().iteration(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +180,7 @@ public class Components.EntryUndo : Geary.BaseObject {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
while (!complete) {
|
while (!complete) {
|
||||||
Gtk.main_iteration();
|
MainContext.default().iteration(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,7 +202,7 @@ public class Components.EntryUndo : Geary.BaseObject {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
while (!complete) {
|
while (!complete) {
|
||||||
Gtk.main_iteration();
|
MainContext.default().iteration(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,7 +299,7 @@ public class Components.EntryUndo : Geary.BaseObject {
|
||||||
private void on_deleted(int start, int end) {
|
private void on_deleted(int start, int end) {
|
||||||
if (this.events_enabled) {
|
if (this.events_enabled) {
|
||||||
// Normalise value of end to be something useful if needed
|
// Normalise value of end to be something useful if needed
|
||||||
string text = this.target.buffer.get_text();
|
string text = this.target.text;
|
||||||
if (end < 0) {
|
if (end < 0) {
|
||||||
end = text.char_count();
|
end = text.char_count();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright © 2017 Software Freedom Conservancy Inc.
|
|
||||||
* Copyright © 2021 Michael Gratton <mike@vee.net>
|
|
||||||
* Copyright © 2022 Cédric Bellegarde <cedric.bellegarde@adishatz.org>
|
|
||||||
*
|
|
||||||
* This software is licensed under the GNU Lesser General Public License
|
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Application HeaderBar
|
|
||||||
*
|
|
||||||
* @see Application.MainWindow
|
|
||||||
*/
|
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-headerbar-application.ui")]
|
|
||||||
public class Components.ApplicationHeaderBar : Hdy.HeaderBar {
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.MenuButton app_menu_button;
|
|
||||||
[GtkChild] public unowned MonitoredSpinner spinner;
|
|
||||||
|
|
||||||
|
|
||||||
construct {
|
|
||||||
Gtk.Builder builder = new Gtk.Builder.from_resource("/org/gnome/Geary/components-menu-application.ui");
|
|
||||||
MenuModel app_menu = (MenuModel) builder.get_object("app_menu");
|
|
||||||
|
|
||||||
this.app_menu_button.popover = new Gtk.Popover.from_model(null, app_menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void show_app_menu() {
|
|
||||||
this.app_menu_button.clicked();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright © 2017 Software Freedom Conservancy Inc.
|
|
||||||
* Copyright © 2021 Michael Gratton <mike@vee.net>
|
|
||||||
* Copyright © 2022 Cédric Bellegarde <cedric.bellegarde@adishatz.org>
|
|
||||||
*
|
|
||||||
* This software is licensed under the GNU Lesser General Public License
|
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The conversation list headerbar.
|
|
||||||
*
|
|
||||||
* @see Application.MainWindow
|
|
||||||
*/
|
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-headerbar-conversation-list.ui")]
|
|
||||||
public class Components.ConversationListHeaderBar : Hdy.HeaderBar {
|
|
||||||
|
|
||||||
public string account { get; set; }
|
|
||||||
public string folder { get; set; }
|
|
||||||
public bool search_open { get; set; default = false; }
|
|
||||||
public bool selection_open { get; set; default = false; }
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ToggleButton search_button;
|
|
||||||
[GtkChild] private unowned Gtk.ToggleButton selection_button;
|
|
||||||
[GtkChild] public unowned Gtk.Button back_button;
|
|
||||||
|
|
||||||
|
|
||||||
construct {
|
|
||||||
this.bind_property("account", this, "title", BindingFlags.SYNC_CREATE);
|
|
||||||
this.bind_property("folder", this, "subtitle", BindingFlags.SYNC_CREATE);
|
|
||||||
|
|
||||||
this.bind_property(
|
|
||||||
"search-open",
|
|
||||||
this.search_button, "active",
|
|
||||||
SYNC_CREATE | BIDIRECTIONAL
|
|
||||||
);
|
|
||||||
this.bind_property(
|
|
||||||
"selection-open",
|
|
||||||
this.selection_button, "active",
|
|
||||||
SYNC_CREATE | BIDIRECTIONAL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -14,25 +14,23 @@
|
||||||
* @see Application.MainWindow
|
* @see Application.MainWindow
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-headerbar-conversation.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/components-headerbar-conversation.ui")]
|
||||||
public class Components.ConversationHeaderBar : Gtk.Bin {
|
public class Components.ConversationHeaderBar : Adw.Bin {
|
||||||
|
|
||||||
public bool find_open { get; set; default = false; }
|
public bool find_open { get; set; default = false; }
|
||||||
|
|
||||||
public ConversationActions shown_actions {
|
public bool compact { get; set; default = false; }
|
||||||
get {
|
|
||||||
return (ConversationActions) this.actions_squeezer.visible_child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[GtkChild] private unowned Hdy.Squeezer actions_squeezer;
|
[GtkChild] public unowned ConversationActions left_actions;
|
||||||
[GtkChild] public unowned ConversationActions full_actions;
|
[GtkChild] public unowned ConversationActions right_actions;
|
||||||
[GtkChild] public unowned ConversationActions compact_actions;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ToggleButton find_button;
|
[GtkChild] private unowned Gtk.ToggleButton find_button;
|
||||||
[GtkChild] public unowned Gtk.Button back_button;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Hdy.HeaderBar conversation_header;
|
[GtkChild] private unowned Adw.HeaderBar conversation_header;
|
||||||
|
// Keep a strong ref when it's temporarily removed
|
||||||
|
private Adw.HeaderBar? _conversation_header = null;
|
||||||
|
|
||||||
|
//XXX GTK4 need to figure out close buttons
|
||||||
|
#if 0
|
||||||
public bool show_close_button {
|
public bool show_close_button {
|
||||||
get {
|
get {
|
||||||
return this.conversation_header.show_close_button;
|
return this.conversation_header.show_close_button;
|
||||||
|
|
@ -41,12 +39,9 @@ public class Components.ConversationHeaderBar : Gtk.Bin {
|
||||||
this.conversation_header.show_close_button = value;
|
this.conversation_header.show_close_button = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
this.actions_squeezer.notify["visible-child"].connect_after(
|
|
||||||
() => { notify_property("shown-actions"); }
|
|
||||||
);
|
|
||||||
|
|
||||||
this.bind_property(
|
this.bind_property(
|
||||||
"find-open",
|
"find-open",
|
||||||
this.find_button, "active",
|
this.find_button, "active",
|
||||||
|
|
@ -54,17 +49,24 @@ public class Components.ConversationHeaderBar : Gtk.Bin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_conversation_header(Hdy.HeaderBar header) {
|
public override void dispose() {
|
||||||
remove(this.conversation_header);
|
this._conversation_header = null;
|
||||||
header.hexpand = true;
|
base.dispose();
|
||||||
header.show_close_button = this.conversation_header.show_close_button;
|
|
||||||
add(header);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove_conversation_header(Hdy.HeaderBar header) {
|
public void set_conversation_header(Adw.HeaderBar header)
|
||||||
remove(header);
|
requires (header.parent == null) {
|
||||||
this.conversation_header.show_close_button = header.show_close_button;
|
this._conversation_header = null;
|
||||||
add(this.conversation_header);
|
header.hexpand = true;
|
||||||
|
//XXX GTK4 need to figure out close buttons
|
||||||
|
// header.show_close_button = this.conversation_header.show_close_button;
|
||||||
|
this.child = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove_conversation_header(Adw.HeaderBar header) {
|
||||||
|
//XXX GTK4 need to figure out close buttons
|
||||||
|
// this.conversation_header.show_close_button = header.show_close_button;
|
||||||
|
this.child = this.conversation_header;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_find_sensitive(bool is_sensitive) {
|
public void set_find_sensitive(bool is_sensitive) {
|
||||||
|
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
/* Copyright 2017 Software Freedom Conservancy Inc.
|
|
||||||
*
|
|
||||||
* This software is licensed under the GNU Lesser General Public License
|
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an in-app notification.
|
|
||||||
*
|
|
||||||
* Following the GNOME HIG, it should only contain a label and maybe a button.
|
|
||||||
* Looks like libadwaita toast, remove this when porting toward GTK4
|
|
||||||
*/
|
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-in-app-notification.ui")]
|
|
||||||
public class Components.InAppNotification : Gtk.Revealer {
|
|
||||||
|
|
||||||
/** Default length of time to show the notification. */
|
|
||||||
public const uint DEFAULT_DURATION = 5;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Label message_label;
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Button action_button;
|
|
||||||
|
|
||||||
private uint duration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an in-app notification.
|
|
||||||
*
|
|
||||||
* @param message The message that should be displayed.
|
|
||||||
* @param duration The length of time to show the notification,
|
|
||||||
* in seconds.
|
|
||||||
*/
|
|
||||||
public InAppNotification(string message,
|
|
||||||
uint duration = DEFAULT_DURATION) {
|
|
||||||
this.transition_type = Gtk.RevealerTransitionType.CROSSFADE;
|
|
||||||
this.message_label.label = message;
|
|
||||||
this.duration = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a button for the notification.
|
|
||||||
*/
|
|
||||||
public void set_button(string label, string action_name) {
|
|
||||||
this.action_button.visible = true;
|
|
||||||
this.action_button.label = label;
|
|
||||||
this.action_button.action_name = action_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void show() {
|
|
||||||
if (this.duration > 0) {
|
|
||||||
base.show();
|
|
||||||
this.reveal_child = true;
|
|
||||||
|
|
||||||
// Close after the given amount of time
|
|
||||||
GLib.Timeout.add_seconds(
|
|
||||||
this.duration, () => { close(); return false; }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the in-app notification.
|
|
||||||
*/
|
|
||||||
[GtkCallback]
|
|
||||||
public void close() {
|
|
||||||
// Allows for the disappearing transition
|
|
||||||
this.reveal_child = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the notification gets destroyed after closing.
|
|
||||||
[GtkCallback]
|
|
||||||
private void on_child_revealed(Object src, ParamSpec p) {
|
|
||||||
if (!this.child_revealed)
|
|
||||||
destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -158,7 +158,7 @@ public class Components.InfoBarStack : Gtk.Frame, Geary.BaseInterface {
|
||||||
|
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
get_style_context().add_class("geary-info-bar-stack");
|
add_css_class("geary-info-bar-stack");
|
||||||
update_queue_type();
|
update_queue_type();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,7 +174,7 @@ public class Components.InfoBarStack : Gtk.Frame, Geary.BaseInterface {
|
||||||
* stack constructed, the info bar may or may not be revealed
|
* stack constructed, the info bar may or may not be revealed
|
||||||
* immediately.
|
* immediately.
|
||||||
*/
|
*/
|
||||||
public new void add(Components.InfoBar to_add) {
|
public void add(Components.InfoBar to_add) {
|
||||||
if (this.available.offer(to_add)) {
|
if (this.available.offer(to_add)) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +187,7 @@ public class Components.InfoBarStack : Gtk.Frame, Geary.BaseInterface {
|
||||||
* replaced with the next info bar added. If the only info bar
|
* replaced with the next info bar added. If the only info bar
|
||||||
* present is removed, the stack also hides itself.
|
* present is removed, the stack also hides itself.
|
||||||
*/
|
*/
|
||||||
public new void remove(Components.InfoBar to_remove) {
|
public void remove(Components.InfoBar to_remove) {
|
||||||
if (this.available.remove(to_remove)) {
|
if (this.available.remove(to_remove)) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +210,7 @@ public class Components.InfoBarStack : Gtk.Frame, Geary.BaseInterface {
|
||||||
// Not currently showing an info bar but have one to show,
|
// Not currently showing an info bar but have one to show,
|
||||||
// so show it
|
// so show it
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
base.add(next);
|
this.child = next;
|
||||||
next.revealed = true;
|
next.revealed = true;
|
||||||
} else if (current != null && next != current) {
|
} else if (current != null && next != current) {
|
||||||
// Currently showing an info bar but should be showing
|
// Currently showing an info bar but should be showing
|
||||||
|
|
@ -241,7 +241,7 @@ public class Components.InfoBarStack : Gtk.Frame, Geary.BaseInterface {
|
||||||
private void on_revealed(GLib.Object target, GLib.ParamSpec param) {
|
private void on_revealed(GLib.Object target, GLib.ParamSpec param) {
|
||||||
var info_bar = target as Components.InfoBar;
|
var info_bar = target as Components.InfoBar;
|
||||||
target.notify["revealed"].disconnect(on_revealed);
|
target.notify["revealed"].disconnect(on_revealed);
|
||||||
base.remove(info_bar);
|
this.child = null;
|
||||||
remove(info_bar);
|
remove(info_bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,16 +97,13 @@ public class Components.InfoBar : Gtk.Box {
|
||||||
this.description.tooltip_text = description;
|
this.description.tooltip_text = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
var container = new Gtk.Grid();
|
var container = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
|
||||||
container.orientation = VERTICAL;
|
container.valign = Gtk.Align.CENTER;
|
||||||
container.valign = CENTER;
|
container.append(this.status);
|
||||||
container.add(this.status);
|
|
||||||
if (this.description != null) {
|
if (this.description != null) {
|
||||||
container.add(this.description);
|
container.append(this.description);
|
||||||
}
|
}
|
||||||
get_content_area().add(container);
|
get_content_area().append(container);
|
||||||
|
|
||||||
show_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfoBar.for_plugin(Plugin.InfoBar plugin,
|
public InfoBar.for_plugin(Plugin.InfoBar plugin,
|
||||||
|
|
@ -143,14 +140,12 @@ public class Components.InfoBar : Gtk.Box {
|
||||||
var secondaries = plugin.secondary_buttons.bidir_list_iterator();
|
var secondaries = plugin.secondary_buttons.bidir_list_iterator();
|
||||||
bool has_prev = secondaries.last();
|
bool has_prev = secondaries.last();
|
||||||
while (has_prev) {
|
while (has_prev) {
|
||||||
get_action_area().add(new_plugin_button(secondaries.get()));
|
get_action_area().append(new_plugin_button(secondaries.get()));
|
||||||
has_prev = secondaries.previous();
|
has_prev = secondaries.previous();
|
||||||
}
|
}
|
||||||
update_plugin_primary_button();
|
update_plugin_primary_button();
|
||||||
|
|
||||||
set_data<int>(InfoBarStack.PRIORITY_QUEUE_KEY, priority);
|
set_data<int>(InfoBarStack.PRIORITY_QUEUE_KEY, priority);
|
||||||
|
|
||||||
show_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
|
|
@ -161,10 +156,9 @@ public class Components.InfoBar : Gtk.Box {
|
||||||
response(Gtk.ResponseType.CLOSE);
|
response(Gtk.ResponseType.CLOSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* {@inheritDoc} */
|
public override void dispose() {
|
||||||
public override void destroy() {
|
|
||||||
this.plugin = null;
|
this.plugin = null;
|
||||||
base.destroy();
|
base.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Gtk.Box get_action_area() {
|
public Gtk.Box get_action_area() {
|
||||||
|
|
@ -180,7 +174,7 @@ public class Components.InfoBar : Gtk.Box {
|
||||||
button.clicked.connect(() => {
|
button.clicked.connect(() => {
|
||||||
response(response_id);
|
response(response_id);
|
||||||
});
|
});
|
||||||
get_action_area().add(button);
|
get_action_area().append(button);
|
||||||
button.visible = true;
|
button.visible = true;
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +188,7 @@ public class Components.InfoBar : Gtk.Box {
|
||||||
get_action_area().remove(plugin_primary_button);
|
get_action_area().remove(plugin_primary_button);
|
||||||
}
|
}
|
||||||
if (new_button != null) {
|
if (new_button != null) {
|
||||||
get_action_area().add(new_button);
|
get_action_area().append(new_button);
|
||||||
}
|
}
|
||||||
this.plugin_primary_button = new_button;
|
this.plugin_primary_button = new_button;
|
||||||
}
|
}
|
||||||
|
|
@ -204,11 +198,7 @@ public class Components.InfoBar : Gtk.Box {
|
||||||
if (ui.icon_name == null) {
|
if (ui.icon_name == null) {
|
||||||
button = new Gtk.Button.with_label(ui.label);
|
button = new Gtk.Button.with_label(ui.label);
|
||||||
} else {
|
} else {
|
||||||
var icon = new Gtk.Image.from_icon_name(
|
button = new Gtk.Button.from_icon_name(ui.icon_name);
|
||||||
ui.icon_name, Gtk.IconSize.BUTTON
|
|
||||||
);
|
|
||||||
button = new Gtk.Button();
|
|
||||||
button.add(icon);
|
|
||||||
button.tooltip_text = ui.label;
|
button.tooltip_text = ui.label;
|
||||||
}
|
}
|
||||||
button.set_action_name(
|
button.set_action_name(
|
||||||
|
|
@ -217,26 +207,26 @@ public class Components.InfoBar : Gtk.Box {
|
||||||
if (ui.action_target != null) {
|
if (ui.action_target != null) {
|
||||||
button.set_action_target_value(ui.action_target);
|
button.set_action_target_value(ui.action_target);
|
||||||
}
|
}
|
||||||
button.show_all();
|
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _set_message_type(Gtk.MessageType message_type) {
|
private void _set_message_type(Gtk.MessageType message_type) {
|
||||||
if (this._message_type != message_type) {
|
if (this._message_type != message_type) {
|
||||||
Gtk.StyleContext context = this.get_style_context();
|
|
||||||
const string[] type_class = {
|
const string[] type_class = {
|
||||||
Gtk.STYLE_CLASS_INFO,
|
"info",
|
||||||
Gtk.STYLE_CLASS_WARNING,
|
"warning",
|
||||||
Gtk.STYLE_CLASS_QUESTION,
|
"question",
|
||||||
Gtk.STYLE_CLASS_ERROR,
|
"error",
|
||||||
null
|
null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type_class[this._message_type] != null)
|
if (type_class[this._message_type] != null)
|
||||||
context.remove_class(type_class[this._message_type]);
|
remove_css_class(type_class[this._message_type]);
|
||||||
|
|
||||||
this._message_type = message_type;
|
this._message_type = message_type;
|
||||||
|
|
||||||
|
// XXX GTK4
|
||||||
|
#if 0
|
||||||
var atk_obj = this.get_accessible();
|
var atk_obj = this.get_accessible();
|
||||||
if (atk_obj is Atk.Object) {
|
if (atk_obj is Atk.Object) {
|
||||||
string name = null;
|
string name = null;
|
||||||
|
|
@ -271,9 +261,10 @@ public class Components.InfoBar : Gtk.Box {
|
||||||
if (name != null)
|
if (name != null)
|
||||||
atk_obj.set_name(name);
|
atk_obj.set_name(name);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (type_class[this._message_type] != null)
|
if (type_class[this._message_type] != null)
|
||||||
context.add_class(type_class[this._message_type]);
|
add_css_class(type_class[this._message_type]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
* A view that displays information about an application error.
|
* A view that displays information about an application error.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector-error-view.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector-error-view.ui")]
|
||||||
public class Components.InspectorErrorView : Gtk.Grid {
|
public class Components.InspectorErrorView : Adw.Bin {
|
||||||
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.TextView problem_text;
|
[GtkChild] private unowned Gtk.TextView problem_text;
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,7 @@
|
||||||
* A view that displays the contents of the Engine's log.
|
* A view that displays the contents of the Engine's log.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector-log-view.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector-log-view.ui")]
|
||||||
public class Components.InspectorLogView : Gtk.Grid {
|
public class Components.InspectorLogView : Gtk.Box {
|
||||||
|
|
||||||
|
|
||||||
private const int COL_MESSAGE = 0;
|
|
||||||
private const int COL_ACCOUNT = 1;
|
|
||||||
private const int COL_DOMAIN = 2;
|
|
||||||
|
|
||||||
|
|
||||||
private class SidebarRow : Gtk.ListBoxRow {
|
private class SidebarRow : Gtk.ListBoxRow {
|
||||||
|
|
@ -47,25 +42,56 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
() => { notify_property("enabled"); }
|
() => { notify_property("enabled"); }
|
||||||
);
|
);
|
||||||
|
|
||||||
var grid = new Gtk.Grid();
|
var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
|
||||||
grid.orientation = HORIZONTAL;
|
box.append(label_widget);
|
||||||
grid.add(label_widget);
|
box.append(this.enabled_toggle);
|
||||||
grid.add(this.enabled_toggle);
|
this.child = box;
|
||||||
add(grid);
|
|
||||||
|
|
||||||
show_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class RecordRow : Gtk.Box {
|
||||||
|
|
||||||
|
public Geary.Logging.Record? record {
|
||||||
|
get { return this._record; }
|
||||||
|
set {
|
||||||
|
this._record = value;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Geary.Logging.Record? _record = null;
|
||||||
|
|
||||||
|
private unowned Gtk.Label message_label;
|
||||||
|
|
||||||
|
construct {
|
||||||
|
this.orientation = Gtk.Orientation.HORIZONTAL;
|
||||||
|
this.spacing = 6;
|
||||||
|
|
||||||
|
var label = new Gtk.Label("");
|
||||||
|
label.selectable = true;
|
||||||
|
label.add_css_class("monospace");
|
||||||
|
append(label);
|
||||||
|
this.message_label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update() {
|
||||||
|
if (this.record == null) {
|
||||||
|
this.message_label.label = "";
|
||||||
|
} else {
|
||||||
|
this.message_label.label = this.record.format();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Determines if the log record search user interface is shown. */
|
/** Determines if the log record search user interface is shown. */
|
||||||
public bool search_mode_enabled {
|
public bool search_mode_enabled {
|
||||||
get { return this.search_bar.search_mode_enabled; }
|
get { return this.search_bar.search_mode_enabled; }
|
||||||
set { this.search_bar.search_mode_enabled = value; }
|
set { this.search_bar.search_mode_enabled = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkChild] private unowned Hdy.SearchBar search_bar;
|
[GtkChild] private unowned Gtk.SearchBar search_bar;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.SearchEntry search_entry;
|
[GtkChild] private unowned Gtk.SearchEntry search_entry;
|
||||||
|
|
||||||
|
|
@ -73,18 +99,13 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ScrolledWindow logs_scroller;
|
[GtkChild] private unowned Gtk.ScrolledWindow logs_scroller;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.TreeView logs_view;
|
[GtkChild] private unowned Gtk.ListView logs_view;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.CellRendererText log_renderer;
|
[GtkChild] private unowned Gtk.MultiSelection selection;
|
||||||
|
|
||||||
private Gtk.ListStore logs_store = new Gtk.ListStore.newv({
|
[GtkChild] private unowned GLib.ListStore logs_store;
|
||||||
typeof(string),
|
|
||||||
typeof(string),
|
|
||||||
typeof(string)
|
|
||||||
});
|
|
||||||
|
|
||||||
private Gtk.TreeModelFilter logs_filter;
|
|
||||||
|
|
||||||
|
[GtkChild] private unowned Gtk.CustomFilter logs_filter;
|
||||||
private string[] logs_filter_terms = new string[0];
|
private string[] logs_filter_terms = new string[0];
|
||||||
|
|
||||||
private bool update_logs = true;
|
private bool update_logs = true;
|
||||||
|
|
@ -106,15 +127,7 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
public signal void record_selection_changed();
|
public signal void record_selection_changed();
|
||||||
|
|
||||||
|
|
||||||
public InspectorLogView(Application.Configuration config,
|
public InspectorLogView(Geary.AccountInformation? filter_by = null) {
|
||||||
Geary.AccountInformation? filter_by = null) {
|
|
||||||
GLib.Settings system = config.gnome_interface;
|
|
||||||
system.bind(
|
|
||||||
"monospace-font-name",
|
|
||||||
this.log_renderer, "font",
|
|
||||||
SettingsBindFlags.DEFAULT
|
|
||||||
);
|
|
||||||
|
|
||||||
// Prefill well-known engine logging domains
|
// Prefill well-known engine logging domains
|
||||||
add_domain(Geary.App.ConversationMonitor.LOGGING_DOMAIN);
|
add_domain(Geary.App.ConversationMonitor.LOGGING_DOMAIN);
|
||||||
add_domain(Geary.Imap.ClientService.LOGGING_DOMAIN);
|
add_domain(Geary.Imap.ClientService.LOGGING_DOMAIN);
|
||||||
|
|
@ -127,6 +140,8 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
this.search_bar.connect_entry(this.search_entry);
|
this.search_bar.connect_entry(this.search_entry);
|
||||||
this.sidebar.set_header_func(this.sidebar_header_update);
|
this.sidebar.set_header_func(this.sidebar_header_update);
|
||||||
this.account_filter = filter_by;
|
this.account_filter = filter_by;
|
||||||
|
|
||||||
|
this.logs_filter.set_filter_func(log_filter_func);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads log records from the logging system into the view. */
|
/** Loads log records from the logging system into the view. */
|
||||||
|
|
@ -138,42 +153,29 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
this.listener_installed = true;
|
this.listener_installed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.ListStore logs_store = this.logs_store;
|
|
||||||
Geary.Logging.Record? logs = first;
|
Geary.Logging.Record? logs = first;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
while (logs != last) {
|
while (logs != last) {
|
||||||
update_record(logs, logs_store, index++);
|
update_record(logs, this.logs_store, index++);
|
||||||
logs = logs.next;
|
logs = logs.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logs_filter = new Gtk.TreeModelFilter(this.logs_store, null);
|
|
||||||
this.logs_filter.set_visible_func(log_filter_func);
|
|
||||||
|
|
||||||
this.logs_view.set_model(this.logs_filter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clears all log records from the view. */
|
/** Clears all log records from the view. */
|
||||||
public void clear() {
|
public void clear() {
|
||||||
this.logs_store.clear();
|
this.logs_store.remove_all();
|
||||||
this.first_pending = null;
|
this.first_pending = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
~InspectorLogView() {
|
||||||
public override void destroy() {
|
|
||||||
if (this.listener_installed) {
|
if (this.listener_installed) {
|
||||||
Geary.Logging.set_log_listener(null);
|
Geary.Logging.set_log_listener(null);
|
||||||
}
|
}
|
||||||
base.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Forwards a key press event to the search entry. */
|
|
||||||
public bool handle_key_press(Gdk.EventKey event) {
|
|
||||||
return this.search_entry.key_press_event(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the number of currently selected log records. */
|
/** Returns the number of currently selected log records. */
|
||||||
public int count_selected_records() {
|
public uint count_selected_records() {
|
||||||
return this.logs_view.get_selection().count_selected_rows();
|
return (uint) this.selection.get_selection().get_size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Enables and disables updating log records as new ones arrive. */
|
/** Enables and disables updating log records as new ones arrive. */
|
||||||
|
|
@ -204,33 +206,30 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
out.put_string("```\n");
|
out.put_string("```\n");
|
||||||
}
|
}
|
||||||
string line_sep = format.get_line_separator();
|
string line_sep = format.get_line_separator();
|
||||||
Gtk.TreeModel model = this.logs_view.model;
|
|
||||||
if (save_all) {
|
if (save_all) {
|
||||||
// Save all rows selected
|
// Save all rows selected
|
||||||
Gtk.TreeIter? iter;
|
for (uint i = 0; i < this.logs_store.get_n_items(); i++) {
|
||||||
bool valid = model.get_iter_first(out iter);
|
if (cancellable.is_cancelled())
|
||||||
while (valid && !cancellable.is_cancelled()) {
|
break;
|
||||||
save_record(model, iter, @out, cancellable);
|
|
||||||
|
var record = (Geary.Logging.Record) this.logs_store.get_item(i);
|
||||||
|
out.put_string(record.format());
|
||||||
out.put_string(line_sep);
|
out.put_string(line_sep);
|
||||||
valid = model.iter_next(ref iter);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Save only selected
|
// Save only selected
|
||||||
GLib.Error? inner_err = null;
|
Gtk.Bitset selected = this.selection.get_selection();
|
||||||
this.logs_view.get_selection().selected_foreach(
|
for (uint i = 0; i < selected.get_size(); i++) {
|
||||||
(model, path, iter) => {
|
if (cancellable.is_cancelled())
|
||||||
if (inner_err == null) {
|
break;
|
||||||
try {
|
|
||||||
save_record(model, iter, @out, cancellable);
|
uint position = selected.get_nth(i);
|
||||||
out.put_string(line_sep);
|
var record = (Geary.Logging.Record) this.logs_store.get_item(position);
|
||||||
} catch (GLib.Error err) {
|
assert(record != null);
|
||||||
inner_err = err;
|
|
||||||
}
|
out.put_string(record.format());
|
||||||
}
|
out.put_string(line_sep);
|
||||||
}
|
|
||||||
);
|
|
||||||
if (inner_err != null) {
|
|
||||||
throw inner_err;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (format == MARKDOWN) {
|
if (format == MARKDOWN) {
|
||||||
|
|
@ -238,19 +237,6 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline void save_record(Gtk.TreeModel model,
|
|
||||||
Gtk.TreeIter iter,
|
|
||||||
GLib.DataOutputStream @out,
|
|
||||||
GLib.Cancellable? cancellable)
|
|
||||||
throws GLib.Error {
|
|
||||||
GLib.Value value;
|
|
||||||
model.get_value(iter, COL_MESSAGE, out value);
|
|
||||||
string? message = (string) value;
|
|
||||||
if (message != null) {
|
|
||||||
out.put_string(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add_account(Geary.AccountInformation account) {
|
private void add_account(Geary.AccountInformation account) {
|
||||||
if (this.seen_accounts.add(account.id)) {
|
if (this.seen_accounts.add(account.id)) {
|
||||||
var row = new SidebarRow(ACCOUNT, account.display_name, account.id);
|
var row = new SidebarRow(ACCOUNT, account.display_name, account.id);
|
||||||
|
|
@ -311,11 +297,11 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
string cleaned =
|
string cleaned =
|
||||||
Geary.String.reduce_whitespace(this.search_entry.text).casefold();
|
Geary.String.reduce_whitespace(this.search_entry.text).casefold();
|
||||||
this.logs_filter_terms = cleaned.split(" ");
|
this.logs_filter_terms = cleaned.split(" ");
|
||||||
this.logs_filter.refilter();
|
this.logs_filter.changed(Gtk.FilterChange.DIFFERENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline void update_record(Geary.Logging.Record record,
|
private inline void update_record(Geary.Logging.Record record,
|
||||||
Gtk.ListStore store,
|
GLib.ListStore store,
|
||||||
int position) {
|
int position) {
|
||||||
record.fill_well_known_sources();
|
record.fill_well_known_sources();
|
||||||
if (record.account != null) {
|
if (record.account != null) {
|
||||||
|
|
@ -325,14 +311,7 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
|
|
||||||
assert(record.format() != null);
|
assert(record.format() != null);
|
||||||
|
|
||||||
var account = record.account;
|
store.insert(position, record);
|
||||||
store.insert_with_values(
|
|
||||||
null,
|
|
||||||
position,
|
|
||||||
COL_MESSAGE, record.format(),
|
|
||||||
COL_ACCOUNT, account != null ? account.information.id : "",
|
|
||||||
COL_DOMAIN, record.domain ?? ""
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sidebar_header_update(Gtk.ListBoxRow current_row,
|
private void sidebar_header_update(Gtk.ListBoxRow current_row,
|
||||||
|
|
@ -347,22 +326,19 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
current_row.set_header(header);
|
current_row.set_header(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool log_filter_func(Gtk.TreeModel model, Gtk.TreeIter iter) {
|
private bool log_filter_func(GLib.Object object) {
|
||||||
GLib.Value value;
|
unowned var record = (Geary.Logging.Record) object;
|
||||||
model.get_value(iter, COL_ACCOUNT, out value);
|
|
||||||
var account = (string) value;
|
var account = record.account;
|
||||||
var show_row = (
|
bool show_row = (
|
||||||
account == "" || !(account in this.suppressed_accounts)
|
account == null || !(account.information.id in this.suppressed_accounts)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (show_row) {
|
if (show_row) {
|
||||||
model.get_value(iter, COL_DOMAIN, out value);
|
show_row = !Geary.Logging.is_suppressed_domain(record.domain ?? "");
|
||||||
var domain = (string) value;
|
|
||||||
show_row = !Geary.Logging.is_suppressed_domain(domain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model.get_value(iter, COL_MESSAGE, out value);
|
string message = record.format();
|
||||||
string message = (string) value;
|
|
||||||
if (show_row && this.logs_filter_terms.length > 0) {
|
if (show_row && this.logs_filter_terms.length > 0) {
|
||||||
var folded_message = message.casefold();
|
var folded_message = message.casefold();
|
||||||
foreach (string term in this.logs_filter_terms) {
|
foreach (string term in this.logs_filter_terms) {
|
||||||
|
|
@ -384,23 +360,34 @@ public class Components.InspectorLogView : Gtk.Grid {
|
||||||
return show_row;
|
return show_row;
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
|
||||||
private void on_logs_size_allocate() {
|
|
||||||
if (this.autoscroll) {
|
|
||||||
update_scrollbar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_logs_search_changed() {
|
private void on_logs_search_changed() {
|
||||||
update_logs_filter();
|
update_logs_filter();
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_logs_selection_changed() {
|
private void on_logs_selection_changed(Gtk.SelectionModel selection,
|
||||||
|
uint position,
|
||||||
|
uint changed) {
|
||||||
record_selection_changed();
|
record_selection_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_item_factory_setup(Object object) {
|
||||||
|
unowned var item = (Gtk.ListItem) object;
|
||||||
|
|
||||||
|
item.child = new RecordRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
[GtkCallback]
|
||||||
|
private void on_item_factory_bind(Object object) {
|
||||||
|
unowned var item = (Gtk.ListItem) object;
|
||||||
|
unowned var record = (Geary.Logging.Record) item.item;
|
||||||
|
unowned var row = (RecordRow) item.child;
|
||||||
|
|
||||||
|
row.record = record;
|
||||||
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_sidebar_row_activated(Gtk.ListBox list,
|
private void on_sidebar_row_activated(Gtk.ListBox list,
|
||||||
Gtk.ListBoxRow activated) {
|
Gtk.ListBoxRow activated) {
|
||||||
|
|
|
||||||
|
|
@ -9,52 +9,7 @@
|
||||||
* A view that displays system and library information.
|
* A view that displays system and library information.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector-system-view.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector-system-view.ui")]
|
||||||
public class Components.InspectorSystemView : Gtk.Grid {
|
public class Components.InspectorSystemView : Gtk.Box {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private class DetailRow : Gtk.ListBoxRow {
|
|
||||||
|
|
||||||
|
|
||||||
private Gtk.Grid layout {
|
|
||||||
get; private set; default = new Gtk.Grid();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Gtk.Label label {
|
|
||||||
get; private set; default = new Gtk.Label("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Gtk.Label value {
|
|
||||||
get; private set; default = new Gtk.Label("");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public DetailRow(string label, string value) {
|
|
||||||
get_style_context().add_class("geary-labelled-row");
|
|
||||||
|
|
||||||
this.label.halign = Gtk.Align.START;
|
|
||||||
this.label.valign = Gtk.Align.CENTER;
|
|
||||||
this.label.set_text(label);
|
|
||||||
this.label.show();
|
|
||||||
|
|
||||||
this.value.halign = Gtk.Align.END;
|
|
||||||
this.value.hexpand = true;
|
|
||||||
this.value.valign = Gtk.Align.CENTER;
|
|
||||||
this.value.xalign = 1.0f;
|
|
||||||
this.value.set_text(value);
|
|
||||||
this.value.show();
|
|
||||||
|
|
||||||
this.layout.orientation = Gtk.Orientation.HORIZONTAL;
|
|
||||||
this.layout.add(this.label);
|
|
||||||
this.layout.add(this.value);
|
|
||||||
this.layout.show();
|
|
||||||
add(this.layout);
|
|
||||||
|
|
||||||
this.activatable = false;
|
|
||||||
show();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox system_list;
|
[GtkChild] private unowned Gtk.ListBox system_list;
|
||||||
|
|
@ -65,9 +20,11 @@ public class Components.InspectorSystemView : Gtk.Grid {
|
||||||
public InspectorSystemView(Application.Client application) {
|
public InspectorSystemView(Application.Client application) {
|
||||||
this.details = application.get_runtime_information();
|
this.details = application.get_runtime_information();
|
||||||
foreach (Application.Client.RuntimeDetail? detail in this.details) {
|
foreach (Application.Client.RuntimeDetail? detail in this.details) {
|
||||||
this.system_list.add(
|
var row = new Adw.ActionRow();
|
||||||
new DetailRow("%s:".printf(detail.name), detail.value)
|
row.add_css_class("property");
|
||||||
);
|
row.title = detail.name;
|
||||||
|
row.subtitle = detail.value;
|
||||||
|
this.system_list.append(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
* A window that displays debugging and development information.
|
* A window that displays debugging and development information.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector.ui")]
|
||||||
public class Components.Inspector : Gtk.ApplicationWindow {
|
public class Components.Inspector : Adw.ApplicationWindow {
|
||||||
|
|
||||||
|
|
||||||
/** Determines the format used when serialising inspector data. */
|
/** Determines the format used when serialising inspector data. */
|
||||||
|
|
@ -68,7 +68,8 @@ public class Components.Inspector : Gtk.ApplicationWindow {
|
||||||
|
|
||||||
public Inspector(Application.Client application) {
|
public Inspector(Application.Client application) {
|
||||||
Object(application: application);
|
Object(application: application);
|
||||||
this.title = this.header_bar.title = _("Inspector");
|
//XXX GTK4 need to figure out titles
|
||||||
|
// this.title = this.header_bar.title = _("Inspector");
|
||||||
|
|
||||||
// Edit actions
|
// Edit actions
|
||||||
GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
|
||||||
|
|
@ -78,7 +79,7 @@ public class Components.Inspector : Gtk.ApplicationWindow {
|
||||||
// Window actions
|
// Window actions
|
||||||
add_action_entries(WINDOW_ACTIONS, this);
|
add_action_entries(WINDOW_ACTIONS, this);
|
||||||
|
|
||||||
this.log_pane = new InspectorLogView(application.config, null);
|
this.log_pane = new InspectorLogView(null);
|
||||||
this.log_pane.record_selection_changed.connect(
|
this.log_pane.record_selection_changed.connect(
|
||||||
on_logs_selection_changed
|
on_logs_selection_changed
|
||||||
);
|
);
|
||||||
|
|
@ -95,11 +96,15 @@ public class Components.Inspector : Gtk.ApplicationWindow {
|
||||||
this.log_pane.load(Geary.Logging.get_earliest_record(), null);
|
this.log_pane.load(Geary.Logging.get_earliest_record(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool key_press_event(Gdk.EventKey event) {
|
[GtkCallback]
|
||||||
|
private bool on_key_pressed(Gtk.EventControllerKey controller,
|
||||||
|
uint keyval,
|
||||||
|
uint keycode,
|
||||||
|
Gdk.ModifierType state) {
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
bool ret = Gdk.EVENT_PROPAGATE;
|
||||||
|
|
||||||
if (this.log_pane.search_mode_enabled &&
|
if (this.log_pane.search_mode_enabled &&
|
||||||
event.keyval == Gdk.Key.Escape) {
|
keyval == Gdk.Key.Escape) {
|
||||||
// Manually deactivate search so the button stays in sync
|
// Manually deactivate search so the button stays in sync
|
||||||
this.search_button.set_active(false);
|
this.search_button.set_active(false);
|
||||||
ret = Gdk.EVENT_STOP;
|
ret = Gdk.EVENT_STOP;
|
||||||
|
|
@ -109,18 +114,17 @@ public class Components.Inspector : Gtk.ApplicationWindow {
|
||||||
this.log_pane.search_mode_enabled) {
|
this.log_pane.search_mode_enabled) {
|
||||||
// Ensure <Space> and others are passed to the search
|
// Ensure <Space> and others are passed to the search
|
||||||
// entry before getting used as an accelerator.
|
// entry before getting used as an accelerator.
|
||||||
ret = this.log_pane.handle_key_press(event);
|
ret = controller.forward(this.log_pane);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == Gdk.EVENT_PROPAGATE) {
|
return ret;
|
||||||
ret = base.key_press_event(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//XXX GTK4 - not sure how to handle this
|
||||||
if (ret == Gdk.EVENT_PROPAGATE &&
|
if (ret == Gdk.EVENT_PROPAGATE &&
|
||||||
!this.log_pane.search_mode_enabled) {
|
!this.log_pane.search_mode_enabled) {
|
||||||
// Nothing has handled the event yet, and search is not
|
// Nothing has handled the event yet, and search is not
|
||||||
// active, so see if we want to activate it now.
|
// active, so see if we want to activate it now.
|
||||||
ret = this.log_pane.handle_key_press(event);
|
ret = controller.forward(this.log_pane);
|
||||||
if (ret == Gdk.EVENT_STOP) {
|
if (ret == Gdk.EVENT_STOP) {
|
||||||
this.search_button.set_active(true);
|
this.search_button.set_active(true);
|
||||||
}
|
}
|
||||||
|
|
@ -140,10 +144,9 @@ public class Components.Inspector : Gtk.ApplicationWindow {
|
||||||
this.log_pane.enable_log_updates(enabled);
|
this.log_pane.enable_log_updates(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void save(string path,
|
private async void save(GLib.File dest,
|
||||||
GLib.Cancellable? cancellable)
|
GLib.Cancellable? cancellable)
|
||||||
throws GLib.Error {
|
throws GLib.Error {
|
||||||
GLib.File dest = GLib.File.new_for_path(path);
|
|
||||||
GLib.FileIOStream dest_io = yield dest.replace_readwrite_async(
|
GLib.FileIOStream dest_io = yield dest.replace_readwrite_async(
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
|
|
@ -209,35 +212,29 @@ public class Components.Inspector : Gtk.ApplicationWindow {
|
||||||
|
|
||||||
string clipboard_value = (string) bytes.get_data();
|
string clipboard_value = (string) bytes.get_data();
|
||||||
if (!Geary.String.is_empty(clipboard_value)) {
|
if (!Geary.String.is_empty(clipboard_value)) {
|
||||||
get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(clipboard_value, -1);
|
get_clipboard().set_text(clipboard_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_save_as_clicked() {
|
private void on_save_as_clicked() {
|
||||||
Gtk.FileChooserNative chooser = new Gtk.FileChooserNative(
|
save_as.begin((obj, res) => {
|
||||||
_("Save As"),
|
save_as.end(res);
|
||||||
this,
|
});
|
||||||
Gtk.FileChooserAction.SAVE,
|
}
|
||||||
_("Save As"),
|
|
||||||
_("Cancel")
|
|
||||||
);
|
|
||||||
chooser.set_current_name(
|
|
||||||
new GLib.DateTime.now_local().format("Geary Inspector - %F %T.txt")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (chooser.run() == Gtk.ResponseType.ACCEPT) {
|
private async void save_as() {
|
||||||
this.save.begin(
|
var dialog = new Gtk.FileDialog();
|
||||||
chooser.get_filename(),
|
dialog.title = _("Save As");
|
||||||
null,
|
dialog.accept_label = _("Save As");
|
||||||
(obj, res) => {
|
dialog.initial_name = new DateTime.now_local().format("Geary Inspector - %F %T.txt");
|
||||||
try {
|
|
||||||
this.save.end(res);
|
try {
|
||||||
} catch (GLib.Error err) {
|
File? file = yield dialog.save(this, null);
|
||||||
warning("Failed to save inspector data: %s", err.message);
|
if (file != null)
|
||||||
}
|
yield this.save(file, null);
|
||||||
}
|
} catch (Error err) {
|
||||||
);
|
warning("Failed to save inspector data: %s", err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
* A placeholder image and message for empty views.
|
* A placeholder image and message for empty views.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/components-placeholder-pane.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/components-placeholder-pane.ui")]
|
||||||
public class Components.PlaceholderPane : Gtk.Grid {
|
public class Components.PlaceholderPane : Gtk.Box {
|
||||||
|
|
||||||
|
|
||||||
public const string CLASS_HAS_TEXT = "geary-has-text";
|
public const string CLASS_HAS_TEXT = "geary-has-text";
|
||||||
|
|
@ -54,7 +54,7 @@ public class Components.PlaceholderPane : Gtk.Grid {
|
||||||
this.subtitle_label.hide();
|
this.subtitle_label.hide();
|
||||||
}
|
}
|
||||||
if (this.title_label.visible || this.subtitle_label.visible) {
|
if (this.title_label.visible || this.subtitle_label.visible) {
|
||||||
get_style_context().add_class(CLASS_HAS_TEXT);
|
add_css_class(CLASS_HAS_TEXT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
159
src/client/components/components-preferences-dialog.vala
Normal file
159
src/client/components/components-preferences-dialog.vala
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[GtkTemplate (ui = "/org/gnome/Geary/components-preferences-dialog.ui")]
|
||||||
|
public class Components.PreferencesDialog : Adw.PreferencesDialog {
|
||||||
|
|
||||||
|
[GtkChild] private unowned Adw.SwitchRow autoselect_row;
|
||||||
|
[GtkChild] private unowned Adw.SwitchRow display_preview_row;
|
||||||
|
[GtkChild] private unowned Adw.SwitchRow single_key_shortcuts_row;
|
||||||
|
[GtkChild] private unowned Adw.SwitchRow startup_notifications_row;
|
||||||
|
[GtkChild] private unowned Adw.SwitchRow trust_images_row;
|
||||||
|
|
||||||
|
[GtkChild] private unowned Adw.PreferencesGroup plugins_group;
|
||||||
|
|
||||||
|
private class PluginRow : Adw.ActionRow {
|
||||||
|
|
||||||
|
private Peas.PluginInfo plugin;
|
||||||
|
private Application.PluginManager plugins;
|
||||||
|
private Gtk.Switch sw = new Gtk.Switch();
|
||||||
|
|
||||||
|
|
||||||
|
public PluginRow(Peas.PluginInfo plugin,
|
||||||
|
Application.PluginManager plugins) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.plugins = plugins;
|
||||||
|
|
||||||
|
this.sw.active = plugin.is_loaded();
|
||||||
|
this.sw.notify["active"].connect_after(() => update_plugin());
|
||||||
|
this.sw.valign = CENTER;
|
||||||
|
|
||||||
|
this.title = plugin.get_name();
|
||||||
|
this.subtitle = plugin.get_description();
|
||||||
|
this.activatable_widget = this.sw;
|
||||||
|
this.add_suffix(this.sw);
|
||||||
|
|
||||||
|
plugins.plugin_activated.connect((info) => {
|
||||||
|
if (this.plugin == info) {
|
||||||
|
this.sw.active = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
plugins.plugin_deactivated.connect((info) => {
|
||||||
|
if (this.plugin == info) {
|
||||||
|
this.sw.active = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
plugins.plugin_error.connect((info) => {
|
||||||
|
if (this.plugin == info) {
|
||||||
|
this.sw.active = false;
|
||||||
|
this.sw.sensitive = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_plugin() {
|
||||||
|
if (this.sw.active && !this.plugin.is_loaded()) {
|
||||||
|
bool loaded = false;
|
||||||
|
try {
|
||||||
|
loaded = this.plugins.load_optional(this.plugin);
|
||||||
|
} catch (GLib.Error err) {
|
||||||
|
warning(
|
||||||
|
"Plugin %s not able to be loaded: %s",
|
||||||
|
plugin.get_name(), err.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!loaded) {
|
||||||
|
this.sw.active = false;
|
||||||
|
}
|
||||||
|
} else if (!sw.active && this.plugin.is_loaded()) {
|
||||||
|
bool unloaded = false;
|
||||||
|
try {
|
||||||
|
unloaded = this.plugins.unload_optional(this.plugin);
|
||||||
|
} catch (GLib.Error err) {
|
||||||
|
warning(
|
||||||
|
"Plugin %s not able to be loaded: %s",
|
||||||
|
plugin.get_name(), err.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!unloaded) {
|
||||||
|
this.sw.active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Returns the window's associated client application instance. */
|
||||||
|
public Application.Client? application { get; construct set; }
|
||||||
|
|
||||||
|
private Application.PluginManager plugins;
|
||||||
|
|
||||||
|
|
||||||
|
public PreferencesDialog(Application.Client application,
|
||||||
|
Application.PluginManager plugins) {
|
||||||
|
Object(application: application);
|
||||||
|
this.plugins = plugins;
|
||||||
|
|
||||||
|
setup_general_pane();
|
||||||
|
setup_plugin_pane();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setup_general_pane() {
|
||||||
|
Application.Configuration config = this.application.config;
|
||||||
|
config.bind(
|
||||||
|
Application.Configuration.AUTOSELECT_KEY,
|
||||||
|
this.autoselect_row,
|
||||||
|
"active"
|
||||||
|
);
|
||||||
|
config.bind(
|
||||||
|
Application.Configuration.DISPLAY_PREVIEW_KEY,
|
||||||
|
this.display_preview_row,
|
||||||
|
"active"
|
||||||
|
);
|
||||||
|
config.bind(
|
||||||
|
Application.Configuration.SINGLE_KEY_SHORTCUTS,
|
||||||
|
this.single_key_shortcuts_row,
|
||||||
|
"active"
|
||||||
|
);
|
||||||
|
config.bind(
|
||||||
|
Application.Configuration.RUN_IN_BACKGROUND_KEY,
|
||||||
|
this.startup_notifications_row,
|
||||||
|
"active"
|
||||||
|
);
|
||||||
|
config.bind_with_mapping(
|
||||||
|
Application.Configuration.IMAGES_TRUSTED_DOMAINS,
|
||||||
|
this.trust_images_row,
|
||||||
|
"active",
|
||||||
|
(GLib.SettingsBindGetMappingShared) settings_trust_images_getter,
|
||||||
|
(GLib.SettingsBindSetMappingShared) settings_trust_images_setter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setup_plugin_pane() {
|
||||||
|
foreach (Peas.PluginInfo plugin in
|
||||||
|
this.plugins.get_optional_plugins()) {
|
||||||
|
this.plugins_group.add(new PluginRow(plugin, this.plugins));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool settings_trust_images_getter(GLib.Value value, GLib.Variant variant, void* user_data) {
|
||||||
|
var domains = variant.get_strv();
|
||||||
|
value.set_boolean(domains.length > 0 && domains[0] == "*");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GLib.Variant settings_trust_images_setter(GLib.Value value, GLib.VariantType expected_type, void* user_data) {
|
||||||
|
var trusted = value.get_boolean();
|
||||||
|
string[] values = {};
|
||||||
|
if (trusted)
|
||||||
|
values += "*";
|
||||||
|
return new GLib.Variant.strv(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,294 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class Components.PreferencesWindow : Hdy.PreferencesWindow {
|
|
||||||
|
|
||||||
|
|
||||||
private const string ACTION_CLOSE = "preferences-close";
|
|
||||||
|
|
||||||
private const ActionEntry[] WINDOW_ACTIONS = {
|
|
||||||
{ Action.Window.CLOSE, on_close },
|
|
||||||
{ ACTION_CLOSE, on_close },
|
|
||||||
};
|
|
||||||
|
|
||||||
private class PluginRow : Hdy.ActionRow {
|
|
||||||
|
|
||||||
private Peas.PluginInfo plugin;
|
|
||||||
private Application.PluginManager plugins;
|
|
||||||
private Gtk.Switch sw = new Gtk.Switch();
|
|
||||||
|
|
||||||
|
|
||||||
public PluginRow(Peas.PluginInfo plugin,
|
|
||||||
Application.PluginManager plugins) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.plugins = plugins;
|
|
||||||
|
|
||||||
this.sw.active = plugin.is_loaded();
|
|
||||||
this.sw.notify["active"].connect_after(() => update_plugin());
|
|
||||||
this.sw.valign = CENTER;
|
|
||||||
|
|
||||||
this.title = plugin.get_name();
|
|
||||||
this.subtitle = plugin.get_description();
|
|
||||||
this.activatable_widget = this.sw;
|
|
||||||
this.add(this.sw);
|
|
||||||
|
|
||||||
plugins.plugin_activated.connect((info) => {
|
|
||||||
if (this.plugin == info) {
|
|
||||||
this.sw.active = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
plugins.plugin_deactivated.connect((info) => {
|
|
||||||
if (this.plugin == info) {
|
|
||||||
this.sw.active = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
plugins.plugin_error.connect((info) => {
|
|
||||||
if (this.plugin == info) {
|
|
||||||
this.sw.active = false;
|
|
||||||
this.sw.sensitive = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_plugin() {
|
|
||||||
if (this.sw.active && !this.plugin.is_loaded()) {
|
|
||||||
bool loaded = false;
|
|
||||||
try {
|
|
||||||
loaded = this.plugins.load_optional(this.plugin);
|
|
||||||
} catch (GLib.Error err) {
|
|
||||||
warning(
|
|
||||||
"Plugin %s not able to be loaded: %s",
|
|
||||||
plugin.get_name(), err.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!loaded) {
|
|
||||||
this.sw.active = false;
|
|
||||||
}
|
|
||||||
} else if (!sw.active && this.plugin.is_loaded()) {
|
|
||||||
bool unloaded = false;
|
|
||||||
try {
|
|
||||||
unloaded = this.plugins.unload_optional(this.plugin);
|
|
||||||
} catch (GLib.Error err) {
|
|
||||||
warning(
|
|
||||||
"Plugin %s not able to be loaded: %s",
|
|
||||||
plugin.get_name(), err.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!unloaded) {
|
|
||||||
this.sw.active = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void add_accelerators(Application.Client app) {
|
|
||||||
app.add_window_accelerators(ACTION_CLOSE, { "Escape" } );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Returns the window's associated client application instance. */
|
|
||||||
public new Application.Client? application {
|
|
||||||
get { return (Application.Client) base.get_application(); }
|
|
||||||
set { base.set_application(value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
private Application.PluginManager plugins;
|
|
||||||
|
|
||||||
|
|
||||||
public PreferencesWindow(Application.MainWindow parent,
|
|
||||||
Application.PluginManager plugins) {
|
|
||||||
Object(
|
|
||||||
application: parent.application,
|
|
||||||
default_width: 800,
|
|
||||||
default_height: 600,
|
|
||||||
transient_for: parent
|
|
||||||
);
|
|
||||||
this.plugins = plugins;
|
|
||||||
|
|
||||||
add_general_pane();
|
|
||||||
add_plugin_pane();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add_general_pane() {
|
|
||||||
var autoselect = new Gtk.Switch();
|
|
||||||
autoselect.valign = CENTER;
|
|
||||||
|
|
||||||
var autoselect_row = new Hdy.ActionRow();
|
|
||||||
/// Translators: Preferences label
|
|
||||||
autoselect_row.title = _("_Automatically select next message");
|
|
||||||
autoselect_row.use_underline = true;
|
|
||||||
autoselect_row.activatable_widget = autoselect;
|
|
||||||
autoselect_row.add(autoselect);
|
|
||||||
|
|
||||||
var display_preview = new Gtk.Switch();
|
|
||||||
display_preview.valign = CENTER;
|
|
||||||
|
|
||||||
var display_preview_row = new Hdy.ActionRow();
|
|
||||||
/// Translators: Preferences label
|
|
||||||
display_preview_row.title = _("_Display conversation preview");
|
|
||||||
display_preview_row.use_underline = true;
|
|
||||||
display_preview_row.activatable_widget = display_preview;
|
|
||||||
display_preview_row.add(display_preview);
|
|
||||||
|
|
||||||
var single_key_shortucts = new Gtk.Switch();
|
|
||||||
single_key_shortucts.valign = CENTER;
|
|
||||||
|
|
||||||
var single_key_shortucts_row = new Hdy.ActionRow();
|
|
||||||
/// Translators: Preferences label
|
|
||||||
single_key_shortucts_row.title = _("Use _single key email shortcuts");
|
|
||||||
single_key_shortucts_row.tooltip_text = _(
|
|
||||||
"Enable keyboard shortcuts for email actions that do not require pressing <Ctrl>"
|
|
||||||
);
|
|
||||||
single_key_shortucts_row.use_underline = true;
|
|
||||||
single_key_shortucts_row.activatable_widget = single_key_shortucts;
|
|
||||||
single_key_shortucts_row.add(single_key_shortucts);
|
|
||||||
|
|
||||||
var startup_notifications = new Gtk.Switch();
|
|
||||||
startup_notifications.valign = CENTER;
|
|
||||||
|
|
||||||
var startup_notifications_row = new Hdy.ActionRow();
|
|
||||||
/// Translators: Preferences label
|
|
||||||
startup_notifications_row.title = _("_Watch for new mail when closed");
|
|
||||||
startup_notifications_row.use_underline = true;
|
|
||||||
/// Translators: Preferences tooltip
|
|
||||||
startup_notifications_row.tooltip_text = _(
|
|
||||||
"Geary will keep running after all windows are closed"
|
|
||||||
);
|
|
||||||
startup_notifications_row.activatable_widget = startup_notifications;
|
|
||||||
startup_notifications_row.add(startup_notifications);
|
|
||||||
|
|
||||||
var trust_images = new Gtk.Switch();
|
|
||||||
trust_images.valign = CENTER;
|
|
||||||
|
|
||||||
var trust_images_row = new Hdy.ActionRow();
|
|
||||||
/// Translators: Preferences label
|
|
||||||
trust_images_row.title = _("_Always load images");
|
|
||||||
trust_images_row.subtitle = _("Showing remote images allows the sender to track you");
|
|
||||||
trust_images_row.use_underline = true;
|
|
||||||
trust_images_row.activatable_widget = trust_images;
|
|
||||||
trust_images_row.add(trust_images);
|
|
||||||
|
|
||||||
var unset_html_colors = new Gtk.Switch();
|
|
||||||
unset_html_colors.valign = CENTER;
|
|
||||||
|
|
||||||
var unset_html_colors_row = new Hdy.ActionRow();
|
|
||||||
/// Translators: Preferences label
|
|
||||||
unset_html_colors_row.title = _("_Override the original colors in HTML emails");
|
|
||||||
unset_html_colors_row.subtitle = _("Overrides the original colors in HTML messages to integrate better with the app theme. Requires restart.");
|
|
||||||
unset_html_colors_row.use_underline = true;
|
|
||||||
unset_html_colors_row.activatable_widget = unset_html_colors;
|
|
||||||
unset_html_colors_row.add(unset_html_colors);
|
|
||||||
|
|
||||||
var group = new Hdy.PreferencesGroup();
|
|
||||||
/// Translators: Preferences group title
|
|
||||||
//group.title = _("General");
|
|
||||||
/// Translators: Preferences group description
|
|
||||||
//group.description = _("General application preferences");
|
|
||||||
group.add(autoselect_row);
|
|
||||||
group.add(display_preview_row);
|
|
||||||
group.add(single_key_shortucts_row);
|
|
||||||
group.add(startup_notifications_row);
|
|
||||||
group.add(trust_images_row);
|
|
||||||
group.add(unset_html_colors_row);
|
|
||||||
|
|
||||||
var page = new Hdy.PreferencesPage();
|
|
||||||
/// Translators: Preferences page title
|
|
||||||
page.title = _("Preferences");
|
|
||||||
page.icon_name = "preferences-other-symbolic";
|
|
||||||
page.add(group);
|
|
||||||
page.show_all();
|
|
||||||
|
|
||||||
add(page);
|
|
||||||
|
|
||||||
GLib.SimpleActionGroup window_actions = new GLib.SimpleActionGroup();
|
|
||||||
window_actions.add_action_entries(WINDOW_ACTIONS, this);
|
|
||||||
insert_action_group(Action.Window.GROUP_NAME, window_actions);
|
|
||||||
|
|
||||||
Application.Client? application = this.application;
|
|
||||||
if (application != null) {
|
|
||||||
Application.Configuration config = application.config;
|
|
||||||
config.bind(
|
|
||||||
Application.Configuration.AUTOSELECT_KEY,
|
|
||||||
autoselect,
|
|
||||||
"state"
|
|
||||||
);
|
|
||||||
config.bind(
|
|
||||||
Application.Configuration.DISPLAY_PREVIEW_KEY,
|
|
||||||
display_preview,
|
|
||||||
"state"
|
|
||||||
);
|
|
||||||
config.bind(
|
|
||||||
Application.Configuration.SINGLE_KEY_SHORTCUTS,
|
|
||||||
single_key_shortucts,
|
|
||||||
"state"
|
|
||||||
);
|
|
||||||
config.bind(
|
|
||||||
Application.Configuration.RUN_IN_BACKGROUND_KEY,
|
|
||||||
startup_notifications,
|
|
||||||
"state"
|
|
||||||
);
|
|
||||||
config.bind_with_mapping(
|
|
||||||
Application.Configuration.IMAGES_TRUSTED_DOMAINS,
|
|
||||||
trust_images,
|
|
||||||
"state",
|
|
||||||
(GLib.SettingsBindGetMappingShared) settings_trust_images_getter,
|
|
||||||
(GLib.SettingsBindSetMappingShared) settings_trust_images_setter
|
|
||||||
);
|
|
||||||
config.bind(
|
|
||||||
Application.Configuration.UNSET_HTML_COLORS,
|
|
||||||
unset_html_colors,
|
|
||||||
"state"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add_plugin_pane() {
|
|
||||||
var group = new Hdy.PreferencesGroup();
|
|
||||||
/// Translators: Preferences group title
|
|
||||||
//group.title = _("Plugins");
|
|
||||||
/// Translators: Preferences group description
|
|
||||||
//group.description = _("Optional features for Geary");
|
|
||||||
|
|
||||||
Application.Client? application = this.application;
|
|
||||||
if (application != null) {
|
|
||||||
foreach (Peas.PluginInfo plugin in
|
|
||||||
this.plugins.get_optional_plugins()) {
|
|
||||||
group.add(new PluginRow(plugin, this.plugins));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var page = new Hdy.PreferencesPage();
|
|
||||||
/// Translators: Preferences page title
|
|
||||||
page.title = _("Plugins");
|
|
||||||
page.icon_name = "application-x-addon-symbolic";
|
|
||||||
page.add(group);
|
|
||||||
page.show_all();
|
|
||||||
|
|
||||||
add(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_close() {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool settings_trust_images_getter(GLib.Value value, GLib.Variant variant, void* user_data) {
|
|
||||||
var domains = variant.get_strv();
|
|
||||||
value.set_boolean(domains.length > 0 && domains[0] == "*");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GLib.Variant settings_trust_images_setter(GLib.Value value, GLib.VariantType expected_type, void* user_data) {
|
|
||||||
var trusted = value.get_boolean();
|
|
||||||
string[] values = {};
|
|
||||||
if (trusted)
|
|
||||||
values += "*";
|
|
||||||
return new GLib.Variant.strv(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -106,14 +106,13 @@ public class Components.ProblemReportInfoBar : InfoBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void show_details() {
|
private void show_details() {
|
||||||
var main = get_toplevel() as Application.MainWindow;
|
var main = get_root() as Application.MainWindow;
|
||||||
if (main != null) {
|
if (main != null) {
|
||||||
var dialog = new Dialogs.ProblemDetailsDialog(
|
var dialog = new Dialogs.ProblemDetailsDialog(
|
||||||
main,
|
|
||||||
main.application,
|
main.application,
|
||||||
this.report
|
this.report
|
||||||
);
|
);
|
||||||
dialog.show();
|
dialog.present(main);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
|
||||||
|
// XXX GTK4 - I really need to know how this works before reimplementing it
|
||||||
|
#if 0
|
||||||
#define COMPONENTS_TYPE_REFLOW_BOX (components_reflow_box_get_type())
|
#define COMPONENTS_TYPE_REFLOW_BOX (components_reflow_box_get_type())
|
||||||
|
|
||||||
G_DECLARE_FINAL_TYPE (ComponentsReflowBox, components_reflow_box, COMPONENTS, REFLOW_BOX, GtkContainer)
|
G_DECLARE_FINAL_TYPE (ComponentsReflowBox, components_reflow_box, COMPONENTS, REFLOW_BOX, GtkContainer)
|
||||||
|
|
@ -491,6 +493,4 @@ components_reflow_box_new (void)
|
||||||
{
|
{
|
||||||
return g_object_new (COMPONENTS_TYPE_REFLOW_BOX, NULL);
|
return g_object_new (COMPONENTS_TYPE_REFLOW_BOX, NULL);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,17 @@
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class SearchBar : Hdy.SearchBar {
|
public class SearchBar : Adw.Bin {
|
||||||
|
|
||||||
/// Translators: Search entry placeholder text
|
/// Translators: Search entry placeholder text
|
||||||
private const string DEFAULT_SEARCH_TEXT = _("Search");
|
private const string DEFAULT_SEARCH_TEXT = _("Search");
|
||||||
|
|
||||||
|
private unowned Gtk.SearchBar search_bar;
|
||||||
|
public bool search_mode_enabled {
|
||||||
|
get { return this.search_bar.search_mode_enabled; }
|
||||||
|
set { this.search_bar.search_mode_enabled = value; }
|
||||||
|
}
|
||||||
|
|
||||||
public Gtk.SearchEntry entry {
|
public Gtk.SearchEntry entry {
|
||||||
get; private set; default = new Gtk.SearchEntry();
|
get; private set; default = new Gtk.SearchEntry();
|
||||||
}
|
}
|
||||||
|
|
@ -23,10 +29,14 @@ public class SearchBar : Hdy.SearchBar {
|
||||||
|
|
||||||
|
|
||||||
public SearchBar(Geary.Engine engine) {
|
public SearchBar(Geary.Engine engine) {
|
||||||
|
var bar = new Gtk.SearchBar();
|
||||||
|
this.search_bar = bar;
|
||||||
|
this.child = bar;
|
||||||
|
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.search_undo = new Components.EntryUndo(this.entry);
|
this.search_undo = new Components.EntryUndo(this.entry);
|
||||||
|
|
||||||
this.notify["search-mode-enabled"].connect(on_search_mode_changed);
|
search_bar.notify["search-mode-enabled"].connect(on_search_mode_changed);
|
||||||
|
|
||||||
/// Translators: Search entry tooltip
|
/// Translators: Search entry tooltip
|
||||||
this.entry.tooltip_text = _("Search all mail in account for keywords");
|
this.entry.tooltip_text = _("Search all mail in account for keywords");
|
||||||
|
|
@ -37,21 +47,18 @@ public class SearchBar : Hdy.SearchBar {
|
||||||
search_text_changed(this.entry.text);
|
search_text_changed(this.entry.text);
|
||||||
});
|
});
|
||||||
this.entry.placeholder_text = DEFAULT_SEARCH_TEXT;
|
this.entry.placeholder_text = DEFAULT_SEARCH_TEXT;
|
||||||
this.entry.has_focus = true;
|
|
||||||
|
|
||||||
var column = new Hdy.Clamp();
|
var column = new Adw.Clamp();
|
||||||
column.maximum_size = 400;
|
column.maximum_size = 400;
|
||||||
column.add(this.entry);
|
column.child = this.entry;
|
||||||
|
|
||||||
connect_entry(this.entry);
|
search_bar.connect_entry(this.entry);
|
||||||
add(column);
|
search_bar.child = column;
|
||||||
|
|
||||||
show_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void grab_focus() {
|
public override bool grab_focus() {
|
||||||
set_search_mode(true);
|
this.search_mode_enabled = true;
|
||||||
this.entry.grab_focus();
|
return this.entry.grab_focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_account(Geary.Account? account) {
|
public void set_account(Geary.Account? account) {
|
||||||
|
|
|
||||||
91
src/client/components/components-validator-group.vala
Normal file
91
src/client/components/components-validator-group.vala
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 Niels De Graef <nielsdegraef@gmail.com>
|
||||||
|
*
|
||||||
|
* This software is licensed under the GNU Lesser General Public License
|
||||||
|
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups several validators together, allowing to show an aggregate result.
|
||||||
|
*/
|
||||||
|
public class Components.ValidatorGroup : GLib.Object, GLib.ListModel, Gtk.Buildable {
|
||||||
|
|
||||||
|
private GenericArray<Validator> validators = new GenericArray<Validator>();
|
||||||
|
|
||||||
|
/** Fired when the relevant validator has changed */
|
||||||
|
public signal void changed(Validator validator);
|
||||||
|
|
||||||
|
/** Fired when the relevant validator has emitted the activated signal */
|
||||||
|
public signal void activated(Validator validator);
|
||||||
|
|
||||||
|
public void add_validator(Validator validator) {
|
||||||
|
validator.changed.connect(on_validator_changed);
|
||||||
|
validator.activated.connect(on_validator_activated);
|
||||||
|
this.validators.add(validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_validator_changed(Validator validator) {
|
||||||
|
this.changed(validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_validator_activated(Validator validator) {
|
||||||
|
this.activated(validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool is_valid() {
|
||||||
|
foreach (unowned var validator in this.validators) {
|
||||||
|
if (validator.is_valid)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GListModel implementation
|
||||||
|
|
||||||
|
public GLib.Type get_item_type() {
|
||||||
|
return typeof(Components.Validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint get_n_items() {
|
||||||
|
return this.validators.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GLib.Object? get_item(uint index) {
|
||||||
|
if (index >= this.validators.length)
|
||||||
|
return null;
|
||||||
|
return this.validators[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// GtkBuildable implementation
|
||||||
|
|
||||||
|
public void add_child(Gtk.Builder builder, Object child, string? type) {
|
||||||
|
unowned var validator = child as Validator;
|
||||||
|
if (validator == null) {
|
||||||
|
critical("Can't add child %p to ValidatorGroup, expected Validator instance", validator);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_validator(validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string id;
|
||||||
|
public void set_id(string id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
public unowned string get_id() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need any of these, but Vala requires us to implement them
|
||||||
|
public void custom_finished(Gtk.Builder builder, GLib.Object? child, string tagname, void* data) {}
|
||||||
|
public void custom_tag_end(Gtk.Builder builder, GLib.Object? child, string tagname, void* data) {}
|
||||||
|
public bool custom_tag_start(Gtk.Builder builder, GLib.Object? child, string tagname, out Gtk.BuildableParser parser, out void* data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public unowned GLib.Object get_internal_child(Gtk.Builder builder, string childname) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public void parser_finished(Gtk.Builder builder) {}
|
||||||
|
public void set_buildable_property(Gtk.Builder builder, string name, GLib.Value value) {}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the contents of a Gtk Entry as they are entered.
|
* Validates the contents of a Gtk Editable as they are entered.
|
||||||
*
|
*
|
||||||
* This class may be used to validate required, but otherwise free
|
* This class may be used to validate required, but otherwise free
|
||||||
* form entries. Subclasses may perform more complex and task-specific
|
* form entries. Subclasses may perform more complex and task-specific
|
||||||
|
|
@ -15,34 +15,30 @@
|
||||||
public class Components.Validator : GLib.Object {
|
public class Components.Validator : GLib.Object {
|
||||||
|
|
||||||
|
|
||||||
private const Gtk.EntryIconPosition ICON_POS =
|
|
||||||
Gtk.EntryIconPosition.SECONDARY;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state of the entry monitored by this validator.
|
* The state of the editable monitored by this validator.
|
||||||
*
|
*
|
||||||
* Only {@link VALID} can be considered strictly valid, all other
|
* Only {@link VALID} can be considered strictly valid, all other
|
||||||
* states should be treated as being invalid.
|
* states should be treated as being invalid.
|
||||||
*/
|
*/
|
||||||
public enum Validity {
|
public enum Validity {
|
||||||
/** The contents of the entry have not been validated. */
|
/** The contents of the editable have not been validated. */
|
||||||
INDETERMINATE,
|
INDETERMINATE,
|
||||||
|
|
||||||
/** The contents of the entry is valid. */
|
/** The contents of the editable is valid. */
|
||||||
VALID,
|
VALID,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The contents of the entry is being checked.
|
* The contents of the editable is being checked.
|
||||||
*
|
*
|
||||||
* See {@link validate} for the use of this value.
|
* See {@link validate} for the use of this value.
|
||||||
*/
|
*/
|
||||||
IN_PROGRESS,
|
IN_PROGRESS,
|
||||||
|
|
||||||
/** The contents of the entry is required but not present. */
|
/** The contents of the editable is required but not present. */
|
||||||
EMPTY,
|
EMPTY,
|
||||||
|
|
||||||
/** The contents of the entry is not valid. */
|
/** The contents of the editable is not valid. */
|
||||||
INVALID;
|
INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,11 +46,11 @@ public class Components.Validator : GLib.Object {
|
||||||
public enum Trigger {
|
public enum Trigger {
|
||||||
/** A manual validation was requested via {@link validate}. */
|
/** A manual validation was requested via {@link validate}. */
|
||||||
MANUAL,
|
MANUAL,
|
||||||
/** The entry's contents changed. */
|
/** The editable's contents changed. */
|
||||||
CHANGED,
|
CHANGED,
|
||||||
/** The entry lost the keyboard focus. */
|
/** The editable lost the keyboard focus. */
|
||||||
LOST_FOCUS,
|
LOST_FOCUS,
|
||||||
/** The user activated the entry. */
|
/** The user activated the editable. */
|
||||||
ACTIVATED;
|
ACTIVATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,10 +60,26 @@ public class Components.Validator : GLib.Object {
|
||||||
public string? icon_tooltip_text;
|
public string? icon_tooltip_text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The entry being monitored */
|
/** The editable being monitored */
|
||||||
public Gtk.Entry target { get; private set; }
|
public Gtk.Editable target {
|
||||||
|
get { return this._target; }
|
||||||
|
construct set {
|
||||||
|
this._target = value;
|
||||||
|
if (value is Gtk.Entry) {
|
||||||
|
this.target_helper = new EntryHelper((Gtk.Entry) value);
|
||||||
|
} else if (value is Adw.EntryRow) {
|
||||||
|
this.target_helper = new EntryRowHelper((Adw.EntryRow) value);
|
||||||
|
} else {
|
||||||
|
critical("Validator for type '%s' unsupported", value.get_type().name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Gtk.Editable _target;
|
||||||
|
protected EditableHelper target_helper;
|
||||||
|
|
||||||
/** Determines if the current state indicates the entry is valid. */
|
private Gtk.EventControllerFocus target_focus_controller = new Gtk.EventControllerFocus();
|
||||||
|
|
||||||
|
/** Determines if the current state indicates the editable is valid. */
|
||||||
public bool is_valid {
|
public bool is_valid {
|
||||||
get { return (this.state == Validity.VALID); }
|
get { return (this.state == Validity.VALID); }
|
||||||
}
|
}
|
||||||
|
|
@ -75,40 +87,22 @@ public class Components.Validator : GLib.Object {
|
||||||
/**
|
/**
|
||||||
* Determines how empty entries are treated.
|
* Determines how empty entries are treated.
|
||||||
*
|
*
|
||||||
* If true, an empty entry is considered {@link Validity.EMPTY}
|
* If true, an empty editable is considered {@link Validity.EMPTY}
|
||||||
* (i.e. invalid) else it is considered to be {@link
|
* (i.e. invalid) else it is considered to be {@link
|
||||||
* Validity.INDETERMINATE}.
|
* Validity.INDETERMINATE}.
|
||||||
*/
|
*/
|
||||||
public bool is_required { get; set; default = true; }
|
public bool is_required { get; set; default = true; }
|
||||||
|
|
||||||
/** The current validation state of the entry. */
|
/** The current validation state of the editable. */
|
||||||
public Validity state {
|
public Validity state {
|
||||||
get; private set; default = Validity.INDETERMINATE;
|
get; private set; default = Validity.INDETERMINATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The UI state to use when indeterminate. */
|
|
||||||
public UiState indeterminate_state;
|
|
||||||
|
|
||||||
/** The UI state to use when valid. */
|
|
||||||
public UiState valid_state;
|
|
||||||
|
|
||||||
/** The UI state to use when in progress. */
|
|
||||||
public UiState in_progress_state;
|
|
||||||
|
|
||||||
/** The UI state to use when empty. */
|
|
||||||
public UiState empty_state;
|
|
||||||
|
|
||||||
/** The UI state to use when invalid. */
|
|
||||||
public UiState invalid_state;
|
|
||||||
|
|
||||||
// Determines if the value has changed since last validation
|
// Determines if the value has changed since last validation
|
||||||
private bool target_changed = false;
|
private bool target_changed = false;
|
||||||
|
|
||||||
private Geary.TimeoutManager ui_update_timer;
|
private Geary.TimeoutManager ui_update_timer;
|
||||||
|
|
||||||
private Geary.TimeoutManager pulse_timer;
|
|
||||||
bool did_pulse = false;
|
|
||||||
|
|
||||||
|
|
||||||
/** Fired when the validation state changes. */
|
/** Fired when the validation state changes. */
|
||||||
public signal void state_changed(Trigger reason, Validity prev_state);
|
public signal void state_changed(Trigger reason, Validity prev_state);
|
||||||
|
|
@ -123,49 +117,28 @@ public class Components.Validator : GLib.Object {
|
||||||
public signal void focus_lost();
|
public signal void focus_lost();
|
||||||
|
|
||||||
|
|
||||||
public Validator(Gtk.Entry target) {
|
construct {
|
||||||
this.target = target;
|
|
||||||
|
|
||||||
this.ui_update_timer = new Geary.TimeoutManager.seconds(
|
this.ui_update_timer = new Geary.TimeoutManager.seconds(
|
||||||
2, on_update_ui
|
1, on_update_ui
|
||||||
);
|
);
|
||||||
|
|
||||||
this.pulse_timer = new Geary.TimeoutManager.milliseconds(
|
this.target_helper.activated.connect(on_activate);
|
||||||
200, on_pulse
|
|
||||||
);
|
|
||||||
this.pulse_timer.repetition = FOREVER;
|
|
||||||
|
|
||||||
this.indeterminate_state = {
|
|
||||||
target.get_icon_name(ICON_POS),
|
|
||||||
target.get_icon_tooltip_text(ICON_POS)
|
|
||||||
};
|
|
||||||
this.valid_state = {
|
|
||||||
target.get_icon_name(ICON_POS),
|
|
||||||
target.get_icon_tooltip_text(ICON_POS)
|
|
||||||
};
|
|
||||||
this.in_progress_state = {
|
|
||||||
target.get_icon_name(ICON_POS),
|
|
||||||
null
|
|
||||||
};
|
|
||||||
this.empty_state = { "dialog-warning-symbolic", null };
|
|
||||||
this.invalid_state = { "dialog-error-symbolic", null };
|
|
||||||
|
|
||||||
this.target.add_events(Gdk.EventMask.FOCUS_CHANGE_MASK);
|
|
||||||
this.target.activate.connect(on_activate);
|
|
||||||
this.target.changed.connect(on_changed);
|
this.target.changed.connect(on_changed);
|
||||||
this.target.focus_out_event.connect(on_focus_out);
|
this.target_focus_controller.leave.connect(on_focus_out);
|
||||||
|
this.target.add_controller(this.target_focus_controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Validator(Gtk.Editable target) {
|
||||||
|
GLib.Object(target: target);
|
||||||
}
|
}
|
||||||
|
|
||||||
~Validator() {
|
~Validator() {
|
||||||
this.target.focus_out_event.disconnect(on_focus_out);
|
|
||||||
this.target.changed.disconnect(on_changed);
|
|
||||||
this.target.activate.disconnect(on_activate);
|
|
||||||
this.ui_update_timer.reset();
|
this.ui_update_timer.reset();
|
||||||
this.pulse_timer.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers a validation of the entry.
|
* Triggers a validation of the editable.
|
||||||
*
|
*
|
||||||
* In the case of an asynchronous validation implementations,
|
* In the case of an asynchronous validation implementations,
|
||||||
* result of the validation will be known sometime after this call
|
* result of the validation will be known sometime after this call
|
||||||
|
|
@ -176,12 +149,12 @@ public class Components.Validator : GLib.Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to validate the target entry's value.
|
* Called to validate the target editable's value.
|
||||||
*
|
*
|
||||||
* This method will be called repeatedly as the user edits the
|
* This method will be called repeatedly as the user edits the
|
||||||
* value of the target entry to set the new validation {@link
|
* value of the target editable to set the new validation {@link
|
||||||
* state} given the updated value. It will *not* be called if the
|
* state} given the updated value. It will *not* be called if the
|
||||||
* entry is changed to be empty, instead the validity state will
|
* editable is changed to be empty, instead the validity state will
|
||||||
* be set based on {@link is_required}.
|
* be set based on {@link is_required}.
|
||||||
*
|
*
|
||||||
* Subclasses may override this method to implement custom
|
* Subclasses may override this method to implement custom
|
||||||
|
|
@ -195,7 +168,7 @@ public class Components.Validator : GLib.Object {
|
||||||
* with the actual result.
|
* with the actual result.
|
||||||
*
|
*
|
||||||
* The given reason specifies which user action was taken to cause
|
* The given reason specifies which user action was taken to cause
|
||||||
* the entry's value to be validated.
|
* the editable's value to be validated.
|
||||||
*
|
*
|
||||||
* By default, this always returns {@link Validity.VALID}, making
|
* By default, this always returns {@link Validity.VALID}, making
|
||||||
* it useful for required, but otherwise free-form fields only.
|
* it useful for required, but otherwise free-form fields only.
|
||||||
|
|
@ -205,7 +178,7 @@ public class Components.Validator : GLib.Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the current validation state and the entry's UI.
|
* Updates the current validation state and the editable's UI.
|
||||||
*
|
*
|
||||||
* This should only be called by subclasses that implement a
|
* This should only be called by subclasses that implement a
|
||||||
* CPU-intensive or long-running validation routine and it has
|
* CPU-intensive or long-running validation routine and it has
|
||||||
|
|
@ -263,8 +236,6 @@ public class Components.Validator : GLib.Object {
|
||||||
// no-op
|
// no-op
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (!this.pulse_timer.is_running) {
|
|
||||||
this.pulse_timer.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,66 +253,12 @@ public class Components.Validator : GLib.Object {
|
||||||
private void update_ui(Validity state) {
|
private void update_ui(Validity state) {
|
||||||
this.ui_update_timer.reset();
|
this.ui_update_timer.reset();
|
||||||
|
|
||||||
Gtk.StyleContext style = this.target.get_style_context();
|
this.target_helper.update_ui(state);
|
||||||
style.remove_class(Gtk.STYLE_CLASS_ERROR);
|
|
||||||
style.remove_class(Gtk.STYLE_CLASS_WARNING);
|
|
||||||
|
|
||||||
UiState ui = { null, null };
|
|
||||||
bool in_progress = false;
|
|
||||||
switch (state) {
|
|
||||||
case Validity.INDETERMINATE:
|
|
||||||
ui = this.indeterminate_state;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Validity.VALID:
|
|
||||||
ui = this.valid_state;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Validity.IN_PROGRESS:
|
|
||||||
in_progress = true;
|
|
||||||
ui = this.in_progress_state;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Validity.EMPTY:
|
|
||||||
style.add_class(Gtk.STYLE_CLASS_WARNING);
|
|
||||||
ui = this.empty_state;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Validity.INVALID:
|
|
||||||
style.add_class(Gtk.STYLE_CLASS_ERROR);
|
|
||||||
ui = this.invalid_state;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_progress) {
|
|
||||||
if (!this.pulse_timer.is_running) {
|
|
||||||
this.pulse_timer.start();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.pulse_timer.reset();
|
|
||||||
// If a pulse hasn't been performed (and hence the
|
|
||||||
// progress bar is not visible), setting the fraction here
|
|
||||||
// to reset it will actually cause the progress bar to
|
|
||||||
// become visible. So only reset if needed.
|
|
||||||
if (this.did_pulse) {
|
|
||||||
this.target.progress_fraction = 0.0;
|
|
||||||
this.did_pulse = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.target.set_icon_from_icon_name(ICON_POS, ui.icon_name);
|
|
||||||
this.target.set_icon_tooltip_text(
|
|
||||||
ICON_POS,
|
|
||||||
// Setting the tooltip to null or the empty string can
|
|
||||||
// cause GTK+ to setfult. See GTK+ issue #1160.
|
|
||||||
Geary.String.is_empty(ui.icon_tooltip_text)
|
|
||||||
? " " : ui.icon_tooltip_text
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_activate() {
|
private void on_activate() {
|
||||||
if (this.target_changed) {
|
if (this.target_changed) {
|
||||||
validate_entry(Trigger.ACTIVATED);
|
validate_entry(Trigger.ACTIVATED);
|
||||||
} else {
|
} else {
|
||||||
activated();
|
activated();
|
||||||
}
|
}
|
||||||
|
|
@ -351,11 +268,6 @@ public class Components.Validator : GLib.Object {
|
||||||
update_ui(this.state);
|
update_ui(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_pulse() {
|
|
||||||
this.target.progress_pulse();
|
|
||||||
this.did_pulse = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_changed() {
|
private void on_changed() {
|
||||||
this.target_changed = true;
|
this.target_changed = true;
|
||||||
validate_entry(Trigger.CHANGED);
|
validate_entry(Trigger.CHANGED);
|
||||||
|
|
@ -364,40 +276,161 @@ public class Components.Validator : GLib.Object {
|
||||||
this.ui_update_timer.start();
|
this.ui_update_timer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_focus_out() {
|
private void on_focus_out(Gtk.EventControllerFocus controller) {
|
||||||
if (this.target_changed) {
|
if (this.target_changed) {
|
||||||
// Only update if the widget has lost focus due to not being
|
// Only update if the widget has lost focus due to not being
|
||||||
// the focused widget any more, rather than the whole window
|
// the focused widget any more, rather than the whole window
|
||||||
// having lost focus.
|
// having lost focus.
|
||||||
if (!this.target.is_focus) {
|
if (!this.target.is_focus()) {
|
||||||
validate_entry(Trigger.LOST_FOCUS);
|
validate_entry(Trigger.LOST_FOCUS);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
focus_lost();
|
focus_lost();
|
||||||
}
|
}
|
||||||
return Gdk.EVENT_PROPAGATE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class to set an icon, abstracting away the underlying API of the
|
||||||
|
* Gtk.Editable implementation.
|
||||||
|
*/
|
||||||
|
protected abstract class Components.EditableHelper : Object {
|
||||||
|
|
||||||
|
/** Tooltip text in case the validator returns an invalid state */
|
||||||
|
public string? invalid_tooltip_text { get; set; default = null; }
|
||||||
|
|
||||||
|
/** Tooltip text in case the validator returns an empty state */
|
||||||
|
public string? empty_tooltip_text { get; set; default = null; }
|
||||||
|
|
||||||
|
/** Emitted if the editable has been activated */
|
||||||
|
public signal void activated();
|
||||||
|
|
||||||
|
/** Sets an error icon with the given name and tooltip */
|
||||||
|
public abstract void update_ui(Validator.Validity state);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class Components.EntryHelper : EditableHelper {
|
||||||
|
|
||||||
|
private unowned Gtk.Entry entry;
|
||||||
|
|
||||||
|
public EntryHelper(Gtk.Entry entry) {
|
||||||
|
this.entry = entry;
|
||||||
|
this.entry.activate.connect((e) => this.activated());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void update_ui(Validator.Validity state) {
|
||||||
|
this.entry.remove_css_class("error");
|
||||||
|
this.entry.remove_css_class("warning");
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case Validator.Validity.INDETERMINATE:
|
||||||
|
case Validator.Validity.VALID:
|
||||||
|
// Reset
|
||||||
|
this.entry.secondary_icon_name = "";
|
||||||
|
this.entry.secondary_icon_tooltip_text = "";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Validator.Validity.IN_PROGRESS:
|
||||||
|
this.entry.secondary_icon_paintable = new Adw.SpinnerPaintable(this.entry);
|
||||||
|
this.entry.secondary_icon_tooltip_text = _("Validating");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Validator.Validity.EMPTY:
|
||||||
|
this.entry.add_css_class("warning");
|
||||||
|
this.entry.secondary_icon_name = "dialog-warning-symbolic";
|
||||||
|
this.entry.secondary_icon_tooltip_text = this.empty_tooltip_text ?? "";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Validator.Validity.INVALID:
|
||||||
|
this.entry.add_css_class("error");
|
||||||
|
this.entry.secondary_icon_name = "dialog-error-symbolic";
|
||||||
|
this.entry.secondary_icon_tooltip_text = this.invalid_tooltip_text ?? "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Components.EntryRowHelper : EditableHelper {
|
||||||
|
|
||||||
|
private unowned Adw.EntryRow row;
|
||||||
|
|
||||||
|
private unowned Adw.Spinner? spinner = null;
|
||||||
|
private unowned Gtk.Image? error_image = null;
|
||||||
|
|
||||||
|
public EntryRowHelper(Adw.EntryRow row) {
|
||||||
|
this.row = row;
|
||||||
|
this.row.entry_activated.connect((e) => this.activated());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void update_ui(Validator.Validity state) {
|
||||||
|
reset();
|
||||||
|
|
||||||
|
this.row.remove_css_class("error");
|
||||||
|
this.row.remove_css_class("warning");
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case Validator.Validity.INDETERMINATE:
|
||||||
|
case Validator.Validity.VALID:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Validator.Validity.IN_PROGRESS:
|
||||||
|
var spinner = new Adw.Spinner();
|
||||||
|
spinner.tooltip_text = _("Validating");
|
||||||
|
this.row.add_suffix(spinner);
|
||||||
|
this.spinner = spinner;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Validator.Validity.EMPTY:
|
||||||
|
this.row.add_css_class("warning");
|
||||||
|
var img = new Gtk.Image.from_icon_name("dialog-warning-symbolic");
|
||||||
|
img.tooltip_text = this.empty_tooltip_text ?? "";
|
||||||
|
this.row.add_suffix(img);
|
||||||
|
this.error_image = img;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Validator.Validity.INVALID:
|
||||||
|
this.row.add_css_class("error");
|
||||||
|
var img = new Gtk.Image.from_icon_name("dialog-error-symbolic");
|
||||||
|
img.tooltip_text = this.invalid_tooltip_text ?? "";
|
||||||
|
this.row.add_suffix(img);
|
||||||
|
this.error_image = img;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
if (this.spinner != null) {
|
||||||
|
this.row.remove(this.spinner);
|
||||||
|
this.spinner = null;
|
||||||
|
}
|
||||||
|
if (this.error_image != null) {
|
||||||
|
this.row.remove(this.error_image);
|
||||||
|
this.error_image = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A validator for GTK Entry widgets that contain an email address.
|
* A validator for GTK Editable widgets that contain an email address.
|
||||||
*/
|
*/
|
||||||
public class Components.EmailValidator : Validator {
|
public class Components.EmailValidator : Validator {
|
||||||
|
|
||||||
public EmailValidator(Gtk.Entry target) {
|
construct {
|
||||||
base(target);
|
// Translators: Tooltip used when an editable requires a valid
|
||||||
|
|
||||||
// Translators: Tooltip used when an entry requires a valid
|
|
||||||
// email address to be entered, but one is not provided.
|
// email address to be entered, but one is not provided.
|
||||||
this.empty_state.icon_tooltip_text = _("An email address is required");
|
this.target_helper.empty_tooltip_text = _("An email address is required");
|
||||||
|
|
||||||
// Translators: Tooltip used when an entry requires a valid
|
// Translators: Tooltip used when an editablerequires a valid
|
||||||
// email address to be entered, but the address is invalid.
|
// email address to be entered, but the address is invalid.
|
||||||
this.invalid_state.icon_tooltip_text = _("Not a valid email address");
|
this.target_helper.invalid_tooltip_text = _("Not a valid email address");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EmailValidator(Gtk.Editable target) {
|
||||||
|
GLib.Object(target: target);
|
||||||
|
}
|
||||||
|
|
||||||
protected override Validator.Validity do_validate(string value,
|
protected override Validator.Validity do_validate(string value,
|
||||||
Validator.Trigger reason) {
|
Validator.Trigger reason) {
|
||||||
|
|
@ -409,9 +442,9 @@ public class Components.EmailValidator : Validator {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A validator for GTK Entry widgets that contain a network address.
|
* A validator for Gtk.Editable widgets that contain a network address.
|
||||||
*
|
*
|
||||||
* This attempts parse the entry value as a host name or IP address
|
* This attempts parse the editable value as a host name or IP address
|
||||||
* with an optional port, then resolve the host name if
|
* with an optional port, then resolve the host name if
|
||||||
* needed. Parsing is performed by {@link GLib.NetworkAddress.parse}
|
* needed. Parsing is performed by {@link GLib.NetworkAddress.parse}
|
||||||
* to parse the user input, hence it may be specified in any form
|
* to parse the user input, hence it may be specified in any form
|
||||||
|
|
@ -426,27 +459,30 @@ public class Components.NetworkAddressValidator : Validator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The default port used when parsing the address. */
|
/** The default port used when parsing the address. */
|
||||||
public uint16 default_port { get; private set; }
|
public uint16 default_port { get; construct set; }
|
||||||
|
|
||||||
private GLib.Resolver resolver;
|
private GLib.Resolver resolver;
|
||||||
private GLib.Cancellable? cancellable = null;
|
private GLib.Cancellable? cancellable = null;
|
||||||
|
|
||||||
|
construct {
|
||||||
public NetworkAddressValidator(Gtk.Entry target, uint16 default_port = 0) {
|
|
||||||
base(target);
|
|
||||||
this.default_port = default_port;
|
|
||||||
|
|
||||||
this.resolver = GLib.Resolver.get_default();
|
this.resolver = GLib.Resolver.get_default();
|
||||||
|
|
||||||
// Translators: Tooltip used when an entry requires a valid,
|
// Translators: Tooltip used when an editable requires a valid,
|
||||||
// resolvable server name to be entered, but one is not
|
// resolvable server name to be entered, but one is not
|
||||||
// provided.
|
// provided.
|
||||||
this.empty_state.icon_tooltip_text = _("A server name is required");
|
this.target_helper.empty_tooltip_text = _("A server name is required");
|
||||||
|
|
||||||
// Translators: Tooltip used when an entry requires a valid
|
// Translators: Tooltip used when an editable requires a valid
|
||||||
// server name to be entered, but it was unable to be
|
// server name to be entered, but it was unable to be
|
||||||
// looked-up in the DNS.
|
// looked-up in the DNS.
|
||||||
this.invalid_state.icon_tooltip_text = _("Could not look up server name");
|
this.target_helper.invalid_tooltip_text = _("Could not look up server name");
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkAddressValidator(Gtk.Editable target, uint16 default_port = 0) {
|
||||||
|
GLib.Object(
|
||||||
|
target: target,
|
||||||
|
default_port: default_port
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,23 +48,6 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
||||||
private const string USER_CSS_LEGACY = "user-message.css";
|
private const string USER_CSS_LEGACY = "user-message.css";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Workaround WK binding ctor not accepting any args
|
|
||||||
private class WebsiteDataManager : WebKit.WebsiteDataManager {
|
|
||||||
|
|
||||||
public WebsiteDataManager(string base_cache_directory) {
|
|
||||||
// Use the cache dir for both cache and data since a)
|
|
||||||
// emails shouldn't be storing data anyway, and b) so WK
|
|
||||||
// doesn't use the default, shared data dir.
|
|
||||||
Object(
|
|
||||||
base_cache_directory: base_cache_directory,
|
|
||||||
base_data_directory: base_cache_directory
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static WebKit.WebContext? default_context = null;
|
private static WebKit.WebContext? default_context = null;
|
||||||
|
|
||||||
private static GenericArray<WebKit.UserStyleSheet> styles = null;
|
private static GenericArray<WebKit.UserStyleSheet> styles = null;
|
||||||
|
|
@ -76,16 +59,12 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
||||||
* Initialises WebKit.WebContext for use by the client.
|
* Initialises WebKit.WebContext for use by the client.
|
||||||
*/
|
*/
|
||||||
public static void init_web_context(Application.Configuration config,
|
public static void init_web_context(Application.Configuration config,
|
||||||
File web_extension_dir,
|
File web_extension_dir) {
|
||||||
File cache_dir,
|
WebKit.WebContext context = new WebKit.WebContext();
|
||||||
bool sandboxed=true) {
|
|
||||||
WebsiteDataManager data_manager = new WebsiteDataManager(cache_dir.get_path());
|
// Configure WebProcess sandboxing
|
||||||
WebKit.WebContext context = new WebKit.WebContext.with_website_data_manager(data_manager);
|
context.add_path_to_sandbox(web_extension_dir.get_path(), true);
|
||||||
// Enable WebProcess sandboxing
|
|
||||||
if (sandboxed) {
|
|
||||||
context.add_path_to_sandbox(web_extension_dir.get_path(), true);
|
|
||||||
context.set_sandbox_enabled(true);
|
|
||||||
}
|
|
||||||
// Use the doc browser model so that we get some caching of
|
// Use the doc browser model so that we get some caching of
|
||||||
// resources between email body loads.
|
// resources between email body loads.
|
||||||
context.set_cache_model(WebKit.CacheModel.DOCUMENT_BROWSER);
|
context.set_cache_model(WebKit.CacheModel.DOCUMENT_BROWSER);
|
||||||
|
|
@ -102,11 +81,11 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
||||||
view.handle_internal_request(req);
|
view.handle_internal_request(req);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
context.initialize_web_extensions.connect((context) => {
|
context.initialize_web_process_extensions.connect((context) => {
|
||||||
context.set_web_extensions_directory(
|
context.set_web_process_extensions_directory(
|
||||||
web_extension_dir.get_path()
|
web_extension_dir.get_path()
|
||||||
);
|
);
|
||||||
context.set_web_extensions_initialization_user_data(
|
context.set_web_process_extensions_initialization_user_data(
|
||||||
new Variant.boolean(config.enable_debug)
|
new Variant.boolean(config.enable_debug)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -196,6 +175,7 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static inline uint to_wk2_font_size(Pango.FontDescription font) {
|
private static inline uint to_wk2_font_size(Pango.FontDescription font) {
|
||||||
|
// XXX GTK4 I have no idea what to do here
|
||||||
double size = font.get_size();
|
double size = font.get_size();
|
||||||
if (!font.get_size_is_absolute()) {
|
if (!font.get_size_is_absolute()) {
|
||||||
size = size / Pango.SCALE;
|
size = size / Pango.SCALE;
|
||||||
|
|
@ -331,6 +311,7 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
||||||
|
|
||||||
|
|
||||||
protected WebView(Application.Configuration config,
|
protected WebView(Application.Configuration config,
|
||||||
|
GLib.File? cache_dir = null,
|
||||||
WebKit.UserContentManager? custom_manager = null,
|
WebKit.UserContentManager? custom_manager = null,
|
||||||
WebView? related = null) {
|
WebView? related = null) {
|
||||||
WebKit.Settings setts = new WebKit.Settings();
|
WebKit.Settings setts = new WebKit.Settings();
|
||||||
|
|
@ -345,9 +326,6 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
||||||
setts.enable_media_stream = false;
|
setts.enable_media_stream = false;
|
||||||
setts.enable_offline_web_application_cache = false;
|
setts.enable_offline_web_application_cache = false;
|
||||||
setts.enable_page_cache = false;
|
setts.enable_page_cache = false;
|
||||||
#if WEBKIT_PLUGINS_SUPPORTED
|
|
||||||
setts.enable_plugins = false;
|
|
||||||
#endif
|
|
||||||
setts.hardware_acceleration_policy =
|
setts.hardware_acceleration_policy =
|
||||||
WebKit.HardwareAccelerationPolicy.NEVER;
|
WebKit.HardwareAccelerationPolicy.NEVER;
|
||||||
setts.javascript_can_access_clipboard = true;
|
setts.javascript_can_access_clipboard = true;
|
||||||
|
|
@ -376,10 +354,23 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
||||||
WebView.styles.foreach(style => content_manager.add_style_sheet(style));
|
WebView.styles.foreach(style => content_manager.add_style_sheet(style));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use cache dir for both cache & data since a) emails shouldn't be
|
||||||
|
// storing data anyway, and b) so WK doesn't use the default, shared datadir
|
||||||
|
WebKit.NetworkSession nw_session;
|
||||||
|
if (cache_dir != null) {
|
||||||
|
nw_session = new WebKit.NetworkSession(
|
||||||
|
cache_dir.get_path(),
|
||||||
|
cache_dir.get_path()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
nw_session = new WebKit.NetworkSession.ephemeral();
|
||||||
|
}
|
||||||
|
|
||||||
Object(
|
Object(
|
||||||
settings: setts,
|
settings: setts,
|
||||||
user_content_manager: content_manager,
|
user_content_manager: content_manager,
|
||||||
web_context: WebView.default_context
|
web_context: WebView.default_context,
|
||||||
|
network_session: nw_session
|
||||||
);
|
);
|
||||||
base_ref();
|
base_ref();
|
||||||
init(config);
|
init(config);
|
||||||
|
|
@ -408,9 +399,9 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
||||||
base_unref();
|
base_unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void destroy() {
|
public override void dispose() {
|
||||||
this.message_handlers.clear();
|
this.message_handlers.clear();
|
||||||
base.destroy();
|
base.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -668,7 +659,9 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
||||||
} else if (this.zoom_level > ZOOM_MAX) {
|
} else if (this.zoom_level > ZOOM_MAX) {
|
||||||
this.zoom_level = ZOOM_MAX;
|
this.zoom_level = ZOOM_MAX;
|
||||||
}
|
}
|
||||||
this.scroll_event.connect(on_scroll_event);
|
var scroll_controller = new Gtk.EventControllerScroll(Gtk.EventControllerScrollFlags.VERTICAL);
|
||||||
|
scroll_controller.scroll.connect(on_scroll_event);
|
||||||
|
add_controller(scroll_controller);
|
||||||
|
|
||||||
// Watch desktop font settings
|
// Watch desktop font settings
|
||||||
Settings system_settings = config.gnome_interface;
|
Settings system_settings = config.gnome_interface;
|
||||||
|
|
@ -797,15 +790,18 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
||||||
return Gdk.EVENT_STOP;
|
return Gdk.EVENT_STOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_scroll_event(Gdk.EventScroll event) {
|
private bool on_scroll_event(Gtk.EventControllerScroll scroll_controller, double dx, double dy) {
|
||||||
if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) {
|
var event = (Gdk.ScrollEvent) scroll_controller.get_current_event();
|
||||||
|
if (Gdk.ModifierType.CONTROL_MASK in event.get_modifier_state()) {
|
||||||
double dir = 0;
|
double dir = 0;
|
||||||
if (event.direction == Gdk.ScrollDirection.UP)
|
if (event.get_direction() == Gdk.ScrollDirection.UP)
|
||||||
dir = -1;
|
dir = -1;
|
||||||
else if (event.direction == Gdk.ScrollDirection.DOWN)
|
else if (event.get_direction() == Gdk.ScrollDirection.DOWN)
|
||||||
dir = 1;
|
dir = 1;
|
||||||
else if (event.direction == Gdk.ScrollDirection.SMOOTH)
|
else if (event.get_direction() == Gdk.ScrollDirection.SMOOTH) {
|
||||||
dir = event.delta_y;
|
double delta_x;
|
||||||
|
event.get_deltas(out delta_x, out dir);
|
||||||
|
}
|
||||||
|
|
||||||
if (dir < 0) {
|
if (dir < 0) {
|
||||||
zoom_in();
|
zoom_in();
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,7 @@ public class FolderPopover : Gtk.Popover {
|
||||||
}
|
}
|
||||||
|
|
||||||
var row = new FolderPopoverRow(context, map);
|
var row = new FolderPopoverRow(context, map);
|
||||||
row.show();
|
list_box.append(row);
|
||||||
list_box.add(row);
|
|
||||||
list_box.invalidate_sort();
|
list_box.invalidate_sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,7 +89,9 @@ public class FolderPopover : Gtk.Popover {
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_unmap(Gtk.Widget widget) {
|
private void on_unmap(Gtk.Widget widget) {
|
||||||
list_box.foreach((row) => list_box.remove(row));
|
unowned var row = this.list_box.get_row_at_index(0);
|
||||||
|
while (row != null)
|
||||||
|
this.list_box.remove(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
|
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
|
||||||
*
|
|
||||||
* This software is licensed under the GNU Lesser General Public License
|
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Singleton class to hold icons.
|
|
||||||
public class IconFactory {
|
|
||||||
public const Gtk.IconSize ICON_TOOLBAR = Gtk.IconSize.LARGE_TOOLBAR;
|
|
||||||
public const Gtk.IconSize ICON_SIDEBAR = Gtk.IconSize.MENU;
|
|
||||||
|
|
||||||
|
|
||||||
public static IconFactory? instance { get; private set; }
|
|
||||||
|
|
||||||
|
|
||||||
public static void init(GLib.File resource_directory) {
|
|
||||||
IconFactory.instance = new IconFactory(resource_directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public const int UNREAD_ICON_SIZE = 16;
|
|
||||||
public const int STAR_ICON_SIZE = 16;
|
|
||||||
|
|
||||||
private Gtk.IconTheme icon_theme { get; private set; }
|
|
||||||
|
|
||||||
private File icons_dir;
|
|
||||||
|
|
||||||
// Creates the icon factory.
|
|
||||||
private IconFactory(GLib.File resource_directory) {
|
|
||||||
icons_dir = resource_directory.get_child("icons");
|
|
||||||
icon_theme = Gtk.IconTheme.get_default();
|
|
||||||
icon_theme.append_search_path(icons_dir.get_path());
|
|
||||||
}
|
|
||||||
|
|
||||||
private int icon_size_to_pixels(Gtk.IconSize icon_size) {
|
|
||||||
switch (icon_size) {
|
|
||||||
case ICON_SIDEBAR:
|
|
||||||
return 16;
|
|
||||||
|
|
||||||
case ICON_TOOLBAR:
|
|
||||||
default:
|
|
||||||
return 24;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Icon get_theme_icon(string name) {
|
|
||||||
return new ThemedIcon(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Icon get_custom_icon(string name, Gtk.IconSize size) {
|
|
||||||
int pixels = icon_size_to_pixels(size);
|
|
||||||
|
|
||||||
// Try sized icon first.
|
|
||||||
File icon_file = icons_dir.get_child("%dx%d".printf(pixels, pixels)).get_child(
|
|
||||||
"%s.svg".printf(name));
|
|
||||||
|
|
||||||
// If that wasn't found, try a non-sized icon.
|
|
||||||
if (!icon_file.query_exists())
|
|
||||||
icon_file = icons_dir.get_child("%s.svg".printf(name));
|
|
||||||
|
|
||||||
return new FileIcon(icon_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempts to load and return the missing image icon.
|
|
||||||
private Gdk.Pixbuf? get_missing_icon(int size, Gtk.IconLookupFlags flags = 0) {
|
|
||||||
try {
|
|
||||||
return icon_theme.load_icon("image-missing", size, flags);
|
|
||||||
} catch (Error err) {
|
|
||||||
warning("Couldn't load image-missing icon: %s", err.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If that fails... well they're out of luck.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Gtk.IconInfo? lookup_icon(string icon_name, int size, Gtk.IconLookupFlags flags = 0) {
|
|
||||||
Gtk.IconInfo? icon_info = icon_theme.lookup_icon(icon_name, size, flags);
|
|
||||||
if (icon_info == null) {
|
|
||||||
icon_info = icon_theme.lookup_icon("text-x-generic-symbolic", size, flags);
|
|
||||||
}
|
|
||||||
return icon_info;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GTK+ 3.14 no longer scales icons via the IconInfo, so perform manually until we
|
|
||||||
// properly install the icons as per 3.14's expectations.
|
|
||||||
private Gdk.Pixbuf aspect_scale_down_pixbuf(Gdk.Pixbuf pixbuf, int size) {
|
|
||||||
if (pixbuf.width <= size && pixbuf.height <= size)
|
|
||||||
return pixbuf;
|
|
||||||
|
|
||||||
int scaled_width, scaled_height;
|
|
||||||
if (pixbuf.width >= pixbuf.height) {
|
|
||||||
double aspect = (double) size / (double) pixbuf.width;
|
|
||||||
scaled_width = size;
|
|
||||||
scaled_height = (int) Math.round((double) pixbuf.height * aspect);
|
|
||||||
} else {
|
|
||||||
double aspect = (double) size / (double) pixbuf.height;
|
|
||||||
scaled_width = (int) Math.round((double) pixbuf.width * aspect);
|
|
||||||
scaled_height = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pixbuf.scale_simple(scaled_width, scaled_height, Gdk.InterpType.BILINEAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Gdk.Pixbuf? load_symbolic(string icon_name, int size, Gtk.StyleContext style,
|
|
||||||
Gtk.IconLookupFlags flags = 0) {
|
|
||||||
Gtk.IconInfo? icon_info = icon_theme.lookup_icon(icon_name, size, flags);
|
|
||||||
|
|
||||||
// Attempt to load as a symbolic icon.
|
|
||||||
if (icon_info != null) {
|
|
||||||
try {
|
|
||||||
return aspect_scale_down_pixbuf(icon_info.load_symbolic_for_context(style), size);
|
|
||||||
} catch (Error e) {
|
|
||||||
message("Couldn't load icon: %s", e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default: missing image icon.
|
|
||||||
return get_missing_icon(size, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads a symbolic icon into a pixbuf, where the color-key has been switched to the provided
|
|
||||||
* color.
|
|
||||||
*/
|
|
||||||
public Gdk.Pixbuf? load_symbolic_colored(string icon_name, int size, Gdk.RGBA color,
|
|
||||||
Gtk.IconLookupFlags flags = 0) {
|
|
||||||
Gtk.IconInfo? icon_info = icon_theme.lookup_icon(icon_name, size, flags);
|
|
||||||
|
|
||||||
// Attempt to load as a symbolic icon.
|
|
||||||
if (icon_info != null) {
|
|
||||||
try {
|
|
||||||
return aspect_scale_down_pixbuf(icon_info.load_symbolic(color), size);
|
|
||||||
} catch (Error e) {
|
|
||||||
warning("Couldn't load icon: %s", e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Default: missing image icon.
|
|
||||||
return get_missing_icon(size, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -7,28 +7,35 @@
|
||||||
/**
|
/**
|
||||||
* Adapts a progress bar to automatically display progress of a Geary.ProgressMonitor.
|
* Adapts a progress bar to automatically display progress of a Geary.ProgressMonitor.
|
||||||
*/
|
*/
|
||||||
public class MonitoredProgressBar : Gtk.ProgressBar {
|
public class MonitoredProgressBar : Adw.Bin {
|
||||||
private Geary.ProgressMonitor? monitor = null;
|
private Geary.ProgressMonitor? monitor = null;
|
||||||
|
|
||||||
|
private Gtk.ProgressBar progress_bar;
|
||||||
|
|
||||||
|
construct {
|
||||||
|
this.progress_bar = new Gtk.ProgressBar();
|
||||||
|
this.child = this.progress_bar;
|
||||||
|
}
|
||||||
|
|
||||||
public void set_progress_monitor(Geary.ProgressMonitor monitor) {
|
public void set_progress_monitor(Geary.ProgressMonitor monitor) {
|
||||||
this.monitor = monitor;
|
this.monitor = monitor;
|
||||||
monitor.start.connect(on_start);
|
monitor.start.connect(on_start);
|
||||||
monitor.finish.connect(on_finish);
|
monitor.finish.connect(on_finish);
|
||||||
monitor.update.connect(on_update);
|
monitor.update.connect(on_update);
|
||||||
|
|
||||||
fraction = monitor.progress;
|
this.progress_bar.fraction = monitor.progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_start() {
|
private void on_start() {
|
||||||
fraction = 0.0;
|
this.progress_bar.fraction = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_update(double total_progress, double change, Geary.ProgressMonitor monitor) {
|
private void on_update(double total_progress, double change, Geary.ProgressMonitor monitor) {
|
||||||
fraction = total_progress;
|
this.progress_bar.fraction = total_progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_finish() {
|
private void on_finish() {
|
||||||
fraction = 1.0;
|
this.progress_bar.fraction = 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,16 @@
|
||||||
/**
|
/**
|
||||||
* Adapts a progress spinner to automatically display progress of a Geary.ProgressMonitor.
|
* Adapts a progress spinner to automatically display progress of a Geary.ProgressMonitor.
|
||||||
*/
|
*/
|
||||||
public class MonitoredSpinner : Gtk.Spinner {
|
public class MonitoredSpinner : Adw.Bin {
|
||||||
private Geary.ProgressMonitor? monitor = null;
|
private Geary.ProgressMonitor? monitor = null;
|
||||||
|
|
||||||
|
private Adw.Spinner spinner;
|
||||||
|
|
||||||
|
construct {
|
||||||
|
this.spinner = new Adw.Spinner();
|
||||||
|
this.child = spinner;
|
||||||
|
}
|
||||||
|
|
||||||
public void set_progress_monitor(Geary.ProgressMonitor? monitor) {
|
public void set_progress_monitor(Geary.ProgressMonitor? monitor) {
|
||||||
if (monitor != null) {
|
if (monitor != null) {
|
||||||
this.monitor = monitor;
|
this.monitor = monitor;
|
||||||
|
|
@ -17,8 +24,7 @@ public class MonitoredSpinner : Gtk.Spinner {
|
||||||
monitor.finish.connect(on_stop);
|
monitor.finish.connect(on_stop);
|
||||||
} else {
|
} else {
|
||||||
this.monitor = null;
|
this.monitor = null;
|
||||||
stop();
|
this.spinner.visible = false;
|
||||||
hide();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,13 +34,11 @@ public class MonitoredSpinner : Gtk.Spinner {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_start() {
|
private void on_start() {
|
||||||
start();
|
this.spinner.visible = true;
|
||||||
show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_stop() {
|
private void on_stop() {
|
||||||
stop();
|
this.spinner.visible = false;
|
||||||
hide();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
457
src/client/composer/composer-addresses-row.vala
Normal file
457
src/client/composer/composer-addresses-row.vala
Normal file
|
|
@ -0,0 +1,457 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Software Freedom Conservancy Inc.
|
||||||
|
* Copyright © 2025 Niels De Graef <nielsdegraef@gmail.com>
|
||||||
|
*
|
||||||
|
* This software is licensed under the GNU Lesser General Public License
|
||||||
|
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A widget that allows a user to create a list of email addresses
|
||||||
|
* (for example a "CC" row).
|
||||||
|
*
|
||||||
|
*XXX we need to put an EntryUndo back here
|
||||||
|
*/
|
||||||
|
public class Composer.AddressesRow : Adw.EntryRow, Geary.BaseInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of email addresses.
|
||||||
|
*
|
||||||
|
* Check the "is-valid" property to see if they are actually valid.
|
||||||
|
*
|
||||||
|
* Manually setting this property will override any text that was there before.
|
||||||
|
*/
|
||||||
|
public Geary.RFC822.MailboxAddresses addresses {
|
||||||
|
get { return this._addresses; }
|
||||||
|
set {
|
||||||
|
this._addresses = value;
|
||||||
|
validate_addresses();
|
||||||
|
this.text = value.to_full_display();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Geary.RFC822.MailboxAddresses _addresses = new Geary.RFC822.MailboxAddresses();
|
||||||
|
|
||||||
|
/** Determines if the entry contains only valid email addresses (and is not empty) */
|
||||||
|
public bool is_valid { get; private set; default = false; }
|
||||||
|
|
||||||
|
public bool is_empty {
|
||||||
|
//XXX could this be this.text.length != 0 insteaD?
|
||||||
|
get { return this.addresses.is_empty; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text between the start of the entry or end of the previous email
|
||||||
|
// address and the current position of the cursor, if any.
|
||||||
|
// This will be used for searching a match in the contact list.
|
||||||
|
//XXX maybe replace this with a filter?
|
||||||
|
public string search_key {
|
||||||
|
get { return this._search_key; }
|
||||||
|
set {
|
||||||
|
if (this._search_key == value)
|
||||||
|
return;
|
||||||
|
string old_value = this._search_key;
|
||||||
|
this._search_key = value;
|
||||||
|
update_search_filter(old_value, value);
|
||||||
|
notify_property("search-key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private string _search_key = "";
|
||||||
|
|
||||||
|
// The list of (possibly incomplete) email addresses
|
||||||
|
private GenericArray<string> addresses_raw = new GenericArray<string>();
|
||||||
|
// Index in addressew_raw of the email address the cursor is currently at
|
||||||
|
private int cursor_at_address = 0;
|
||||||
|
|
||||||
|
public Application.ContactStore? contacts { get; set; default = null; }
|
||||||
|
|
||||||
|
private unowned AddressSuggestionPopover popover;
|
||||||
|
|
||||||
|
static construct {
|
||||||
|
set_css_name("geary-composer-widget-header-row");
|
||||||
|
}
|
||||||
|
|
||||||
|
construct {
|
||||||
|
this.changed.connect(on_changed);
|
||||||
|
|
||||||
|
var popover = new AddressSuggestionPopover();
|
||||||
|
bind_property("contacts", popover, "contacts", BindingFlags.SYNC_CREATE);
|
||||||
|
popover.selected_address.connect(on_address_suggestion_selected);
|
||||||
|
popover.set_parent(this);
|
||||||
|
this.popover = popover;
|
||||||
|
|
||||||
|
// We can't use the default autohide behavior since it grabs focus,
|
||||||
|
// which we don't want (as the user should be able to continue typing).
|
||||||
|
popover.autohide = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressesRow(string title) {
|
||||||
|
Object(title: title);
|
||||||
|
base_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
~AddressesRow() {
|
||||||
|
base_unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validate_addresses() {
|
||||||
|
bool is_valid = !this._addresses.is_empty;
|
||||||
|
foreach (Geary.RFC822.MailboxAddress address in this.addresses) {
|
||||||
|
if (!address.is_valid()) {
|
||||||
|
is_valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.is_valid = is_valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_changed() {
|
||||||
|
update_addresses();
|
||||||
|
update_validity();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_addresses() {
|
||||||
|
string current_key = "";
|
||||||
|
this.cursor_at_address = 0;
|
||||||
|
this.addresses_raw.length = 0;
|
||||||
|
|
||||||
|
// NB: Do not strip any white space from the addresses,
|
||||||
|
// otherwise we won't be able to accurately insert
|
||||||
|
// addresses in the middle of the list in
|
||||||
|
// ::insert_address_at_cursor.
|
||||||
|
|
||||||
|
int current_char = 0;
|
||||||
|
unichar c = 0;
|
||||||
|
int start_idx = 0;
|
||||||
|
int next_idx = 0;
|
||||||
|
bool in_quote = false;
|
||||||
|
while (this.text.get_next_char(ref next_idx, out c)) {
|
||||||
|
if (current_char == (this.text.char_count() - 1) &&
|
||||||
|
current_char != 0) {
|
||||||
|
if (c != ',') {
|
||||||
|
// Strip whitespace here though so it does not
|
||||||
|
// interfere with search and highlighting.
|
||||||
|
current_key = this.text.slice(
|
||||||
|
start_idx, next_idx
|
||||||
|
).strip();
|
||||||
|
}
|
||||||
|
// We're in the middle of the address, so it
|
||||||
|
// hasn't yet been added to the list and hence we
|
||||||
|
// don't need to subtract 1 from its size here
|
||||||
|
this.cursor_at_address = this.addresses_raw.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case ',':
|
||||||
|
if (!in_quote) {
|
||||||
|
// Don't include the comma in the address
|
||||||
|
string address = this.text.slice(start_idx, next_idx - 1);
|
||||||
|
this.addresses_raw.add(address);
|
||||||
|
// Don't include it in the next one, either
|
||||||
|
start_idx = next_idx;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
in_quote = !in_quote;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_char++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any remaining text after the last comma
|
||||||
|
string address = this.text.substring(start_idx);
|
||||||
|
this.addresses_raw.add(address);
|
||||||
|
|
||||||
|
// XXX we probably want to do this with a timeout
|
||||||
|
// Update current key
|
||||||
|
this.search_key = current_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_validity() {
|
||||||
|
if (Geary.String.is_empty_or_whitespace(text)) {
|
||||||
|
this._addresses = new Geary.RFC822.MailboxAddresses();
|
||||||
|
this.is_valid = false;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
this._addresses =
|
||||||
|
new Geary.RFC822.MailboxAddresses.from_rfc822_string(text);
|
||||||
|
this.is_valid = true;
|
||||||
|
} catch (Geary.RFC822.Error err) {
|
||||||
|
this._addresses = new Geary.RFC822.MailboxAddresses();
|
||||||
|
this.is_valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//XXX we should make this conditional
|
||||||
|
notify_property("addresses");
|
||||||
|
notify_property("is-valid");
|
||||||
|
notify_property("is-empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_search_filter(string old_value, string new_value) {
|
||||||
|
if (this.contacts == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (new_value.length > 3) {
|
||||||
|
this.popover.search_contacts.begin(new_value, null, (obj, res) => {
|
||||||
|
this.popover.search_contacts.end(res);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.popover.clear_suggestions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_address_suggestion_selected(AddressSuggestionPopover popover,
|
||||||
|
Geary.RFC822.MailboxAddress address) {
|
||||||
|
insert_address_at_cursor(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insert_address_at_cursor(Geary.RFC822.MailboxAddress mailbox) {
|
||||||
|
// 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 = 0;
|
||||||
|
if (this.cursor_at_address > 0) {
|
||||||
|
// Address parts don't contain commas, so need to add
|
||||||
|
// an char width for it. Don't need to worry about
|
||||||
|
// spaces because they are preserved by
|
||||||
|
// ::update_addresses.
|
||||||
|
start_char++;
|
||||||
|
for (uint i = 0; i < this.cursor_at_address; i++) {
|
||||||
|
start_char += this.addresses_raw[i].char_count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int end_char = get_position();
|
||||||
|
|
||||||
|
// Format and use the selected address
|
||||||
|
string formatted = mailbox.to_full_display();
|
||||||
|
if (this.cursor_at_address != 0) {
|
||||||
|
// Isn't the first address, so add some whitespace to
|
||||||
|
// pad it out
|
||||||
|
formatted = " " + formatted;
|
||||||
|
}
|
||||||
|
if (get_position() < this.text.char_count() &&
|
||||||
|
this.addresses_raw[this.cursor_at_address].strip() !=
|
||||||
|
this.search_key.strip()) {
|
||||||
|
// Isn't at the end of the entry, and the address
|
||||||
|
// under the cursor does not simply consist of the
|
||||||
|
// lookup key (i.e. is effectively already empty
|
||||||
|
// otherwise), so add a comma to separate this address
|
||||||
|
// from the next one
|
||||||
|
formatted = formatted + ", ";
|
||||||
|
}
|
||||||
|
this.addresses_raw.insert(this.cursor_at_address, formatted);
|
||||||
|
|
||||||
|
// Update the entry text
|
||||||
|
if (start_char < end_char) {
|
||||||
|
delete_text(start_char, end_char);
|
||||||
|
}
|
||||||
|
insert_text(formatted, -1, 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.
|
||||||
|
if (start_char < this.text.char_count()) {
|
||||||
|
start_char += 2;
|
||||||
|
}
|
||||||
|
set_position(start_char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper object to list not just the addresses but also their related contact
|
||||||
|
*/
|
||||||
|
public class ContactAddressItem : GLib.Object {
|
||||||
|
public Application.Contact contact { get; construct set; }
|
||||||
|
public Geary.RFC822.MailboxAddress address { get; construct set; }
|
||||||
|
|
||||||
|
public ContactAddressItem(Application.Contact contact,
|
||||||
|
Geary.RFC822.MailboxAddress address) {
|
||||||
|
Object(contact: contact, address: address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddressSuggestionPopover : Gtk.Popover {
|
||||||
|
|
||||||
|
// Minimum visibility for the contact to appear in autocompletion.
|
||||||
|
private const Geary.Contact.Importance VISIBILITY_THRESHOLD =
|
||||||
|
Geary.Contact.Importance.RECEIVED_FROM;
|
||||||
|
|
||||||
|
public Application.ContactStore contacts { get; construct set; }
|
||||||
|
|
||||||
|
private GLib.ListStore model;
|
||||||
|
private Gtk.SingleSelection selection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when the user has selected an address suggestion
|
||||||
|
*/
|
||||||
|
public signal void selected_address(Geary.RFC822.MailboxAddress address);
|
||||||
|
|
||||||
|
construct {
|
||||||
|
var factory = new Gtk.SignalListItemFactory();
|
||||||
|
factory.setup.connect(on_setup_item);
|
||||||
|
factory.bind.connect(on_bind_item);
|
||||||
|
|
||||||
|
this.model = new GLib.ListStore(typeof(ContactAddressItem));
|
||||||
|
this.model.items_changed.connect((model, pos, removed, added) => {
|
||||||
|
bool is_empty = (model.get_n_items() == 0);
|
||||||
|
bool was_empty = ((model.get_n_items() - added + removed) == 0);
|
||||||
|
if (is_empty != was_empty) {
|
||||||
|
if (was_empty)
|
||||||
|
popup();
|
||||||
|
else
|
||||||
|
popdown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.selection = new Gtk.SingleSelection(this.model);
|
||||||
|
|
||||||
|
var listview = new Gtk.ListView(selection, factory);
|
||||||
|
listview.single_click_activate = true;
|
||||||
|
listview.tab_behavior = Gtk.ListTabBehavior.ITEM;
|
||||||
|
listview.activate.connect(on_activate);
|
||||||
|
|
||||||
|
var sw = new Gtk.ScrolledWindow();
|
||||||
|
sw.hscrollbar_policy = Gtk.PolicyType.NEVER;
|
||||||
|
sw.propagate_natural_height = true;
|
||||||
|
sw.max_content_height = 300;
|
||||||
|
sw.child = listview;
|
||||||
|
this.child = sw;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_setup_item(Object object) {
|
||||||
|
unowned var item = (Gtk.ListItem) object;
|
||||||
|
|
||||||
|
// Create the row widget
|
||||||
|
var row = new AddressSuggestionRow();
|
||||||
|
item.child = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_bind_item(Object object) {
|
||||||
|
unowned var item = (Gtk.ListItem) object;
|
||||||
|
unowned var row = (AddressSuggestionRow) item.child;
|
||||||
|
unowned var contact_address = (ContactAddressItem) item.item;
|
||||||
|
|
||||||
|
row.contact_address = contact_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_activate(Gtk.ListView listvieww,
|
||||||
|
uint position) {
|
||||||
|
var contact_addr = (ContactAddressItem?) this.selection.selected_item;
|
||||||
|
if (contact_addr == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
popdown();
|
||||||
|
selected_address(contact_addr.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear_suggestions() {
|
||||||
|
model.remove_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void search_contacts(string query,
|
||||||
|
GLib.Cancellable? cancellable) {
|
||||||
|
Gee.Collection<Application.Contact>? results = null;
|
||||||
|
try {
|
||||||
|
results = yield this.contacts.search(
|
||||||
|
query,
|
||||||
|
VISIBILITY_THRESHOLD,
|
||||||
|
20,
|
||||||
|
cancellable
|
||||||
|
);
|
||||||
|
} catch (GLib.IOError.CANCELLED err) {
|
||||||
|
// All good
|
||||||
|
} catch (GLib.Error err) {
|
||||||
|
debug("Error searching contacts for completion: %s", err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cancellable.is_cancelled()) {
|
||||||
|
model.remove_all();
|
||||||
|
foreach (Application.Contact contact in results) {
|
||||||
|
for (uint i = 0; i < contact.email_addresses.get_n_items(); i++) {
|
||||||
|
var addr = (Geary.RFC822.MailboxAddress) contact.email_addresses.get_item(i);
|
||||||
|
model.append(new ContactAddressItem(contact, addr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AddressSuggestionRow : Gtk.Box {
|
||||||
|
|
||||||
|
private unowned Adw.Avatar avatar;
|
||||||
|
private unowned Gtk.Label name_label;
|
||||||
|
private unowned Gtk.Label address_label;
|
||||||
|
|
||||||
|
public ContactAddressItem? contact_address {
|
||||||
|
get { return this._contact_address; }
|
||||||
|
set {
|
||||||
|
if (this._contact_address == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
update(value);
|
||||||
|
notify_property("contact-address");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private ContactAddressItem? _contact_address = null;
|
||||||
|
|
||||||
|
construct {
|
||||||
|
this.orientation = Gtk.Orientation.HORIZONTAL;
|
||||||
|
this.spacing = 6;
|
||||||
|
this.margin_top = 3;
|
||||||
|
this.margin_bottom = 3;
|
||||||
|
this.margin_start = 3;
|
||||||
|
this.margin_end = 3;
|
||||||
|
|
||||||
|
add_css_class("contact-address-list-row");
|
||||||
|
|
||||||
|
var avatar = new Adw.Avatar(32, null, true);
|
||||||
|
append(avatar);
|
||||||
|
this.avatar = avatar;
|
||||||
|
|
||||||
|
var names_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 3);
|
||||||
|
append(names_vbox);
|
||||||
|
|
||||||
|
var label = new Gtk.Label("");
|
||||||
|
label.ellipsize = Pango.EllipsizeMode.END;
|
||||||
|
label.valign = Gtk.Align.CENTER;
|
||||||
|
label.halign = Gtk.Align.START;
|
||||||
|
label.xalign = 0;
|
||||||
|
label.width_chars = 24;
|
||||||
|
names_vbox.append(label);
|
||||||
|
this.name_label = label;
|
||||||
|
|
||||||
|
label = new Gtk.Label("");
|
||||||
|
label.ellipsize = Pango.EllipsizeMode.END;
|
||||||
|
label.valign = Gtk.Align.CENTER;
|
||||||
|
label.halign = Gtk.Align.START;
|
||||||
|
label.xalign = 0;
|
||||||
|
label.width_chars = 24;
|
||||||
|
names_vbox.append(label);
|
||||||
|
this.address_label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(ContactAddressItem contact_addr) {
|
||||||
|
this._contact_address = contact_addr;
|
||||||
|
|
||||||
|
if (contact_addr == null) {
|
||||||
|
this.avatar.text = null;
|
||||||
|
this.name_label.label = "";
|
||||||
|
this.address_label.label = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//XXX GTK4
|
||||||
|
if (Geary.String.is_empty(contact_addr.contact.display_name)) {
|
||||||
|
this.avatar.text = null;
|
||||||
|
this.name_label.label = "";
|
||||||
|
this.name_label.visible = false;
|
||||||
|
} else {
|
||||||
|
this.avatar.text = contact_addr.contact.display_name;
|
||||||
|
this.name_label.label = contact_addr.contact.display_name;
|
||||||
|
this.name_label.visible = true;
|
||||||
|
}
|
||||||
|
this.address_label.label = contact_addr.address.address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,4 +25,5 @@ internal interface Composer.ApplicationInterface :
|
||||||
|
|
||||||
internal abstract async void discard_composed_email(Composer.Widget composer);
|
internal abstract async void discard_composed_email(Composer.Widget composer);
|
||||||
|
|
||||||
|
internal abstract GLib.File get_web_cache_dir();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
* Adding a composer to this container places it in {@link
|
* Adding a composer to this container places it in {@link
|
||||||
* Widget.PresentationMode.PANED} mode.
|
* Widget.PresentationMode.PANED} mode.
|
||||||
*/
|
*/
|
||||||
public class Composer.Box : Gtk.Frame, Container {
|
public class Composer.Box : Gtk.Frame, Composer.Container {
|
||||||
|
|
||||||
static construct {
|
static construct {
|
||||||
set_css_name("geary-composer-box");
|
set_css_name("geary-composer-box");
|
||||||
|
|
@ -21,7 +21,7 @@ public class Composer.Box : Gtk.Frame, Container {
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public Gtk.ApplicationWindow? top_window {
|
public Gtk.ApplicationWindow? top_window {
|
||||||
get { return get_toplevel() as Gtk.ApplicationWindow; }
|
get { return get_root() as Gtk.ApplicationWindow; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
|
|
@ -39,14 +39,14 @@ public class Composer.Box : Gtk.Frame, Container {
|
||||||
this.composer.set_mode(PANED);
|
this.composer.set_mode(PANED);
|
||||||
|
|
||||||
this.headerbar = headerbar;
|
this.headerbar = headerbar;
|
||||||
this.headerbar.set_conversation_header(composer.header);
|
this.headerbar.set_conversation_header(composer.header.headerbar);
|
||||||
|
|
||||||
get_style_context().add_class("geary-composer-box");
|
add_css_class("geary-composer-box");
|
||||||
this.halign = Gtk.Align.FILL;
|
this.halign = Gtk.Align.FILL;
|
||||||
this.vexpand = true;
|
this.vexpand = true;
|
||||||
this.vexpand_set = true;
|
this.vexpand_set = true;
|
||||||
|
|
||||||
add(this.composer);
|
this.child = this.composer;
|
||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,8 +54,8 @@ public class Composer.Box : Gtk.Frame, Container {
|
||||||
public void close() {
|
public void close() {
|
||||||
vanished();
|
vanished();
|
||||||
|
|
||||||
this.headerbar.remove_conversation_header(composer.header);
|
this.headerbar.remove_conversation_header(composer.header.headerbar);
|
||||||
remove(this.composer);
|
this.child = null;
|
||||||
destroy();
|
destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
[CCode (cname = "components_reflow_box_get_type")]
|
//XXX GTK4
|
||||||
private extern Type components_reflow_box_get_type();
|
// [CCode (cname = "components_reflow_box_get_type")]
|
||||||
|
// private extern Type components_reflow_box_get_type();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A widget for editing the body of an email message.
|
* A widget for editing the body of an email message.
|
||||||
|
|
@ -33,7 +34,6 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
private const string ACTION_PASTE_WITHOUT_FORMATTING = "paste-without-formatting";
|
private const string ACTION_PASTE_WITHOUT_FORMATTING = "paste-without-formatting";
|
||||||
private const string ACTION_REMOVE_FORMAT = "remove-format";
|
private const string ACTION_REMOVE_FORMAT = "remove-format";
|
||||||
private const string ACTION_SELECT_ALL = "select-all";
|
private const string ACTION_SELECT_ALL = "select-all";
|
||||||
private const string ACTION_SELECT_DICTIONARY = "select-dictionary";
|
|
||||||
private const string ACTION_SHOW_FORMATTING = "show-formatting";
|
private const string ACTION_SHOW_FORMATTING = "show-formatting";
|
||||||
private const string ACTION_STRIKETHROUGH = "strikethrough";
|
private const string ACTION_STRIKETHROUGH = "strikethrough";
|
||||||
internal const string ACTION_TEXT_FORMAT = "text-format";
|
internal const string ACTION_TEXT_FORMAT = "text-format";
|
||||||
|
|
@ -71,7 +71,6 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
{ ACTION_PASTE_WITHOUT_FORMATTING, on_paste_without_formatting },
|
{ ACTION_PASTE_WITHOUT_FORMATTING, on_paste_without_formatting },
|
||||||
{ ACTION_REMOVE_FORMAT, on_remove_format, null, "false" },
|
{ ACTION_REMOVE_FORMAT, on_remove_format, null, "false" },
|
||||||
{ ACTION_SELECT_ALL, on_select_all },
|
{ ACTION_SELECT_ALL, on_select_all },
|
||||||
{ ACTION_SELECT_DICTIONARY, on_select_dictionary },
|
|
||||||
{ ACTION_SHOW_FORMATTING, on_toggle_action, null, "false",
|
{ ACTION_SHOW_FORMATTING, on_toggle_action, null, "false",
|
||||||
on_show_formatting },
|
on_show_formatting },
|
||||||
{ ACTION_STRIKETHROUGH, on_action, null, "false" },
|
{ ACTION_STRIKETHROUGH, on_action, null, "false" },
|
||||||
|
|
@ -127,7 +126,7 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
private Menu context_menu_webkit_text_entry;
|
private Menu context_menu_webkit_text_entry;
|
||||||
private Menu context_menu_inspector;
|
private Menu context_menu_inspector;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid body_container;
|
[GtkChild] private unowned Adw.Bin body_bin;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Label message_overlay_label;
|
[GtkChild] private unowned Gtk.Label message_overlay_label;
|
||||||
|
|
||||||
|
|
@ -147,15 +146,16 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
[GtkChild] private unowned Gtk.Image font_color_icon;
|
[GtkChild] private unowned Gtk.Image font_color_icon;
|
||||||
[GtkChild] private unowned Gtk.MenuButton more_options_button;
|
[GtkChild] private unowned Gtk.MenuButton more_options_button;
|
||||||
|
|
||||||
private Gtk.GestureMultiPress click_gesture;
|
private Gtk.GestureClick click_gesture;
|
||||||
|
|
||||||
|
|
||||||
internal signal void insert_image(bool from_clipboard);
|
internal signal void insert_image(bool from_clipboard);
|
||||||
|
|
||||||
|
|
||||||
internal Editor(Application.Configuration config) {
|
internal Editor(Application.Configuration config, GLib.File cache_dir) {
|
||||||
base_ref();
|
base_ref();
|
||||||
components_reflow_box_get_type();
|
//XXX GTK4
|
||||||
|
// components_reflow_box_get_type();
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
Gtk.Builder builder = new Gtk.Builder.from_resource(
|
Gtk.Builder builder = new Gtk.Builder.from_resource(
|
||||||
|
|
@ -168,7 +168,7 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
this.context_menu_webkit_spelling = (Menu) builder.get_object("context_menu_webkit_spelling");
|
this.context_menu_webkit_spelling = (Menu) builder.get_object("context_menu_webkit_spelling");
|
||||||
this.context_menu_webkit_text_entry = (Menu) builder.get_object("context_menu_webkit_text_entry");
|
this.context_menu_webkit_text_entry = (Menu) builder.get_object("context_menu_webkit_text_entry");
|
||||||
|
|
||||||
this.body = new WebView(config);
|
this.body = new WebView(config, cache_dir);
|
||||||
this.body.command_stack_changed.connect(on_command_state_changed);
|
this.body.command_stack_changed.connect(on_command_state_changed);
|
||||||
this.body.context_menu.connect(on_context_menu);
|
this.body.context_menu.connect(on_context_menu);
|
||||||
this.body.cursor_context_changed.connect(on_cursor_context_changed);
|
this.body.cursor_context_changed.connect(on_cursor_context_changed);
|
||||||
|
|
@ -178,12 +178,13 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
this.body.set_hexpand(true);
|
this.body.set_hexpand(true);
|
||||||
this.body.set_vexpand(true);
|
this.body.set_vexpand(true);
|
||||||
this.body.show();
|
this.body.show();
|
||||||
this.body_container.add(this.body);
|
this.body_bin.child = this.body;
|
||||||
|
|
||||||
this.click_gesture = new Gtk.GestureMultiPress(this.body);
|
this.click_gesture = new Gtk.GestureClick();
|
||||||
this.click_gesture.propagation_phase = CAPTURE;
|
this.click_gesture.propagation_phase = CAPTURE;
|
||||||
this.click_gesture.pressed.connect(this.on_button_press);
|
this.click_gesture.pressed.connect(this.on_button_press);
|
||||||
this.click_gesture.released.connect(this.on_button_release);
|
this.click_gesture.released.connect(this.on_button_release);
|
||||||
|
this.body.add_controller(this.click_gesture);
|
||||||
|
|
||||||
this.actions.add_action_entries(ACTIONS, this);
|
this.actions.add_action_entries(ACTIONS, this);
|
||||||
this.actions.change_action_state(
|
this.actions.change_action_state(
|
||||||
|
|
@ -219,16 +220,15 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
base_unref();
|
base_unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void destroy() {
|
public override void dispose() {
|
||||||
this.show_background_work_timeout.reset();
|
this.show_background_work_timeout.reset();
|
||||||
this.background_work_pulse.reset();
|
this.background_work_pulse.reset();
|
||||||
base.destroy();
|
base.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adds an action bar to the composer. */
|
/** Adds an action bar to the composer. */
|
||||||
public void add_action_bar(Gtk.ActionBar to_add) {
|
public void add_action_bar(Gtk.ActionBar to_add) {
|
||||||
this.action_bar_box.pack_start(to_add);
|
this.action_bar_box.prepend(to_add);
|
||||||
this.action_bar_box.reorder_child(to_add, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -304,10 +304,12 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void update_color_icon(Gdk.RGBA color) {
|
private async void update_color_icon(Gdk.RGBA color) {
|
||||||
var theme = Gtk.IconTheme.get_default();
|
// XXX GTK4 - need to look into this
|
||||||
var icon = theme.lookup_icon("font-color-symbolic", 16, 0);
|
#if 0
|
||||||
var fg_color = Util.Gtk.rgba(0, 0, 0, 1);
|
var theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default());
|
||||||
this.get_style_context().lookup_color("theme_fg_color", out fg_color);
|
var icon = theme.lookup_icon("font-color-symbolic", null, 16, 1, Gtk.TextDirection.NONE, 0);
|
||||||
|
Gdk.RGBA fg_color = { 0, 0, 0, 1 };
|
||||||
|
get_style_context().lookup_color("theme_fg_color", out fg_color);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var pixbuf = yield icon.load_symbolic_async(
|
var pixbuf = yield icon.load_symbolic_async(
|
||||||
|
|
@ -318,6 +320,7 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
warning("Could not load icon `font-color-symbolic`!");
|
warning("Could not load icon `font-color-symbolic`!");
|
||||||
this.font_color_icon.icon_name = "font-color-symbolic";
|
this.font_color_icon.icon_name = "font-color-symbolic";
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private GLib.SimpleAction? get_action(string action_name) {
|
private GLib.SimpleAction? get_action(string action_name) {
|
||||||
|
|
@ -343,7 +346,7 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
LinkPopover.Type.EXISTING_LINK, this.pointer_url,
|
LinkPopover.Type.EXISTING_LINK, this.pointer_url,
|
||||||
(obj, res) => {
|
(obj, res) => {
|
||||||
LinkPopover popover = this.new_link_popover.end(res);
|
LinkPopover popover = this.new_link_popover.end(res);
|
||||||
popover.set_relative_to(this.body);
|
popover.set_parent(this.body);
|
||||||
popover.set_pointing_to(location);
|
popover.set_pointing_to(location);
|
||||||
popover.popup();
|
popover.popup();
|
||||||
});
|
});
|
||||||
|
|
@ -352,7 +355,6 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
|
|
||||||
private bool on_context_menu(WebKit.WebView view,
|
private bool on_context_menu(WebKit.WebView view,
|
||||||
WebKit.ContextMenu context_menu,
|
WebKit.ContextMenu context_menu,
|
||||||
Gdk.Event event,
|
|
||||||
WebKit.HitTestResult hit_test_result) {
|
WebKit.HitTestResult hit_test_result) {
|
||||||
// This is a three step process:
|
// This is a three step process:
|
||||||
// 1. Work out what existing menu items exist that we want to keep
|
// 1. Work out what existing menu items exist that we want to keep
|
||||||
|
|
@ -535,11 +537,8 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
action.set_state(new_state);
|
action.set_state(new_state);
|
||||||
|
|
||||||
update_formatting_toolbar();
|
update_formatting_toolbar();
|
||||||
this.update_color_icon.begin(Util.Gtk.rgba(0, 0, 0, 0));
|
Gdk.RGBA color = { 0, 0, 0, 0 };
|
||||||
}
|
this.update_color_icon.begin(color);
|
||||||
|
|
||||||
private void on_select_dictionary(SimpleAction action, Variant? param) {
|
|
||||||
this.select_dictionary_button.toggled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_command_state_changed(bool can_undo, bool can_redo) {
|
private void on_command_state_changed(bool can_undo, bool can_redo) {
|
||||||
|
|
@ -568,18 +567,24 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_copy_link(SimpleAction action, Variant? param) {
|
private void on_copy_link(SimpleAction action, Variant? param) {
|
||||||
Gtk.Clipboard c = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
|
Gdk.Clipboard c = get_clipboard();
|
||||||
// XXX could this also be the cursor URL? We should be getting
|
// XXX could this also be the cursor URL? We should be getting
|
||||||
// the target URLn as from the action param
|
// the target URLn as from the action param
|
||||||
c.set_text(this.pointer_url, -1);
|
c.set_text(this.pointer_url);
|
||||||
c.store();
|
c.store_async.begin(Priority.DEFAULT, null, (obj, res) => {
|
||||||
|
try {
|
||||||
|
c.store_async.end(res);
|
||||||
|
} catch (Error err) {
|
||||||
|
debug("Couldn't store clipboard: %s", err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_paste() {
|
private void on_paste() {
|
||||||
if (this.body.is_rich_text) {
|
if (this.body.is_rich_text) {
|
||||||
// Check for pasted image in clipboard
|
// Check for pasted image in clipboard
|
||||||
Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
|
Gdk.Clipboard clipboard = get_clipboard();
|
||||||
bool has_image = clipboard.wait_is_image_available();
|
bool has_image = clipboard.formats.contain_gtype(typeof(Gdk.Texture));
|
||||||
if (has_image) {
|
if (has_image) {
|
||||||
insert_image(true);
|
insert_image(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -643,7 +648,7 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
style.set_state(NORMAL);
|
style.set_state(NORMAL);
|
||||||
});
|
});
|
||||||
|
|
||||||
popover.set_relative_to(this.insert_link_button);
|
popover.set_parent(this.insert_link_button);
|
||||||
popover.popup();
|
popover.popup();
|
||||||
style.set_state(ACTIVE);
|
style.set_state(ACTIVE);
|
||||||
});
|
});
|
||||||
|
|
@ -684,19 +689,24 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_select_color() {
|
private void on_select_color() {
|
||||||
var dialog = new Gtk.ColorChooserDialog(
|
var dialog = new Gtk.ColorDialog();
|
||||||
_("Select Color"),
|
dialog.title = _("Select Color");
|
||||||
get_toplevel() as Gtk.Window
|
|
||||||
);
|
dialog.choose_rgba.begin(get_root() as Gtk.Window, null, null, (obj, res) => {
|
||||||
if (dialog.run() == Gtk.ResponseType.OK) {
|
Gdk.RGBA? rgba = null;
|
||||||
var rgba = dialog.get_rgba();
|
try {
|
||||||
|
rgba = dialog.choose_rgba.end(res);
|
||||||
|
} catch (Error err) {
|
||||||
|
debug("Couldn't select color: %s", err.message);
|
||||||
|
}
|
||||||
|
if (rgba == null)
|
||||||
|
return;
|
||||||
|
|
||||||
this.body.execute_editing_command_with_argument(
|
this.body.execute_editing_command_with_argument(
|
||||||
"forecolor", rgba.to_string()
|
"forecolor", rgba.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
this.update_color_icon.begin(rgba);
|
this.update_color_icon.begin(rgba);
|
||||||
}
|
});
|
||||||
dialog.destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_action(GLib.SimpleAction action, GLib.Variant? param) {
|
private void on_action(GLib.SimpleAction action, GLib.Variant? param) {
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,10 @@ public class Composer.EmailEntry : Gtk.Entry {
|
||||||
|
|
||||||
public EmailEntry(Composer.Widget composer) {
|
public EmailEntry(Composer.Widget composer) {
|
||||||
changed.connect(on_changed);
|
changed.connect(on_changed);
|
||||||
key_press_event.connect(on_key_press);
|
Gtk.EventControllerKey key_controller = new Gtk.EventControllerKey();
|
||||||
|
key_controller.key_pressed.connect(on_key_pressed);
|
||||||
|
add_controller(key_controller);
|
||||||
this.composer = composer;
|
this.composer = composer;
|
||||||
show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Marks the entry as being modified. */
|
/** Marks the entry as being modified. */
|
||||||
|
|
@ -71,11 +72,14 @@ public class Composer.EmailEntry : Gtk.Entry {
|
||||||
private void on_changed() {
|
private void on_changed() {
|
||||||
this.is_modified = true;
|
this.is_modified = true;
|
||||||
|
|
||||||
|
//XXX GTK4 see completion class
|
||||||
|
#if 0
|
||||||
ContactEntryCompletion? completion =
|
ContactEntryCompletion? completion =
|
||||||
get_completion() as ContactEntryCompletion;
|
get_completion() as ContactEntryCompletion;
|
||||||
if (completion != null) {
|
if (completion != null) {
|
||||||
completion.update_model();
|
completion.update_model();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (Geary.String.is_empty_or_whitespace(text)) {
|
if (Geary.String.is_empty_or_whitespace(text)) {
|
||||||
this._addresses = new Geary.RFC822.MailboxAddresses();
|
this._addresses = new Geary.RFC822.MailboxAddresses();
|
||||||
|
|
@ -92,9 +96,14 @@ public class Composer.EmailEntry : Gtk.Entry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_key_press(Gtk.Widget widget, Gdk.EventKey event) {
|
private bool on_key_pressed(Gtk.EventControllerKey key_controller,
|
||||||
|
uint keyval,
|
||||||
|
uint keycode,
|
||||||
|
Gdk.ModifierType state) {
|
||||||
bool propagate = Gdk.EVENT_PROPAGATE;
|
bool propagate = Gdk.EVENT_PROPAGATE;
|
||||||
if (event.keyval == Gdk.Key.Tab) {
|
if (keyval == Gdk.Key.Tab) {
|
||||||
|
//XXX GTK4 see completion class
|
||||||
|
#if 0
|
||||||
// If there is a completion entry selected, then use that
|
// If there is a completion entry selected, then use that
|
||||||
ContactEntryCompletion? completion = (
|
ContactEntryCompletion? completion = (
|
||||||
get_completion() as ContactEntryCompletion
|
get_completion() as ContactEntryCompletion
|
||||||
|
|
@ -104,10 +113,11 @@ public class Composer.EmailEntry : Gtk.Entry {
|
||||||
composer.child_focus(Gtk.DirectionType.TAB_FORWARD);
|
composer.child_focus(Gtk.DirectionType.TAB_FORWARD);
|
||||||
propagate = Gdk.EVENT_STOP;
|
propagate = Gdk.EVENT_STOP;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (propagate == Gdk.EVENT_PROPAGATE &&
|
if (propagate == Gdk.EVENT_PROPAGATE &&
|
||||||
event.keyval != Gdk.Key.Escape) {
|
keyval != Gdk.Key.Escape) {
|
||||||
// Keyboard shortcuts for undo/redo won't work when the
|
// Keyboard shortcuts for undo/redo won't work when the
|
||||||
// completion UI is visible unless we explicitly check for
|
// completion UI is visible unless we explicitly check for
|
||||||
// them there.
|
// them there.
|
||||||
|
|
@ -115,9 +125,9 @@ public class Composer.EmailEntry : Gtk.Entry {
|
||||||
// However, don't forward it on if the button pressed is
|
// However, don't forward it on if the button pressed is
|
||||||
// Escape, so that the completion is hidden if present
|
// Escape, so that the completion is hidden if present
|
||||||
// before the composer is closed.
|
// before the composer is closed.
|
||||||
Gtk.Window? window = get_toplevel() as Gtk.Window;
|
Gtk.Window? window = get_root() as Gtk.Window;
|
||||||
if (window != null) {
|
if (window != null) {
|
||||||
propagate = window.activate_key(event);
|
propagate = key_controller.forward(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return propagate;
|
return propagate;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
* Widget.PresentationMode.INLINE} or {@link
|
* Widget.PresentationMode.INLINE} or {@link
|
||||||
* Widget.PresentationMode.INLINE_COMPACT} mode.
|
* Widget.PresentationMode.INLINE_COMPACT} mode.
|
||||||
*/
|
*/
|
||||||
public class Composer.Embed : Gtk.EventBox, Container {
|
public class Composer.Embed : Adw.Bin, Container {
|
||||||
|
|
||||||
private const int MIN_EDITOR_HEIGHT = 200;
|
private const int MIN_EDITOR_HEIGHT = 200;
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ public class Composer.Embed : Gtk.EventBox, Container {
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public Gtk.ApplicationWindow? top_window {
|
public Gtk.ApplicationWindow? top_window {
|
||||||
get { return get_toplevel() as Gtk.ApplicationWindow; }
|
get { return get_root() as Gtk.ApplicationWindow; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The email this composer was originally a reply to. */
|
/** The email this composer was originally a reply to. */
|
||||||
|
|
@ -57,26 +57,27 @@ public class Composer.Embed : Gtk.EventBox, Container {
|
||||||
|
|
||||||
this.outer_scroller = outer_scroller;
|
this.outer_scroller = outer_scroller;
|
||||||
|
|
||||||
get_style_context().add_class("geary-composer-embed");
|
add_css_class("geary-composer-embed");
|
||||||
this.halign = Gtk.Align.FILL;
|
this.halign = Gtk.Align.FILL;
|
||||||
this.vexpand = true;
|
this.vexpand = true;
|
||||||
this.vexpand_set = true;
|
this.vexpand_set = true;
|
||||||
|
|
||||||
add(composer);
|
this.child = composer;
|
||||||
realize.connect(on_realize);
|
//XXX GTK4 see below
|
||||||
show();
|
// realize.connect(on_realize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public void close() {
|
public void close() {
|
||||||
disable_scroll_reroute(this);
|
//XXX GTK4 see below
|
||||||
|
// disable_scroll_reroute(this);
|
||||||
vanished();
|
vanished();
|
||||||
|
|
||||||
this.composer.free_header();
|
this.child = null;
|
||||||
remove(this.composer);
|
|
||||||
destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX GTK4 I think scrolling has changed enough that we might need to rewrite this completely
|
||||||
|
#if 0
|
||||||
private void on_realize() {
|
private void on_realize() {
|
||||||
reroute_scroll_handling(this);
|
reroute_scroll_handling(this);
|
||||||
}
|
}
|
||||||
|
|
@ -84,19 +85,19 @@ public class Composer.Embed : Gtk.EventBox, Container {
|
||||||
private void reroute_scroll_handling(Gtk.Widget widget) {
|
private void reroute_scroll_handling(Gtk.Widget widget) {
|
||||||
widget.add_events(Gdk.EventMask.SCROLL_MASK | Gdk.EventMask.SMOOTH_SCROLL_MASK);
|
widget.add_events(Gdk.EventMask.SCROLL_MASK | Gdk.EventMask.SMOOTH_SCROLL_MASK);
|
||||||
widget.scroll_event.connect(on_inner_scroll_event);
|
widget.scroll_event.connect(on_inner_scroll_event);
|
||||||
Gtk.Container? container = widget as Gtk.Container;
|
unowned Gtk.Widget? child = widget.get_first_child();
|
||||||
if (container != null) {
|
while (child != null) {
|
||||||
foreach (Gtk.Widget child in container.get_children())
|
reroute_scroll_handling(child);
|
||||||
reroute_scroll_handling(child);
|
child = child.get_next_sibling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disable_scroll_reroute(Gtk.Widget widget) {
|
private void disable_scroll_reroute(Gtk.Widget widget) {
|
||||||
widget.scroll_event.disconnect(on_inner_scroll_event);
|
widget.scroll_event.disconnect(on_inner_scroll_event);
|
||||||
Gtk.Container? container = widget as Gtk.Container;
|
unowned Gtk.Widget? child = widget.get_first_child();
|
||||||
if (container != null) {
|
while (child != null) {
|
||||||
foreach (Gtk.Widget child in container.get_children())
|
disable_scroll_reroute(child);
|
||||||
disable_scroll_reroute(child);
|
child = child.get_next_sibling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,5 +204,6 @@ public class Composer.Embed : Gtk.EventBox, Container {
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//XXX GTK4 rename headerbar to header in files too
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/composer-headerbar.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/composer-headerbar.ui")]
|
||||||
public class Composer.Headerbar : Hdy.HeaderBar {
|
public class Composer.Header : Adw.Bin {
|
||||||
|
|
||||||
|
|
||||||
public bool show_save_and_close {
|
public bool show_save_and_close {
|
||||||
|
|
@ -22,6 +23,8 @@ public class Composer.Headerbar : Hdy.HeaderBar {
|
||||||
|
|
||||||
private bool is_attached = true;
|
private bool is_attached = true;
|
||||||
|
|
||||||
|
public Adw.HeaderBar headerbar;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Box detach_start;
|
[GtkChild] private unowned Gtk.Box detach_start;
|
||||||
[GtkChild] private unowned Gtk.Box detach_end;
|
[GtkChild] private unowned Gtk.Box detach_end;
|
||||||
[GtkChild] private unowned Gtk.Button recipients_button;
|
[GtkChild] private unowned Gtk.Button recipients_button;
|
||||||
|
|
@ -34,23 +37,24 @@ public class Composer.Headerbar : Hdy.HeaderBar {
|
||||||
public signal void expand_composer();
|
public signal void expand_composer();
|
||||||
|
|
||||||
|
|
||||||
public Headerbar(Application.Configuration config) {
|
public Header(Application.Configuration config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
this.headerbar = (Adw.HeaderBar) this.child;
|
||||||
Gtk.Settings.get_default().notify["gtk-decoration-layout"].connect(
|
Gtk.Settings.get_default().notify["gtk-decoration-layout"].connect(
|
||||||
on_gtk_decoration_layout_changed
|
on_gtk_decoration_layout_changed
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void destroy() {
|
public override void dispose() {
|
||||||
Gtk.Settings.get_default().notify["gtk-decoration-layout"].disconnect(
|
Gtk.Settings.get_default().notify["gtk-decoration-layout"].disconnect(
|
||||||
on_gtk_decoration_layout_changed
|
on_gtk_decoration_layout_changed
|
||||||
);
|
);
|
||||||
base.destroy();
|
base.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_recipients(string label, string tooltip) {
|
public void set_recipients(string label, string tooltip) {
|
||||||
recipients_label.label = label;
|
this.recipients_label.label = label;
|
||||||
recipients_button.tooltip_text = tooltip;
|
this.recipients_button.tooltip_text = tooltip;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void set_mode(Widget.PresentationMode mode) {
|
internal void set_mode(Widget.PresentationMode mode) {
|
||||||
|
|
@ -77,8 +81,9 @@ public class Composer.Headerbar : Hdy.HeaderBar {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.show_close_button = (mode == Widget.PresentationMode.PANED
|
//XXX GTK4 still need to figure out close buttons
|
||||||
&& this.config.desktop_environment != UNITY);
|
// this.show_close_button = (mode == Widget.PresentationMode.PANED
|
||||||
|
// && this.config.desktop_environment != UNITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set_attached(bool is_attached) {
|
private void set_attached(bool is_attached) {
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,9 @@ public class Composer.LinkPopover : Gtk.Popover {
|
||||||
this.url.grab_focus();
|
this.url.grab_focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void destroy() {
|
public override void dispose() {
|
||||||
this.validation_timeout.reset();
|
this.validation_timeout.reset();
|
||||||
base.destroy();
|
base.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_link_url(string url) {
|
public void set_link_url(string url) {
|
||||||
|
|
@ -129,25 +129,24 @@ public class Composer.LinkPopover : Gtk.Popover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.StyleContext style = this.url.get_style_context();
|
|
||||||
Gtk.EntryIconPosition pos = Gtk.EntryIconPosition.SECONDARY;
|
Gtk.EntryIconPosition pos = Gtk.EntryIconPosition.SECONDARY;
|
||||||
if (!is_valid) {
|
if (!is_valid) {
|
||||||
style.add_class(Gtk.STYLE_CLASS_ERROR);
|
this.url.add_css_class("error");
|
||||||
style.remove_class(Gtk.STYLE_CLASS_WARNING);
|
this.url.remove_css_class("warning");
|
||||||
this.url.set_icon_from_icon_name(pos, "dialog-error-symbolic");
|
this.url.set_icon_from_icon_name(pos, "dialog-error-symbolic");
|
||||||
this.url.set_tooltip_text(
|
this.url.set_tooltip_text(
|
||||||
_("Link URL is not correctly formatted, e.g. http://example.com")
|
_("Link URL is not correctly formatted, e.g. http://example.com")
|
||||||
);
|
);
|
||||||
} else if (!is_nominal) {
|
} else if (!is_nominal) {
|
||||||
style.remove_class(Gtk.STYLE_CLASS_ERROR);
|
this.url.remove_css_class("error");
|
||||||
style.add_class(Gtk.STYLE_CLASS_WARNING);
|
this.url.add_css_class("warning");
|
||||||
this.url.set_icon_from_icon_name(pos, "dialog-warning-symbolic");
|
this.url.set_icon_from_icon_name(pos, "dialog-warning-symbolic");
|
||||||
this.url.set_tooltip_text(
|
this.url.set_tooltip_text(
|
||||||
!is_mailto ? _("Invalid link URL") : _("Invalid email address")
|
!is_mailto ? _("Invalid link URL") : _("Invalid email address")
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
style.remove_class(Gtk.STYLE_CLASS_ERROR);
|
this.url.remove_css_class("error");
|
||||||
style.remove_class(Gtk.STYLE_CLASS_WARNING);
|
this.url.remove_css_class("warning");
|
||||||
this.url.set_icon_from_icon_name(pos, null);
|
this.url.set_icon_from_icon_name(pos, null);
|
||||||
this.url.set_tooltip_text("");
|
this.url.set_tooltip_text("");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ public class Composer.WebView : Components.WebView {
|
||||||
public Gdk.RGBA font_color {
|
public Gdk.RGBA font_color {
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
default = Util.Gtk.rgba(0, 0, 0, 1);
|
default = { 0, 0, 0, 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint context = 0;
|
private uint context = 0;
|
||||||
|
|
@ -141,10 +141,8 @@ public class Composer.WebView : Components.WebView {
|
||||||
public signal void image_file_dropped(string filename, string type, uint8[] contents);
|
public signal void image_file_dropped(string filename, string type, uint8[] contents);
|
||||||
|
|
||||||
|
|
||||||
public WebView(Application.Configuration config) {
|
public WebView(Application.Configuration config, GLib.File cache_dir) {
|
||||||
base(config);
|
base(config, cache_dir);
|
||||||
|
|
||||||
add_events(Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK);
|
|
||||||
|
|
||||||
this.user_content_manager.add_style_sheet(WebView.app_style);
|
this.user_content_manager.add_style_sheet(WebView.app_style);
|
||||||
this.user_content_manager.add_script(WebView.app_script);
|
this.user_content_manager.add_script(WebView.app_script);
|
||||||
|
|
@ -248,11 +246,16 @@ public class Composer.WebView : Components.WebView {
|
||||||
* Pastes plain text from the clipboard into the view.
|
* Pastes plain text from the clipboard into the view.
|
||||||
*/
|
*/
|
||||||
public void paste_plain_text() {
|
public void paste_plain_text() {
|
||||||
get_clipboard(Gdk.SELECTION_CLIPBOARD).request_text((clipboard, text) => {
|
var clipboard = get_clipboard();
|
||||||
if (text != null) {
|
clipboard.read_text_async.begin(null, (obj, res) => {
|
||||||
|
try {
|
||||||
|
string text = clipboard.read_text_async.end(res);
|
||||||
|
if (text != null)
|
||||||
insert_text(text);
|
insert_text(text);
|
||||||
}
|
} catch (Error err) {
|
||||||
});
|
debug("Couldn't read text from clipboard to paste: %s", err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -35,7 +35,7 @@ public class Composer.Window : Gtk.ApplicationWindow, Container {
|
||||||
|
|
||||||
|
|
||||||
public Window(Widget composer, Application.Client application) {
|
public Window(Widget composer, Application.Client application) {
|
||||||
Object(application: application, type: Gtk.WindowType.TOPLEVEL);
|
Object(application: application);
|
||||||
this.composer = composer;
|
this.composer = composer;
|
||||||
this.composer.set_mode(DETACHED);
|
this.composer.set_mode(DETACHED);
|
||||||
|
|
||||||
|
|
@ -47,42 +47,34 @@ public class Composer.Window : Gtk.ApplicationWindow, Container {
|
||||||
// XXX Bug 764622
|
// XXX Bug 764622
|
||||||
set_property("name", "GearyComposerWindow");
|
set_property("name", "GearyComposerWindow");
|
||||||
|
|
||||||
add(this.composer);
|
this.child = this.composer;
|
||||||
|
|
||||||
this.composer.update_window_title();
|
this.composer.update_window_title();
|
||||||
if (application.config.desktop_environment == UNITY) {
|
if (application.config.desktop_environment == UNITY) {
|
||||||
composer.embed_header();
|
composer.embed_header();
|
||||||
} else {
|
} else {
|
||||||
set_titlebar(this.composer.header);
|
set_titlebar(this.composer.header.headerbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.focus_in_event.connect((w, e) => {
|
Gtk.EventControllerFocus focus_controller = new Gtk.EventControllerFocus();
|
||||||
|
focus_controller.enter.connect((controller) => {
|
||||||
application.controller.window_focus_in();
|
application.controller.window_focus_in();
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
this.focus_out_event.connect((w, e) => {
|
focus_controller.leave.connect((controller) => {
|
||||||
application.controller.window_focus_out();
|
application.controller.window_focus_out();
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
((Gtk.Widget) this).add_controller(focus_controller);
|
||||||
show();
|
|
||||||
set_position(Gtk.WindowPosition.CENTER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
public new void close() {
|
public new void close() {
|
||||||
this.composer.free_header();
|
this.child = null;
|
||||||
remove(this.composer);
|
|
||||||
destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void show() {
|
public override void show() {
|
||||||
Gdk.Display? display = Gdk.Display.get_default();
|
Gdk.Display? display = Gdk.Display.get_default();
|
||||||
if (display != null) {
|
if (display != null) {
|
||||||
Gdk.Monitor? monitor = display.get_primary_monitor();
|
Gdk.Monitor? monitor = display.get_monitor_at_surface(get_surface());
|
||||||
if (monitor == null) {
|
|
||||||
monitor = display.get_monitor_at_point(1, 1);
|
|
||||||
}
|
|
||||||
int[] size = this.application.config.get_composer_window_size();
|
int[] size = this.application.config.get_composer_window_size();
|
||||||
//check if stored values are reasonable
|
//check if stored values are reasonable
|
||||||
if (monitor != null &&
|
if (monitor != null &&
|
||||||
|
|
@ -98,44 +90,36 @@ public class Composer.Window : Gtk.ApplicationWindow, Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void save_window_geometry () {
|
private void save_window_geometry () {
|
||||||
if (!this.is_maximized) {
|
if (!this.maximized) {
|
||||||
Gdk.Display? display = get_display();
|
Gdk.Display? display = get_display();
|
||||||
Gdk.Window? window = get_window();
|
Gdk.Surface? surface = get_surface();
|
||||||
if (display != null && window != null) {
|
if (display != null && surface != null) {
|
||||||
Gdk.Monitor monitor = display.get_monitor_at_window(window);
|
Gdk.Monitor monitor = display.get_monitor_at_surface(surface);
|
||||||
|
|
||||||
int width = 0;
|
|
||||||
int height = 0;
|
|
||||||
get_size(out width, out height);
|
|
||||||
|
|
||||||
// Only store if the values are reasonable-looking.
|
// Only store if the values are reasonable-looking.
|
||||||
if (width > 0 && width <= monitor.geometry.width &&
|
if (this.default_width > 0 && this.default_width <= monitor.geometry.width &&
|
||||||
height > 0 && height <= monitor.geometry.height) {
|
this.default_height > 0 && this.default_height <= monitor.geometry.height) {
|
||||||
this.application.config.set_composer_window_size({
|
this.application.config.set_composer_window_size({
|
||||||
width, height
|
this.default_width, this.default_height
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fired on window resize. Save window size for the next start.
|
public override bool close_request() {
|
||||||
public override void size_allocate(Gtk.Allocation allocation) {
|
save_window_geometry();
|
||||||
base.size_allocate(allocation);
|
|
||||||
|
|
||||||
this.save_window_geometry();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool delete_event(Gdk.EventAny event) {
|
|
||||||
// Use the child instead of the `composer` property so we
|
// Use the child instead of the `composer` property so we
|
||||||
// don't check with the composer if it has already been
|
// don't check with the composer if it has already been
|
||||||
// removed from the container.
|
// removed from the container.
|
||||||
Widget? child = get_child() as Widget;
|
Widget? child = get_child() as Widget;
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
bool ret = Gdk.EVENT_PROPAGATE;
|
||||||
if (child != null &&
|
// XXX GTK4 - This is now an async method, I'm not sure we can still stop htis?
|
||||||
child.conditional_close(true) == CANCELLED) {
|
// if (child != null &&
|
||||||
ret = Gdk.EVENT_STOP;
|
// child.conditional_close(true) == CANCELLED) {
|
||||||
}
|
// ret = Gdk.EVENT_STOP;
|
||||||
|
// }
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
|
public class ContactEntryCompletion : Adw.EntryRow, Geary.BaseInterface {
|
||||||
|
//XXX GTK4 probably want to create a ContactEntryRow or something like that, GtkEntryCompletion is deprecated
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
|
||||||
// Minimum visibility for the contact to appear in autocompletion.
|
// Minimum visibility for the contact to appear in autocompletion.
|
||||||
|
|
@ -365,4 +367,5 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,10 @@ public class SpellCheckPopover {
|
||||||
this.is_lang_visible = is_active || is_visible;
|
this.is_lang_visible = is_active || is_visible;
|
||||||
|
|
||||||
Gtk.Box box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
|
Gtk.Box box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
|
||||||
box.margin = 6;
|
|
||||||
box.margin_start = 12;
|
box.margin_start = 12;
|
||||||
|
box.margin_end = 6;
|
||||||
|
box.margin_top = 6;
|
||||||
|
box.margin_bottom = 6;
|
||||||
|
|
||||||
lang_name = Util.I18n.language_name_from_locale(lang_code);
|
lang_name = Util.I18n.language_name_from_locale(lang_code);
|
||||||
country_name = Util.I18n.country_name_from_locale(lang_code);
|
country_name = Util.I18n.country_name_from_locale(lang_code);
|
||||||
|
|
@ -80,28 +82,27 @@ public class SpellCheckPopover {
|
||||||
country_label.halign = Gtk.Align.START;
|
country_label.halign = Gtk.Align.START;
|
||||||
country_label.ellipsize = END;
|
country_label.ellipsize = END;
|
||||||
country_label.xalign = 0;
|
country_label.xalign = 0;
|
||||||
country_label.get_style_context().add_class("dim-label");
|
country_label.add_css_class("dim-label");
|
||||||
|
|
||||||
label_box.add(label);
|
label_box.append(label);
|
||||||
label_box.add(country_label);
|
label_box.append(country_label);
|
||||||
box.pack_start(label_box, false, false);
|
box.append(label_box);
|
||||||
} else {
|
} else {
|
||||||
box.pack_start(label, false, false);
|
box.append(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.IconSize sz = Gtk.IconSize.SMALL_TOOLBAR;
|
active_image = new Gtk.Image.from_icon_name("object-select-symbolic");
|
||||||
active_image = new Gtk.Image.from_icon_name("object-select-symbolic", sz);
|
|
||||||
this.visibility_button = new Gtk.Button();
|
this.visibility_button = new Gtk.Button();
|
||||||
this.visibility_button.set_relief(Gtk.ReliefStyle.NONE);
|
this.visibility_button.add_css_class("flat");
|
||||||
box.pack_start(active_image, false, false, 6);
|
box.append(active_image);
|
||||||
box.pack_start(this.visibility_button, true, true);
|
box.append(this.visibility_button);
|
||||||
this.visibility_button.halign = Gtk.Align.END; // Make the button stay at the right end of the screen
|
this.visibility_button.halign = Gtk.Align.END; // Make the button stay at the right end of the screen
|
||||||
this.visibility_button.valign = CENTER;
|
this.visibility_button.valign = CENTER;
|
||||||
|
|
||||||
this.visibility_button.clicked.connect(on_visibility_clicked);
|
this.visibility_button.clicked.connect(on_visibility_clicked);
|
||||||
|
|
||||||
update_images();
|
update_images();
|
||||||
add(box);
|
this.child = box;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool is_lang_active() {
|
public bool is_lang_active() {
|
||||||
|
|
@ -109,23 +110,21 @@ public class SpellCheckPopover {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_images() {
|
private void update_images() {
|
||||||
Gtk.IconSize sz = Gtk.IconSize.SMALL_TOOLBAR;
|
|
||||||
|
|
||||||
switch (lang_active) {
|
switch (lang_active) {
|
||||||
case SpellCheckStatus.ACTIVE:
|
case SpellCheckStatus.ACTIVE:
|
||||||
active_image.set_from_icon_name("object-select-symbolic", sz);
|
this.active_image.set_from_icon_name("object-select-symbolic");
|
||||||
break;
|
break;
|
||||||
case SpellCheckStatus.INACTIVE:
|
case SpellCheckStatus.INACTIVE:
|
||||||
active_image.clear();
|
this.active_image.clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_lang_visible) {
|
if (is_lang_visible) {
|
||||||
this.visibility_button.set_image(new Gtk.Image.from_icon_name("list-remove-symbolic", sz));
|
this.visibility_button.icon_name = "list-remove-symbolic";
|
||||||
this.visibility_button.set_tooltip_text(_("Remove this language from the preferred list"));
|
this.visibility_button.set_tooltip_text(_("Remove this language from the preferred list"));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.visibility_button.set_image(new Gtk.Image.from_icon_name("list-add-symbolic", sz));
|
this.visibility_button.icon_name = "list-add-symbolic";
|
||||||
this.visibility_button.set_tooltip_text(_("Add this language to the preferred list"));
|
this.visibility_button.set_tooltip_text(_("Add this language to the preferred list"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -193,7 +192,7 @@ public class SpellCheckPopover {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpellCheckPopover(Gtk.MenuButton button, Application.Configuration config) {
|
public SpellCheckPopover(Gtk.MenuButton button, Application.Configuration config) {
|
||||||
this.popover = new Gtk.Popover(button);
|
this.popover = new Gtk.Popover();
|
||||||
button.popover = this.popover;
|
button.popover = this.popover;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.selected_rows = new GLib.GenericSet<string>(GLib.str_hash, GLib.str_equal);
|
this.selected_rows = new GLib.GenericSet<string>(GLib.str_hash, GLib.str_equal);
|
||||||
|
|
@ -224,12 +223,11 @@ public class SpellCheckPopover {
|
||||||
search_box = new Gtk.SearchEntry();
|
search_box = new Gtk.SearchEntry();
|
||||||
search_box.set_placeholder_text(_("Search for more languages"));
|
search_box.set_placeholder_text(_("Search for more languages"));
|
||||||
search_box.changed.connect(on_search_box_changed);
|
search_box.changed.connect(on_search_box_changed);
|
||||||
search_box.grab_focus.connect(on_search_box_grab_focus);
|
content.append(search_box);
|
||||||
content.pack_start(search_box, false, true);
|
|
||||||
|
|
||||||
view = new Gtk.ScrolledWindow(null, null);
|
view = new Gtk.ScrolledWindow();
|
||||||
view.set_shadow_type(Gtk.ShadowType.IN);
|
|
||||||
view.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
view.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||||
|
view.vexpand = true;
|
||||||
|
|
||||||
langs_list = new Gtk.ListBox();
|
langs_list = new Gtk.ListBox();
|
||||||
langs_list.set_selection_mode(Gtk.SelectionMode.NONE);
|
langs_list.set_selection_mode(Gtk.SelectionMode.NONE);
|
||||||
|
|
@ -239,7 +237,7 @@ public class SpellCheckPopover {
|
||||||
lang in enabled_langs,
|
lang in enabled_langs,
|
||||||
lang in visible_langs
|
lang in visible_langs
|
||||||
);
|
);
|
||||||
langs_list.add(row);
|
langs_list.append(row);
|
||||||
|
|
||||||
if (row.is_lang_active())
|
if (row.is_lang_active())
|
||||||
selected_rows.add(lang);
|
selected_rows.add(lang);
|
||||||
|
|
@ -248,14 +246,14 @@ public class SpellCheckPopover {
|
||||||
row.visibility_changed.connect(this.on_row_visibility_changed);
|
row.visibility_changed.connect(this.on_row_visibility_changed);
|
||||||
}
|
}
|
||||||
langs_list.row_activated.connect(on_row_activated);
|
langs_list.row_activated.connect(on_row_activated);
|
||||||
view.add(langs_list);
|
view.child = langs_list;
|
||||||
|
|
||||||
content.pack_start(view, true, true);
|
content.append(view);
|
||||||
|
|
||||||
langs_list.set_filter_func(this.filter_function);
|
langs_list.set_filter_func(this.filter_function);
|
||||||
langs_list.set_header_func(this.header_function);
|
langs_list.set_header_func(this.header_function);
|
||||||
|
|
||||||
popover.add(content);
|
this.popover.child = content;
|
||||||
|
|
||||||
// Make sure that the search box does not get the focus first. We want it to have it only
|
// Make sure that the search box does not get the focus first. We want it to have it only
|
||||||
// if the user wants to perform an extended search.
|
// if the user wants to perform an extended search.
|
||||||
|
|
@ -265,8 +263,8 @@ public class SpellCheckPopover {
|
||||||
content.set_margin_top(6);
|
content.set_margin_top(6);
|
||||||
content.set_margin_bottom(6);
|
content.set_margin_bottom(6);
|
||||||
|
|
||||||
popover.show.connect(this.on_shown);
|
this.popover.show.connect(this.on_shown);
|
||||||
popover.set_size_request(360, 350);
|
this.popover.set_size_request(360, 350);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_row_activated(Gtk.ListBoxRow row) {
|
private void on_row_activated(Gtk.ListBoxRow row) {
|
||||||
|
|
@ -281,10 +279,6 @@ public class SpellCheckPopover {
|
||||||
langs_list.invalidate_filter();
|
langs_list.invalidate_filter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_search_box_grab_focus() {
|
|
||||||
set_expanded(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set_expanded(bool expanded) {
|
private void set_expanded(bool expanded) {
|
||||||
is_expanded = expanded;
|
is_expanded = expanded;
|
||||||
langs_list.invalidate_filter();
|
langs_list.invalidate_filter();
|
||||||
|
|
@ -295,8 +289,6 @@ public class SpellCheckPopover {
|
||||||
content.set_focus_child(view);
|
content.set_focus_child(view);
|
||||||
is_expanded = false;
|
is_expanded = false;
|
||||||
langs_list.invalidate_filter();
|
langs_list.invalidate_filter();
|
||||||
|
|
||||||
popover.show_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_row_enabled_changed(SpellCheckLangRow row,
|
private void on_row_enabled_changed(SpellCheckLangRow row,
|
||||||
|
|
|
||||||
|
|
@ -110,10 +110,10 @@ internal class ConversationList.Row : Gtk.ListBoxRow {
|
||||||
private void set_button_active(bool active) {
|
private void set_button_active(bool active) {
|
||||||
this.selected_button.set_active(active);
|
this.selected_button.set_active(active);
|
||||||
if (active) {
|
if (active) {
|
||||||
this.get_style_context().add_class("selected");
|
this.add_css_class("selected");
|
||||||
this.set_state_flags(Gtk.StateFlags.SELECTED, false);
|
this.set_state_flags(Gtk.StateFlags.SELECTED, false);
|
||||||
} else {
|
} else {
|
||||||
this.get_style_context().remove_class("selected");
|
this.remove_css_class("selected");
|
||||||
this.unset_state_flags(Gtk.StateFlags.SELECTED);
|
this.unset_state_flags(Gtk.StateFlags.SELECTED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,9 +134,9 @@ internal class ConversationList.Row : Gtk.ListBoxRow {
|
||||||
|
|
||||||
private void update_flags(Geary.Email? email) {
|
private void update_flags(Geary.Email? email) {
|
||||||
if (conversation.is_unread()) {
|
if (conversation.is_unread()) {
|
||||||
get_style_context().add_class("unread");
|
add_css_class("unread");
|
||||||
} else {
|
} else {
|
||||||
get_style_context().remove_class("unread");
|
remove_css_class("unread");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conversation.is_flagged()) {
|
if (conversation.is_flagged()) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/conversation-list-view.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/conversation-list-view.ui")]
|
||||||
public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
public class ConversationList.View : Adw.Bin, Geary.BaseInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The fields that must be available on any ConversationMonitor
|
* The fields that must be available on any ConversationMonitor
|
||||||
* passed to ConversationList.View
|
* passed to ConversationList.View
|
||||||
|
|
@ -42,11 +43,11 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
|
|
||||||
private Application.Configuration config;
|
private Application.Configuration config;
|
||||||
|
|
||||||
private Gtk.GestureMultiPress press_gesture;
|
|
||||||
private Gtk.GestureLongPress long_press_gesture;
|
private Gtk.GestureLongPress long_press_gesture;
|
||||||
private Gtk.EventControllerKey key_event_controller;
|
|
||||||
private Gdk.ModifierType last_modifier_type;
|
private Gdk.ModifierType last_modifier_type;
|
||||||
|
|
||||||
|
[GtkChild] public unowned Gtk.ScrolledWindow scrolled_window;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ListBox list;
|
[GtkChild] private unowned Gtk.ListBox list;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -64,14 +65,10 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
|
|
||||||
this.list.set_header_func(header_func);
|
this.list.set_header_func(header_func);
|
||||||
|
|
||||||
this.vadjustment.value_changed.connect(maybe_load_more);
|
this.scrolled_window.vadjustment.value_changed.connect(maybe_load_more);
|
||||||
this.vadjustment.value_changed.connect(update_visible_conversations);
|
this.scrolled_window.vadjustment.value_changed.connect(update_visible_conversations);
|
||||||
|
|
||||||
this.press_gesture = new Gtk.GestureMultiPress(this.list);
|
this.long_press_gesture = new Gtk.GestureLongPress();
|
||||||
this.press_gesture.set_button(0);
|
|
||||||
this.press_gesture.released.connect(on_press_gesture_released);
|
|
||||||
|
|
||||||
this.long_press_gesture = new Gtk.GestureLongPress(this.list);
|
|
||||||
this.long_press_gesture.propagation_phase = CAPTURE;
|
this.long_press_gesture.propagation_phase = CAPTURE;
|
||||||
this.long_press_gesture.pressed.connect((n_press, x, y) => {
|
this.long_press_gesture.pressed.connect((n_press, x, y) => {
|
||||||
Row? row = (Row) this.list.get_row_at_y((int) y);
|
Row? row = (Row) this.list.get_row_at_y((int) y);
|
||||||
|
|
@ -80,14 +77,13 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
this.selection_mode_enabled = true;
|
this.selection_mode_enabled = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.list.add_controller(this.long_press_gesture);
|
||||||
|
|
||||||
this.key_event_controller = new Gtk.EventControllerKey(this.list);
|
//XXX GTK4 - check if started on click
|
||||||
this.key_event_controller.key_pressed.connect(on_key_event_controller_key_pressed);
|
var drag_source = new Gtk.DragSource();
|
||||||
|
drag_source.drag_begin.connect(on_drag_begin);
|
||||||
Gtk.drag_source_set(this.list, Gdk.ModifierType.BUTTON1_MASK, FolderList.Tree.TARGET_ENTRY_LIST,
|
drag_source.drag_end.connect(on_drag_end);
|
||||||
Gdk.DragAction.COPY | Gdk.DragAction.MOVE);
|
this.list.add_controller(drag_source);
|
||||||
this.list.drag_begin.connect(on_drag_begin);
|
|
||||||
this.list.drag_end.connect(on_drag_end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static construct {
|
static construct {
|
||||||
|
|
@ -113,10 +109,13 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
* automatically but instead it must be externally scheduled
|
* automatically but instead it must be externally scheduled
|
||||||
*/
|
*/
|
||||||
public void refresh_times() {
|
public void refresh_times() {
|
||||||
this.list.foreach((child) => {
|
int i = 0;
|
||||||
var row = (Row) child;
|
Row? row = this.list.get_row_at_index(0) as Row;
|
||||||
|
while (row != null) {
|
||||||
row.refresh_time();
|
row.refresh_time();
|
||||||
});
|
i++;
|
||||||
|
row = this.list.get_row_at_index(i) as Row;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------
|
// -------------------
|
||||||
|
|
@ -204,7 +203,7 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
|
|
||||||
private Gtk.Popover construct_popover(Row row, uint selection_size) {
|
private Gtk.Popover construct_popover(Row row, uint selection_size) {
|
||||||
GLib.Menu context_menu_model = new GLib.Menu();
|
GLib.Menu context_menu_model = new GLib.Menu();
|
||||||
var main = get_toplevel() as Application.MainWindow;
|
var main = get_root() as Application.MainWindow;
|
||||||
|
|
||||||
if (main != null) {
|
if (main != null) {
|
||||||
if (!main.is_shift_down) {
|
if (!main.is_shift_down) {
|
||||||
|
|
@ -303,13 +302,8 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
);
|
);
|
||||||
context_menu_model.append_section(null, actions_section);
|
context_menu_model.append_section(null, actions_section);
|
||||||
|
|
||||||
// Use a popover rather than a regular context menu since
|
var context_menu = new Gtk.PopoverMenu.from_model(context_menu_model);
|
||||||
// the latter grabs the event queue, so the MainWindow
|
context_menu.set_parent(row);
|
||||||
// will not receive events if the user releases Shift,
|
|
||||||
// making the trash/delete header bar state wrong.
|
|
||||||
Gtk.Popover context_menu = new Gtk.Popover.from_model(
|
|
||||||
row, context_menu_model
|
|
||||||
);
|
|
||||||
|
|
||||||
return context_menu;
|
return context_menu;
|
||||||
}
|
}
|
||||||
|
|
@ -347,13 +341,16 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
* If a conversation is not present in the ListBox, it is ignored.
|
* If a conversation is not present in the ListBox, it is ignored.
|
||||||
*/
|
*/
|
||||||
public void select_conversations(Gee.Collection<Geary.App.Conversation> selection) {
|
public void select_conversations(Gee.Collection<Geary.App.Conversation> selection) {
|
||||||
this.list.foreach((child) => {
|
int i = 0;
|
||||||
var row = (Row) child;
|
Row? row = this.list.get_row_at_index(0) as Row;
|
||||||
|
while (row != null) {
|
||||||
Geary.App.Conversation conversation = row.conversation;
|
Geary.App.Conversation conversation = row.conversation;
|
||||||
if (selection.contains(conversation)) {
|
if (selection.contains(conversation)) {
|
||||||
this.list.select_row(row);
|
this.list.select_row(row);
|
||||||
}
|
}
|
||||||
});
|
i++;
|
||||||
|
row = this.list.get_row_at_index(i) as Row;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -466,9 +463,9 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
*/
|
*/
|
||||||
private int VISIBILITY_UPDATE_DELAY_MS = 1000;
|
private int VISIBILITY_UPDATE_DELAY_MS = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of all conversations currently displayed in the viewport
|
* The set of all conversations currently displayed in the viewport
|
||||||
*/
|
*/
|
||||||
public Gee.Set<Geary.App.Conversation> visible_conversations {get; private set; default = new Gee.HashSet<Geary.App.Conversation>(); }
|
public Gee.Set<Geary.App.Conversation> visible_conversations {get; private set; default = new Gee.HashSet<Geary.App.Conversation>(); }
|
||||||
private Geary.Scheduler.Scheduled? scheduled_visible_update;
|
private Geary.Scheduler.Scheduled? scheduled_visible_update;
|
||||||
|
|
||||||
|
|
@ -482,7 +479,7 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
|
|
||||||
scheduled_visible_update = Geary.Scheduler.after_msec(VISIBILITY_UPDATE_DELAY_MS, () => {
|
scheduled_visible_update = Geary.Scheduler.after_msec(VISIBILITY_UPDATE_DELAY_MS, () => {
|
||||||
var visible = new Gee.HashSet<Geary.App.Conversation>();
|
var visible = new Gee.HashSet<Geary.App.Conversation>();
|
||||||
Gtk.ListBoxRow? first = this.list.get_row_at_y((int) this.vadjustment.value);
|
Gtk.ListBoxRow? first = this.list.get_row_at_y((int) this.scrolled_window.vadjustment.value);
|
||||||
|
|
||||||
if (first == null) {
|
if (first == null) {
|
||||||
this.visible_conversations = visible;
|
this.visible_conversations = visible;
|
||||||
|
|
@ -492,7 +489,7 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
uint start_index = ((uint) first.get_index());
|
uint start_index = ((uint) first.get_index());
|
||||||
uint end_index = uint.min(
|
uint end_index = uint.min(
|
||||||
// Assume that all messages are the same height
|
// Assume that all messages are the same height
|
||||||
start_index + (uint) (this.vadjustment.page_size / first.get_allocated_height()),
|
start_index + (uint) (this.scrolled_window.vadjustment.page_size / first.get_allocated_height()),
|
||||||
this.model.get_n_items()
|
this.model.get_n_items()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -581,29 +578,34 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
* Update conversation row
|
* Update conversation row
|
||||||
*/
|
*/
|
||||||
private void on_conversation_updated(Geary.App.Conversation convo) {
|
private void on_conversation_updated(Geary.App.Conversation convo) {
|
||||||
this.list.foreach((child) => {
|
int i = 0;
|
||||||
var row = (Row) child;
|
Row? row = this.list.get_row_at_index(0) as Row;
|
||||||
|
while (row != null) {
|
||||||
if (convo == row.conversation) {
|
if (convo == row.conversation) {
|
||||||
row.update();
|
row.update();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
i++;
|
||||||
|
row = this.list.get_row_at_index(i) as Row;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
// Gestures
|
// Gestures
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
private void on_press_gesture_released(int n_press, double x, double y) {
|
[GtkCallback]
|
||||||
|
private void on_press_gesture_released(Gtk.GestureClick click_gesture, int n_press, double x, double y) {
|
||||||
var row = (Row) this.list.get_row_at_y((int) y);
|
var row = (Row) this.list.get_row_at_y((int) y);
|
||||||
|
|
||||||
if (row == null)
|
if (row == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var button = this.press_gesture.get_current_button();
|
var button = click_gesture.get_current_button();
|
||||||
if (button == 1) {
|
if (button == Gdk.BUTTON_PRIMARY) {
|
||||||
Gdk.EventSequence sequence = this.press_gesture.get_current_sequence();
|
Gdk.EventSequence sequence = click_gesture.get_current_sequence();
|
||||||
Gdk.Event event = this.press_gesture.get_last_event(sequence);
|
Gdk.Event event = click_gesture.get_last_event(sequence);
|
||||||
event.get_state(out this.last_modifier_type);
|
this.last_modifier_type = event.get_modifier_state();
|
||||||
if (!this.selection_mode_enabled) {
|
if (!this.selection_mode_enabled) {
|
||||||
if ((this.last_modifier_type & Gdk.ModifierType.SHIFT_MASK) ==
|
if ((this.last_modifier_type & Gdk.ModifierType.SHIFT_MASK) ==
|
||||||
Gdk.ModifierType.SHIFT_MASK ||
|
Gdk.ModifierType.SHIFT_MASK ||
|
||||||
|
|
@ -614,19 +616,19 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
conversation_activated(((Row) row).conversation, 1);
|
conversation_activated(((Row) row).conversation, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (button == 2) {
|
} else if (button == Gdk.BUTTON_MIDDLE) {
|
||||||
conversation_activated(row.conversation, 2);
|
conversation_activated(row.conversation, 2);
|
||||||
} else if (button == 3) {
|
} else if (button == Gdk.BUTTON_SECONDARY) {
|
||||||
var rect = Gdk.Rectangle();
|
Graphene.Point p = { (float) x, (float) y };
|
||||||
row.translate_coordinates(this.list, 0, 0, out rect.x, out rect.y);
|
Graphene.Point p_row;
|
||||||
rect.x = (int) x;
|
this.list.compute_point(row, p, out p_row);
|
||||||
rect.y = (int) y - rect.y;
|
Gdk.Rectangle rect = { (int) p_row.x, (int) p_row.y, 0, 0 };
|
||||||
rect.width = rect.height = 0;
|
|
||||||
context_menu(row, rect);
|
context_menu(row, rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_key_event_controller_key_pressed(uint keyval, uint keycode, Gdk.ModifierType modifier_type) {
|
[GtkCallback]
|
||||||
|
private bool on_key_pressed(uint keyval, uint keycode, Gdk.ModifierType modifier_type) {
|
||||||
switch (keyval) {
|
switch (keyval) {
|
||||||
case Gdk.Key.Up:
|
case Gdk.Key.Up:
|
||||||
case Gdk.Key.Down:
|
case Gdk.Key.Down:
|
||||||
|
|
@ -646,19 +648,17 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Widgets used as drag icons have to be explicitly destroyed after the drag
|
* Widgets used as drag icons have to be explicitly destroyed after the drag
|
||||||
* so we track the widget as a private member
|
* so we track the widget as a private member
|
||||||
*/
|
*/
|
||||||
private Row? drag_widget = null;
|
private Row? drag_widget = null;
|
||||||
|
|
||||||
private void on_drag_begin(Gdk.DragContext ctx) {
|
private void on_drag_begin(Gtk.DragSource drag_source, Gdk.Drag drag) {
|
||||||
int screen_x, screen_y;
|
int screen_x, screen_y;
|
||||||
Gdk.ModifierType _modifier;
|
Gdk.ModifierType _modifier;
|
||||||
|
|
||||||
this.get_window().get_device_position(ctx.get_device(), out screen_x, out screen_y, out _modifier);
|
Row? row = this.list.get_row_at_y((int) this.scrolled_window.vadjustment.value) as Row?;
|
||||||
|
|
||||||
Row? row = this.list.get_row_at_y(screen_y + (int) this.vadjustment.value) as Row?;
|
|
||||||
if (row != null) {
|
if (row != null) {
|
||||||
// If the user has a selection but drags starting from an unselected
|
// If the user has a selection but drags starting from an unselected
|
||||||
// row, we need to set the selection to that row
|
// row, we need to set the selection to that row
|
||||||
|
|
@ -669,18 +669,18 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
|
|
||||||
this.drag_widget = new Row(this.config, row.conversation, false);
|
this.drag_widget = new Row(this.config, row.conversation, false);
|
||||||
this.drag_widget.width_request = row.get_allocated_width();
|
this.drag_widget.width_request = row.get_allocated_width();
|
||||||
this.drag_widget.get_style_context().add_class("drag-n-drop");
|
this.drag_widget.add_css_class("drag-n-drop");
|
||||||
this.drag_widget.visible = true;
|
this.drag_widget.visible = true;
|
||||||
|
|
||||||
int hot_x, hot_y;
|
double hot_x, hot_y;
|
||||||
this.translate_coordinates(row, screen_x, screen_y, out hot_x, out hot_y);
|
// XXX GTK4 - this might be a bit more work to do properly wiht Paintable
|
||||||
Gtk.drag_set_icon_widget(ctx, this.drag_widget, hot_x, hot_y);
|
translate_coordinates(row, 0, 0, out hot_x, out hot_y);
|
||||||
|
// drag_source.set_icon(this.drag_widget, hot_x, hot_y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_drag_end(Gdk.DragContext ctx) {
|
private void on_drag_end(Gtk.DragSource drag_source, Gdk.Drag drag, bool delete_data) {
|
||||||
if (this.drag_widget != null) {
|
if (this.drag_widget != null) {
|
||||||
this.drag_widget.destroy();
|
|
||||||
this.drag_widget = null;
|
this.drag_widget = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -706,10 +706,13 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_selection_mode_changed() {
|
private void on_selection_mode_changed() {
|
||||||
this.list.foreach((child) => {
|
int i = 0;
|
||||||
var row = (Row) child;
|
Row? row = this.list.get_row_at_index(0) as Row;
|
||||||
|
while (row != null) {
|
||||||
row.set_selection_enabled(this.selection_mode_enabled);
|
row.set_selection_enabled(this.selection_mode_enabled);
|
||||||
});
|
i++;
|
||||||
|
row = this.list.get_row_at_index(i) as Row;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.selection_mode_enabled) {
|
if (this.selection_mode_enabled) {
|
||||||
this.to_restore_row = this.list.get_selected_row();
|
this.to_restore_row = this.list.get_selected_row();
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ public class Conversation.ContactPopover : Gtk.Popover {
|
||||||
|
|
||||||
private Application.Configuration config;
|
private Application.Configuration config;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid contact_pane;
|
[GtkChild] private unowned Gtk.Box contact_pane;
|
||||||
|
|
||||||
[GtkChild] private unowned Hdy.Avatar avatar;
|
[GtkChild] private unowned Adw.Avatar avatar;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Label contact_name;
|
[GtkChild] private unowned Gtk.Label contact_name;
|
||||||
|
|
||||||
|
|
@ -55,13 +55,13 @@ public class Conversation.ContactPopover : Gtk.Popover {
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Button unstarred_button;
|
[GtkChild] private unowned Gtk.Button unstarred_button;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ModelButton open_button;
|
[GtkChild] private unowned Gtk.Button open_button;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ModelButton save_button;
|
[GtkChild] private unowned Gtk.Button save_button;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ModelButton load_remote_button;
|
[GtkChild] private unowned Gtk.CheckButton load_remote_button;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid deceptive_pane;
|
[GtkChild] private unowned Gtk.Box deceptive_pane;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Label forged_email_label;
|
[GtkChild] private unowned Gtk.Label forged_email_label;
|
||||||
|
|
||||||
|
|
@ -79,22 +79,20 @@ public class Conversation.ContactPopover : Gtk.Popover {
|
||||||
Geary.RFC822.MailboxAddress mailbox,
|
Geary.RFC822.MailboxAddress mailbox,
|
||||||
Application.Configuration config) {
|
Application.Configuration config) {
|
||||||
|
|
||||||
this.relative_to = relative_to;
|
set_parent(relative_to);
|
||||||
this.contact = contact;
|
this.contact = contact;
|
||||||
this.mailbox = mailbox;
|
this.mailbox = mailbox;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
this.load_remote_button.role = CHECK;
|
|
||||||
|
|
||||||
this.contact.bind_property("display-name",
|
this.contact.bind_property("display-name",
|
||||||
this.avatar,
|
this.avatar,
|
||||||
"text",
|
"text",
|
||||||
BindingFlags.SYNC_CREATE);
|
BindingFlags.SYNC_CREATE);
|
||||||
|
|
||||||
this.contact.bind_property("avatar",
|
load_avatar.begin((obj, res) => { load_avatar.end(res); });
|
||||||
this.avatar,
|
this.contact.notify["avatar"].connect((obj, pspec) => {
|
||||||
"loadable-icon",
|
load_avatar.begin((obj, res) => { load_avatar.end(res); });
|
||||||
BindingFlags.SYNC_CREATE);
|
});
|
||||||
|
|
||||||
this.actions.add_action_entries(ACTION_ENTRIES, this);
|
this.actions.add_action_entries(ACTION_ENTRIES, this);
|
||||||
insert_action_group(ACTION_GROUP, this.actions);
|
insert_action_group(ACTION_GROUP, this.actions);
|
||||||
|
|
@ -106,10 +104,10 @@ public class Conversation.ContactPopover : Gtk.Popover {
|
||||||
/**
|
/**
|
||||||
* Starts loading the avatar for the message's sender.
|
* Starts loading the avatar for the message's sender.
|
||||||
*/
|
*/
|
||||||
public override void destroy() {
|
public override void dispose() {
|
||||||
this.contact.changed.disconnect(this.on_contact_changed);
|
this.contact.changed.disconnect(this.on_contact_changed);
|
||||||
this.load_cancellable.cancel();
|
this.load_cancellable.cancel();
|
||||||
base.destroy();
|
base.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update() {
|
private void update() {
|
||||||
|
|
@ -182,13 +180,34 @@ public class Conversation.ContactPopover : Gtk.Popover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void load_avatar() {
|
||||||
|
if (this.contact.avatar == null) {
|
||||||
|
this.avatar.custom_image = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
GLib.InputStream stream = yield this.contact.avatar.load_async(
|
||||||
|
avatar.size, this.load_cancellable
|
||||||
|
);
|
||||||
|
var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(
|
||||||
|
stream, avatar.size, avatar.size, true, load_cancellable
|
||||||
|
);
|
||||||
|
this.avatar.custom_image = Gdk.Texture.for_pixbuf(pixbuf);
|
||||||
|
} catch (GLib.Error err) {
|
||||||
|
debug("Couldn't load avatar for contact: %s", err.message);
|
||||||
|
this.avatar.custom_image = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void set_load_remote_resources(bool enabled) {
|
private async void set_load_remote_resources(bool enabled) {
|
||||||
try {
|
try {
|
||||||
// Remove all contact email domains from trusted list
|
// Remove all contact email domains from trusted list
|
||||||
// Otherwise, user may not understand why images are always shown
|
// Otherwise, user may not understand why images are always shown
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
var email_addresses = this.contact.email_addresses;
|
var email_addresses = this.contact.email_addresses;
|
||||||
foreach (Geary.RFC822.MailboxAddress email in email_addresses) {
|
for (uint i = 0; i < email_addresses.get_n_items(); i++) {
|
||||||
|
var email = (Geary.RFC822.MailboxAddress) email_addresses.get_item(i);
|
||||||
this.config.remove_images_trusted_domain(email.domain);
|
this.config.remove_images_trusted_domain(email.domain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -214,9 +233,15 @@ public class Conversation.ContactPopover : Gtk.Popover {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_copy_email() {
|
private void on_copy_email() {
|
||||||
Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
|
Gdk.Clipboard clipboard = get_clipboard();
|
||||||
clipboard.set_text(this.mailbox.to_full_display(), -1);
|
clipboard.set_text(this.mailbox.to_full_display());
|
||||||
clipboard.store();
|
clipboard.store_async.begin(Priority.DEFAULT, null, (obj, res) => {
|
||||||
|
try {
|
||||||
|
clipboard.store_async.end(res);
|
||||||
|
} catch (Error err) {
|
||||||
|
debug("Couldn't copy email to clipboard: %s", err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_load_remote(GLib.SimpleAction action) {
|
private void on_load_remote(GLib.SimpleAction action) {
|
||||||
|
|
@ -225,7 +250,7 @@ public class Conversation.ContactPopover : Gtk.Popover {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_new_conversation() {
|
private void on_new_conversation() {
|
||||||
var main = this.get_toplevel() as Application.MainWindow;
|
var main = this.get_root() as Application.MainWindow;
|
||||||
if (main != null) {
|
if (main != null) {
|
||||||
main.application.new_composer.begin(this.mailbox);
|
main.application.new_composer.begin(this.mailbox);
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +265,7 @@ public class Conversation.ContactPopover : Gtk.Popover {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_show_conversations() {
|
private void on_show_conversations() {
|
||||||
var main = this.get_toplevel() as Application.MainWindow;
|
var main = this.get_root() as Application.MainWindow;
|
||||||
if (main != null) {
|
if (main != null) {
|
||||||
main.show_search_bar("from:%s".printf(this.mailbox.address));
|
main.show_search_bar("from:%s".printf(this.mailbox.address));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -177,12 +177,12 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
|
|
||||||
/** Determines if the email has been manually marked as being read. */
|
/** Determines if the email has been manually marked as being read. */
|
||||||
public bool is_manually_read {
|
public bool is_manually_read {
|
||||||
get { return get_style_context().has_class(MANUAL_READ_CLASS); }
|
get { return has_css_class(MANUAL_READ_CLASS); }
|
||||||
set {
|
set {
|
||||||
if (value) {
|
if (value) {
|
||||||
get_style_context().add_class(MANUAL_READ_CLASS);
|
add_css_class(MANUAL_READ_CLASS);
|
||||||
} else {
|
} else {
|
||||||
get_style_context().remove_class(MANUAL_READ_CLASS);
|
remove_css_class(MANUAL_READ_CLASS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +237,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
// window, for updating email menu trash/delete actions.
|
// window, for updating email menu trash/delete actions.
|
||||||
private bool shift_handler_installed = false;
|
private bool shift_handler_installed = false;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid actions;
|
[GtkChild] private unowned Gtk.Box actions;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Button attachments_button;
|
[GtkChild] private unowned Gtk.Button attachments_button;
|
||||||
|
|
||||||
|
|
@ -247,7 +247,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.MenuButton email_menubutton;
|
[GtkChild] private unowned Gtk.MenuButton email_menubutton;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid sub_messages;
|
[GtkChild] private unowned Gtk.Box sub_messages;
|
||||||
|
|
||||||
|
|
||||||
/** Fired when a internal link is activated */
|
/** Fired when a internal link is activated */
|
||||||
|
|
@ -284,7 +284,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
new Geary.Nonblocking.Spinlock(load_cancellable);
|
new Geary.Nonblocking.Spinlock(load_cancellable);
|
||||||
|
|
||||||
if (is_sent) {
|
if (is_sent) {
|
||||||
get_style_context().add_class(SENT_CLASS);
|
add_css_class(SENT_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the view for the primary message, hook into it
|
// Construct the view for the primary message, hook into it
|
||||||
|
|
@ -295,7 +295,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
this.contacts,
|
this.contacts,
|
||||||
this.config
|
this.config
|
||||||
);
|
);
|
||||||
this.primary_message.summary.add(this.actions);
|
this.primary_message.summary.append(this.actions);
|
||||||
connect_message_view_signals(this.primary_message);
|
connect_message_view_signals(this.primary_message);
|
||||||
|
|
||||||
// Wire up the rest of the UI
|
// Wire up the rest of the UI
|
||||||
|
|
@ -310,7 +310,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
BODY_LOAD_TIMEOUT_MSEC, this.on_body_loading_timeout
|
BODY_LOAD_TIMEOUT_MSEC, this.on_body_loading_timeout
|
||||||
);
|
);
|
||||||
|
|
||||||
pack_start(this.primary_message, true, true, 0);
|
append(this.primary_message);
|
||||||
update_email_state();
|
update_email_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -482,7 +482,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
|
|
||||||
/** Displays the raw RFC 822 source for this email. */
|
/** Displays the raw RFC 822 source for this email. */
|
||||||
public async void view_source() {
|
public async void view_source() {
|
||||||
var main = get_toplevel() as Application.MainWindow;
|
var main = get_root() as Application.MainWindow;
|
||||||
if (main != null) {
|
if (main != null) {
|
||||||
Geary.Email email = this.email;
|
Geary.Email email = this.email;
|
||||||
try {
|
try {
|
||||||
|
|
@ -567,7 +567,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
string js = "geary.addPrintHeaders(" + generator.to_data(null) + ");";
|
string js = "geary.addPrintHeaders(" + generator.to_data(null) + ");";
|
||||||
yield this.primary_message.evaluate_javascript(js, null);
|
yield this.primary_message.evaluate_javascript(js, null);
|
||||||
|
|
||||||
Gtk.Window? window = get_toplevel() as Gtk.Window;
|
Gtk.Window? window = get_root() as Gtk.Window;
|
||||||
WebKit.PrintOperation op = this.primary_message.new_print_operation();
|
WebKit.PrintOperation op = this.primary_message.new_print_operation();
|
||||||
Gtk.PrintSettings settings = new Gtk.PrintSettings();
|
Gtk.PrintSettings settings = new Gtk.PrintSettings();
|
||||||
|
|
||||||
|
|
@ -694,7 +694,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
|
|
||||||
Gee.List<Geary.RFC822.Message> sub_messages = message.get_sub_messages();
|
Gee.List<Geary.RFC822.Message> sub_messages = message.get_sub_messages();
|
||||||
if (sub_messages.size > 0) {
|
if (sub_messages.size > 0) {
|
||||||
this.primary_message.body_container.add(this.sub_messages);
|
this.primary_message.body_container.append(this.sub_messages);
|
||||||
}
|
}
|
||||||
foreach (Geary.RFC822.Message sub_message in sub_messages) {
|
foreach (Geary.RFC822.Message sub_message in sub_messages) {
|
||||||
ConversationMessage attached_message =
|
ConversationMessage attached_message =
|
||||||
|
|
@ -706,7 +706,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
);
|
);
|
||||||
connect_message_view_signals(attached_message);
|
connect_message_view_signals(attached_message);
|
||||||
attached_message.add_internal_resources(cid_resources);
|
attached_message.add_internal_resources(cid_resources);
|
||||||
this.sub_messages.add(attached_message);
|
this.sub_messages.append(attached_message);
|
||||||
this._attached_messages.add(attached_message);
|
this._attached_messages.add(attached_message);
|
||||||
attached_message.load_contacts.begin(this.load_cancellable);
|
attached_message.load_contacts.begin(this.load_cancellable);
|
||||||
yield attached_message.load_message_body(
|
yield attached_message.load_message_body(
|
||||||
|
|
@ -719,20 +719,18 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_email_state() {
|
private void update_email_state() {
|
||||||
Gtk.StyleContext style = get_style_context();
|
|
||||||
|
|
||||||
if (this.is_unread) {
|
if (this.is_unread) {
|
||||||
style.add_class(UNREAD_CLASS);
|
add_css_class(UNREAD_CLASS);
|
||||||
} else {
|
} else {
|
||||||
style.remove_class(UNREAD_CLASS);
|
remove_css_class(UNREAD_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.is_starred) {
|
if (this.is_starred) {
|
||||||
style.add_class(STARRED_CLASS);
|
add_css_class(STARRED_CLASS);
|
||||||
this.star_button.hide();
|
this.star_button.hide();
|
||||||
this.unstar_button.show();
|
this.unstar_button.show();
|
||||||
} else {
|
} else {
|
||||||
style.remove_class(STARRED_CLASS);
|
remove_css_class(STARRED_CLASS);
|
||||||
this.star_button.show();
|
this.star_button.show();
|
||||||
this.unstar_button.hide();
|
this.unstar_button.hide();
|
||||||
}
|
}
|
||||||
|
|
@ -756,7 +754,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
this.conversation.base_folder is Geary.FolderSupport.Remove
|
this.conversation.base_folder is Geary.FolderSupport.Remove
|
||||||
);
|
);
|
||||||
bool is_shift_down = false;
|
bool is_shift_down = false;
|
||||||
var main = get_toplevel() as Application.MainWindow;
|
var main = get_root() as Application.MainWindow;
|
||||||
if (main != null) {
|
if (main != null) {
|
||||||
is_shift_down = main.is_shift_down;
|
is_shift_down = main.is_shift_down;
|
||||||
|
|
||||||
|
|
@ -805,7 +803,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.email_menubutton.popover.bind_model(new_model, null);
|
this.email_menubutton.menu_model = new_model;
|
||||||
this.email_menubutton.popover.grab_focus();
|
this.email_menubutton.popover.grab_focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -814,13 +812,13 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
private void update_displayed_attachments() {
|
private void update_displayed_attachments() {
|
||||||
bool has_attachments = !this.displayed_attachments.is_empty;
|
bool has_attachments = !this.displayed_attachments.is_empty;
|
||||||
this.attachments_button.set_visible(has_attachments);
|
this.attachments_button.set_visible(has_attachments);
|
||||||
var main = get_toplevel() as Application.MainWindow;
|
var main = get_root() as Application.MainWindow;
|
||||||
|
|
||||||
if (has_attachments && main != null) {
|
if (has_attachments && main != null) {
|
||||||
this.attachments_pane = new Components.AttachmentPane(
|
this.attachments_pane = new Components.AttachmentPane(
|
||||||
false, main.attachments
|
false, main.attachments
|
||||||
);
|
);
|
||||||
this.primary_message.body_container.add(this.attachments_pane);
|
this.primary_message.body_container.append(this.attachments_pane);
|
||||||
|
|
||||||
foreach (var attachment in this.displayed_attachments) {
|
foreach (var attachment in this.displayed_attachments) {
|
||||||
this.attachments_pane.add_attachment(
|
this.attachments_pane.add_attachment(
|
||||||
|
|
@ -834,7 +832,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
this.message_body_state = FAILED;
|
this.message_body_state = FAILED;
|
||||||
this.primary_message.show_load_error_pane();
|
this.primary_message.show_load_error_pane();
|
||||||
|
|
||||||
var main = get_toplevel() as Application.MainWindow;
|
var main = get_root() as Application.MainWindow;
|
||||||
if (main != null) {
|
if (main != null) {
|
||||||
Geary.AccountInformation account = this.email_store.account.information;
|
Geary.AccountInformation account = this.email_store.account.information;
|
||||||
main.application.controller.report_problem(
|
main.application.controller.report_problem(
|
||||||
|
|
@ -853,16 +851,13 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activate_email_action(string name) {
|
private void activate_email_action(string name) {
|
||||||
GLib.ActionGroup? email_actions = get_action_group(
|
//XXX GTK4 check if this works still
|
||||||
ConversationListBox.EMAIL_ACTION_GROUP_NAME
|
string action_name = ConversationListBox.EMAIL_ACTION_GROUP_NAME + "." + name;
|
||||||
);
|
activate_action_variant(action_name, this.email.id.to_variant());
|
||||||
if (email_actions != null) {
|
|
||||||
email_actions.activate_action(name, this.email.id.to_variant());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_email_menu() {
|
private void on_email_menu(Gtk.MenuButton email_menubutton) {
|
||||||
update_email_menu();
|
update_email_menu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -885,7 +880,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
|
||||||
private void on_save_image(string uri,
|
private void on_save_image(string uri,
|
||||||
string? alt_text,
|
string? alt_text,
|
||||||
Geary.Memory.Buffer? content) {
|
Geary.Memory.Buffer? content) {
|
||||||
var main = get_toplevel() as Application.MainWindow;
|
var main = get_root() as Application.MainWindow;
|
||||||
if (main != null) {
|
if (main != null) {
|
||||||
if (uri.has_prefix(Components.WebView.CID_URL_PREFIX)) {
|
if (uri.has_prefix(Components.WebView.CID_URL_PREFIX)) {
|
||||||
string cid = uri.substring(Components.WebView.CID_URL_PREFIX.length);
|
string cid = uri.substring(Components.WebView.CID_URL_PREFIX.length);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
* ConversationListBox sorts by the {@link Geary.Email.date} field
|
* ConversationListBox sorts by the {@link Geary.Email.date} field
|
||||||
* (the Date: header), as that's the date displayed to the user.
|
* (the Date: header), as that's the date displayed to the user.
|
||||||
*/
|
*/
|
||||||
public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
public class ConversationListBox : Adw.Bin, Geary.BaseInterface {
|
||||||
|
|
||||||
/** Fields that must be available for listing conversation email. */
|
/** Fields that must be available for listing conversation email. */
|
||||||
public const Geary.Email.Field REQUIRED_FIELDS = (
|
public const Geary.Email.Field REQUIRED_FIELDS = (
|
||||||
|
|
@ -194,17 +194,18 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
public void unmark_terms() {
|
public void unmark_terms() {
|
||||||
cancel();
|
cancel();
|
||||||
|
|
||||||
this.list.foreach((child) => {
|
for (int i = 0; true; i++) {
|
||||||
EmailRow? row = child as EmailRow;
|
unowned var row = this.list.listbox.get_row_at_index(i) as EmailRow;
|
||||||
if (row != null) {
|
if (row == null)
|
||||||
if (row.is_search_match) {
|
break;
|
||||||
row.is_search_match = false;
|
|
||||||
foreach (ConversationMessage msg_view in row.view) {
|
if (row.is_search_match) {
|
||||||
msg_view.unmark_search_terms();
|
row.is_search_match = false;
|
||||||
}
|
foreach (ConversationMessage msg_view in row.view) {
|
||||||
}
|
msg_view.unmark_search_terms();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
|
|
@ -324,58 +325,46 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
// Enables firing the should_scroll signal when this row is
|
// Enables firing the should_scroll signal when this row is
|
||||||
// allocated a size
|
// allocated a size
|
||||||
public void enable_should_scroll() {
|
public void enable_should_scroll() {
|
||||||
this.size_allocate.connect(on_size_allocate);
|
//XXX GTK4 - once we work with models, we won't need this
|
||||||
|
// this.size_allocate.connect(on_size_allocate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_css_class() {
|
private void update_css_class() {
|
||||||
if (this.is_expanded)
|
if (this.is_expanded)
|
||||||
get_style_context().add_class(EXPANDED_CLASS);
|
add_css_class(EXPANDED_CLASS);
|
||||||
else
|
else
|
||||||
get_style_context().remove_class(EXPANDED_CLASS);
|
remove_css_class(EXPANDED_CLASS);
|
||||||
|
|
||||||
update_previous_sibling_css_class();
|
update_previous_sibling_css_class();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is mostly taken form libhandy HdyExpanderRow
|
|
||||||
private Gtk.Widget? get_previous_sibling() {
|
|
||||||
if (this.parent is Gtk.Container) {
|
|
||||||
var siblings = this.parent.get_children();
|
|
||||||
unowned List<weak Gtk.Widget> l;
|
|
||||||
for (l = siblings; l != null && l.next != null && l.next.data != this; l = l.next);
|
|
||||||
|
|
||||||
if (l != null && l.next != null && l.next.data == this) {
|
|
||||||
return l.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_previous_sibling_css_class() {
|
private void update_previous_sibling_css_class() {
|
||||||
var previous_sibling = get_previous_sibling();
|
var previous_sibling = get_prev_sibling();
|
||||||
if (previous_sibling != null) {
|
if (previous_sibling != null) {
|
||||||
if (this.is_expanded)
|
if (this.is_expanded)
|
||||||
previous_sibling.get_style_context().add_class("geary-expanded-previous-sibling");
|
previous_sibling.add_css_class("geary-expanded-previous-sibling");
|
||||||
else
|
else
|
||||||
previous_sibling.get_style_context().remove_class("geary-expanded-previous-sibling");
|
previous_sibling.remove_css_class("geary-expanded-previous-sibling");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected inline void set_style_context_class(string class_name, bool value) {
|
protected inline void set_style_context_class(string class_name, bool value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
get_style_context().add_class(class_name);
|
add_css_class(class_name);
|
||||||
} else {
|
} else {
|
||||||
get_style_context().remove_class(class_name);
|
remove_css_class(class_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX GTK4 - once we work with models, we won't need this
|
||||||
|
#if 0
|
||||||
protected void on_size_allocate() {
|
protected void on_size_allocate() {
|
||||||
// Disable should_scroll so we don't keep on scrolling
|
// Disable should_scroll so we don't keep on scrolling
|
||||||
// later, like when the window has been resized.
|
// later, like when the window has been resized.
|
||||||
this.size_allocate.disconnect(on_size_allocate);
|
this.size_allocate.disconnect(on_size_allocate);
|
||||||
should_scroll();
|
should_scroll();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -391,7 +380,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
|
|
||||||
// Does the row contain an email matching the current search?
|
// Does the row contain an email matching the current search?
|
||||||
public bool is_search_match {
|
public bool is_search_match {
|
||||||
get { return get_style_context().has_class(MATCH_CLASS); }
|
get { return has_css_class(MATCH_CLASS); }
|
||||||
set {
|
set {
|
||||||
set_style_context_class(MATCH_CLASS, value);
|
set_style_context_class(MATCH_CLASS, value);
|
||||||
this.is_pinned = value;
|
this.is_pinned = value;
|
||||||
|
|
@ -407,7 +396,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
public EmailRow(ConversationEmail view) {
|
public EmailRow(ConversationEmail view) {
|
||||||
base(view.email);
|
base(view.email);
|
||||||
this.view = view;
|
this.view = view;
|
||||||
add(view);
|
this.child = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void expand()
|
public override async void expand()
|
||||||
|
|
@ -446,14 +435,12 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
|
|
||||||
public LoadingRow() {
|
public LoadingRow() {
|
||||||
base(null);
|
base(null);
|
||||||
get_style_context().add_class(LOADING_CLASS);
|
add_css_class(LOADING_CLASS);
|
||||||
|
|
||||||
Gtk.Spinner spinner = new Gtk.Spinner();
|
Gtk.Spinner spinner = new Gtk.Spinner();
|
||||||
spinner.height_request = 16;
|
spinner.height_request = 16;
|
||||||
spinner.width_request = 16;
|
spinner.width_request = 16;
|
||||||
spinner.show();
|
this.child = spinner;
|
||||||
spinner.start();
|
|
||||||
add(spinner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -470,7 +457,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
base(view.referred);
|
base(view.referred);
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.is_expanded = true;
|
this.is_expanded = true;
|
||||||
add(this.view);
|
this.child = this.view;
|
||||||
|
|
||||||
this.focus_on_click = false;
|
this.focus_on_click = false;
|
||||||
}
|
}
|
||||||
|
|
@ -479,23 +466,13 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
|
|
||||||
|
|
||||||
static construct {
|
static construct {
|
||||||
// Set up custom keybindings
|
add_shortcut(new Gtk.Shortcut(Gtk.ShortcutTrigger.parse_string("Space"),
|
||||||
unowned Gtk.BindingSet bindings = Gtk.BindingSet.by_class(
|
new Gtk.NamedAction("focus-next")));
|
||||||
(ObjectClass) typeof(ConversationListBox).class_ref()
|
add_shortcut(new Gtk.Shortcut(Gtk.ShortcutTrigger.parse_string("<Shift>Space"),
|
||||||
);
|
new Gtk.NamedAction("focus-prev")));
|
||||||
Gtk.BindingEntry.add_signal(
|
|
||||||
bindings, Gdk.Key.space, 0, "focus-next", 0
|
|
||||||
);
|
|
||||||
Gtk.BindingEntry.add_signal(
|
|
||||||
bindings, Gdk.Key.KP_Space, 0, "focus-next", 0
|
|
||||||
);
|
|
||||||
Gtk.BindingEntry.add_signal(
|
|
||||||
bindings, Gdk.Key.space, Gdk.ModifierType.SHIFT_MASK, "focus-prev", 0
|
|
||||||
);
|
|
||||||
Gtk.BindingEntry.add_signal(
|
|
||||||
bindings, Gdk.Key.KP_Space, Gdk.ModifierType.SHIFT_MASK, "focus-prev", 0
|
|
||||||
);
|
|
||||||
|
|
||||||
|
//XXX GTK4
|
||||||
|
#if 0
|
||||||
Gtk.BindingEntry.add_signal(
|
Gtk.BindingEntry.add_signal(
|
||||||
bindings, Gdk.Key.Up, 0, "scroll", 1,
|
bindings, Gdk.Key.Up, 0, "scroll", 1,
|
||||||
typeof(Gtk.ScrollType), Gtk.ScrollType.STEP_UP
|
typeof(Gtk.ScrollType), Gtk.ScrollType.STEP_UP
|
||||||
|
|
@ -520,6 +497,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
bindings, Gdk.Key.End, 0, "scroll", 1,
|
bindings, Gdk.Key.End, 0, "scroll", 1,
|
||||||
typeof(Gtk.ScrollType), Gtk.ScrollType.END
|
typeof(Gtk.ScrollType), Gtk.ScrollType.END
|
||||||
);
|
);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int on_sort(Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
|
private static int on_sort(Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
|
||||||
|
|
@ -535,6 +513,8 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
return Geary.Email.compare_sent_date_ascending(email1, email2);
|
return Geary.Email.compare_sent_date_ascending(email1, email2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal Gtk.ListBox listbox { get; set; default = new Gtk.ListBox(); }
|
||||||
|
|
||||||
|
|
||||||
/** Conversation being displayed. */
|
/** Conversation being displayed. */
|
||||||
public Geary.App.Conversation conversation { get; private set; }
|
public Geary.App.Conversation conversation { get; private set; }
|
||||||
|
|
@ -588,7 +568,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
var handled = false;
|
var handled = false;
|
||||||
var composer = this.current_composer;
|
var composer = this.current_composer;
|
||||||
if (composer != null) {
|
if (composer != null) {
|
||||||
var window = get_toplevel() as Gtk.Window;
|
var window = get_root() as Gtk.Window;
|
||||||
if (window != null) {
|
if (window != null) {
|
||||||
var focused = window.get_focus();
|
var focused = window.get_focus();
|
||||||
if (focused != null &&
|
if (focused != null &&
|
||||||
|
|
@ -612,7 +592,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
Gtk.Adjustment adj = get_adjustment();
|
Gtk.Adjustment adj = this.listbox.get_adjustment();
|
||||||
double value = adj.get_value();
|
double value = adj.get_value();
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Gtk.ScrollType.STEP_UP:
|
case Gtk.ScrollType.STEP_UP:
|
||||||
|
|
@ -645,14 +625,14 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
/** Keyboard action to shift focus to the next message, if any. */
|
/** Keyboard action to shift focus to the next message, if any. */
|
||||||
[Signal (action=true)]
|
[Signal (action=true)]
|
||||||
public virtual signal void focus_next() {
|
public virtual signal void focus_next() {
|
||||||
this.move_cursor(Gtk.MovementStep.DISPLAY_LINES, 1);
|
this.listbox.move_cursor(Gtk.MovementStep.DISPLAY_LINES, 1, false, false);
|
||||||
this.mark_read_timer.start();
|
this.mark_read_timer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Keyboard action to shift focus to the prev message, if any. */
|
/** Keyboard action to shift focus to the prev message, if any. */
|
||||||
[Signal (action=true)]
|
[Signal (action=true)]
|
||||||
public virtual signal void focus_prev() {
|
public virtual signal void focus_prev() {
|
||||||
this.move_cursor(Gtk.MovementStep.DISPLAY_LINES, -1);
|
this.listbox.move_cursor(Gtk.MovementStep.DISPLAY_LINES, -1, false, false);
|
||||||
this.mark_read_timer.start();
|
this.mark_read_timer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -702,23 +682,20 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
MARK_READ_TIMEOUT_MSEC, this.check_mark_read
|
MARK_READ_TIMEOUT_MSEC, this.check_mark_read
|
||||||
);
|
);
|
||||||
|
|
||||||
this.selection_mode = NONE;
|
this.child = this.listbox;
|
||||||
|
this.listbox.selection_mode = NONE;
|
||||||
|
this.listbox.valign = Gtk.Align.START;
|
||||||
|
|
||||||
get_style_context().add_class("content");
|
this.listbox.add_css_class("content");
|
||||||
get_style_context().add_class("background");
|
this.listbox.add_css_class("conversation-listbox");
|
||||||
get_style_context().add_class("conversation-listbox");
|
|
||||||
|
|
||||||
/* we need to update the previous sibling style class when rows are added or removed */
|
this.listbox.set_adjustment(adjustment);
|
||||||
add.connect(update_previous_sibling_css_class);
|
this.listbox.set_sort_func(ConversationListBox.on_sort);
|
||||||
remove.connect(update_previous_sibling_css_class);
|
|
||||||
|
|
||||||
set_adjustment(adjustment);
|
|
||||||
set_sort_func(ConversationListBox.on_sort);
|
|
||||||
|
|
||||||
this.email_actions.add_action_entries(email_action_entries, this);
|
this.email_actions.add_action_entries(email_action_entries, this);
|
||||||
insert_action_group(EMAIL_ACTION_GROUP_NAME, this.email_actions);
|
insert_action_group(EMAIL_ACTION_GROUP_NAME, this.email_actions);
|
||||||
|
|
||||||
this.row_activated.connect(on_row_activated);
|
this.listbox.row_activated.connect(on_row_activated);
|
||||||
|
|
||||||
this.conversation.appended.connect(on_conversation_appended);
|
this.conversation.appended.connect(on_conversation_appended);
|
||||||
this.conversation.trimmed.connect(on_conversation_trimmed);
|
this.conversation.trimmed.connect(on_conversation_trimmed);
|
||||||
|
|
@ -729,34 +706,45 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
base_unref();
|
base_unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void destroy() {
|
public override void dispose() {
|
||||||
this.search.cancel();
|
this.search.cancel();
|
||||||
this.cancellable.cancel();
|
this.cancellable.cancel();
|
||||||
this.email_rows.clear();
|
this.email_rows.clear();
|
||||||
this.mark_read_timer.reset();
|
this.mark_read_timer.reset();
|
||||||
base.destroy();
|
base.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void append(Gtk.Widget child) {
|
||||||
|
this.listbox.append(child);
|
||||||
|
update_previous_sibling_css_class();
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void remove(Gtk.Widget child) {
|
||||||
|
this.listbox.remove(child);
|
||||||
|
update_previous_sibling_css_class();
|
||||||
}
|
}
|
||||||
|
|
||||||
// For some reason insert doesn't emit the add event
|
|
||||||
public new void insert(Gtk.Widget child, int position) {
|
public new void insert(Gtk.Widget child, int position) {
|
||||||
base.insert(child, position);
|
this.listbox.insert(child, position);
|
||||||
update_previous_sibling_css_class();
|
update_previous_sibling_css_class();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is mostly taken form libhandy HdyExpanderRow
|
// This is mostly taken form libhandy HdyExpanderRow
|
||||||
private void update_previous_sibling_css_class() {
|
private void update_previous_sibling_css_class() {
|
||||||
var siblings = this.get_children();
|
unowned var child = get_first_child() as ConversationRow;
|
||||||
unowned List<weak Gtk.Widget> l;
|
if (child == null)
|
||||||
for (l = siblings; l != null && l.next != null && l.next.data != this; l = l.next) {
|
return;
|
||||||
if (l != null && l.next != null) {
|
|
||||||
var row = l.next.data as ConversationRow;
|
unowned var next = child.get_next_sibling() as ConversationRow;
|
||||||
if (row != null) {
|
while (next != null) {
|
||||||
if (row.is_expanded) {
|
|
||||||
l.data.get_style_context().add_class("geary-expanded-previous-sibling");
|
child = next;
|
||||||
} else {
|
next = child.get_next_sibling() as ConversationRow;
|
||||||
l.data.get_style_context().remove_class("geary-expanded-previous-sibling");
|
|
||||||
}
|
if (next.is_expanded) {
|
||||||
}
|
child.add_css_class("geary-expanded-previous-sibling");
|
||||||
|
} else {
|
||||||
|
child.remove_css_class("geary-expanded-previous-sibling");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -764,7 +752,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
public async void load_conversation(Gee.Collection<Geary.EmailIdentifier> scroll_to,
|
public async void load_conversation(Gee.Collection<Geary.EmailIdentifier> scroll_to,
|
||||||
Geary.SearchQuery? query)
|
Geary.SearchQuery? query)
|
||||||
throws GLib.Error {
|
throws GLib.Error {
|
||||||
set_sort_func(null);
|
this.listbox.set_sort_func(null);
|
||||||
|
|
||||||
Gee.Collection<Geary.Email>? all_email = this.conversation.get_emails(
|
Gee.Collection<Geary.Email>? all_email = this.conversation.get_emails(
|
||||||
Geary.App.Conversation.Ordering.SENT_DATE_ASCENDING
|
Geary.App.Conversation.Ordering.SENT_DATE_ASCENDING
|
||||||
|
|
@ -850,7 +838,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
public void scroll_to_messages(Gee.Collection<Geary.EmailIdentifier> targets) {
|
public void scroll_to_messages(Gee.Collection<Geary.EmailIdentifier> targets) {
|
||||||
// Get the currently displayed email, allowing for some
|
// Get the currently displayed email, allowing for some
|
||||||
// padding at the top
|
// padding at the top
|
||||||
Gtk.ListBoxRow? current_child = get_row_at_y(32);
|
Gtk.ListBoxRow? current_child = this.listbox.get_row_at_y(32);
|
||||||
|
|
||||||
// Find the row currently at the top of the viewport
|
// Find the row currently at the top of the viewport
|
||||||
EmailRow? current = null;
|
EmailRow? current = null;
|
||||||
|
|
@ -858,7 +846,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
int pos = current_child.get_index();
|
int pos = current_child.get_index();
|
||||||
do {
|
do {
|
||||||
current = current_child as EmailRow;
|
current = current_child as EmailRow;
|
||||||
current_child = get_row_at_index(--pos);
|
current_child = this.listbox.get_row_at_index(--pos);
|
||||||
} while (current == null && pos > 0);
|
} while (current == null && pos > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -904,12 +892,12 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
ConversationEmail? view = get_selection_view();
|
ConversationEmail? view = get_selection_view();
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
EmailRow? last = null;
|
EmailRow? last = null;
|
||||||
this.foreach((child) => {
|
for (int i = 0; true; i++) {
|
||||||
EmailRow? row = child as EmailRow;
|
unowned var row = this.listbox.get_row_at_index(i) as EmailRow;
|
||||||
if (row != null) {
|
if (row == null)
|
||||||
last = row;
|
break;
|
||||||
}
|
last = row;
|
||||||
});
|
}
|
||||||
|
|
||||||
if (last != null) {
|
if (last != null) {
|
||||||
view = last.view;
|
view = last.view;
|
||||||
|
|
@ -953,7 +941,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
// Use row param rather than row var from closure to avoid a
|
// Use row param rather than row var from closure to avoid a
|
||||||
// circular ref.
|
// circular ref.
|
||||||
row.should_scroll.connect((row) => { scroll_to_row(row); });
|
row.should_scroll.connect((row) => { scroll_to_row(row); });
|
||||||
add(row);
|
append(row);
|
||||||
this.current_composer = row;
|
this.current_composer = row;
|
||||||
|
|
||||||
embed.composer.notify["saved-id"].connect(
|
embed.composer.notify["saved-id"].connect(
|
||||||
|
|
@ -1060,22 +1048,21 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
|
|
||||||
// Since first rows may have extra margin, remove that from
|
// Since first rows may have extra margin, remove that from
|
||||||
// the height of rows when adjusting scrolling.
|
// the height of rows when adjusting scrolling.
|
||||||
Gtk.ListBoxRow initial_row = get_row_at_index(0);
|
Gtk.ListBoxRow initial_row = this.listbox.get_row_at_index(0);
|
||||||
int loading_height = 0;
|
int loading_height = 0;
|
||||||
if (initial_row is LoadingRow) {
|
if (initial_row is LoadingRow) {
|
||||||
loading_height = Util.Gtk.get_border_box_height(initial_row);
|
loading_height = Util.Gtk.get_border_box_height(initial_row);
|
||||||
remove(initial_row);
|
remove(initial_row);
|
||||||
// Adjust for the changed margin of the first row
|
// Adjust for the changed margin of the first row
|
||||||
var first_row = get_row_at_index(0);
|
var first_row = this.listbox.get_row_at_index(0);
|
||||||
var style = first_row.get_style_context();
|
var margin = first_row.get_style_context().get_margin();;
|
||||||
var margin = style.get_margin(style.get_state());
|
|
||||||
loading_height -= margin.top;
|
loading_height -= margin.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
// None of these will be interesting, so just add them all,
|
// None of these will be interesting, so just add them all,
|
||||||
// but keep the scrollbar adjusted so that the first
|
// but keep the scrollbar adjusted so that the first
|
||||||
// interesting message remains visible.
|
// interesting message remains visible.
|
||||||
Gtk.Adjustment listbox_adj = get_adjustment();
|
Gtk.Adjustment listbox_adj = this.listbox.get_adjustment();
|
||||||
int i_mail_loaded = 0;
|
int i_mail_loaded = 0;
|
||||||
foreach (Geary.Email email in to_insert) {
|
foreach (Geary.Email email in to_insert) {
|
||||||
EmailRow row = add_email(email, false);
|
EmailRow row = add_email(email, false);
|
||||||
|
|
@ -1096,7 +1083,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
++i_mail_loaded;
|
++i_mail_loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_sort_func(on_sort);
|
this.listbox.set_sort_func(on_sort);
|
||||||
|
|
||||||
if (query != null) {
|
if (query != null) {
|
||||||
// XXX this sucks for large conversations because it can take
|
// XXX this sucks for large conversations because it can take
|
||||||
|
|
@ -1184,19 +1171,22 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
);
|
);
|
||||||
|
|
||||||
ConversationMessage conversation_message = view.primary_message;
|
ConversationMessage conversation_message = view.primary_message;
|
||||||
|
// XXX GTK4 - I think we can do this separately
|
||||||
|
#if 0
|
||||||
conversation_message.body_container.button_release_event.connect_after((event) => {
|
conversation_message.body_container.button_release_event.connect_after((event) => {
|
||||||
// Consume all non-consumed clicks so the row is not
|
// Consume all non-consumed clicks so the row is not
|
||||||
// inadvertently activated after clicking on the
|
// inadvertently activated after clicking on the
|
||||||
// email body.
|
// email body.
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
EmailRow row = new EmailRow(view);
|
EmailRow row = new EmailRow(view);
|
||||||
row.email_loaded.connect((e) => { email_loaded(e); });
|
row.email_loaded.connect((e) => { email_loaded(e); });
|
||||||
this.email_rows.set(email.id, row);
|
this.email_rows.set(email.id, row);
|
||||||
|
|
||||||
if (append_row) {
|
if (append_row) {
|
||||||
add(row);
|
append(row);
|
||||||
} else {
|
} else {
|
||||||
insert(row, 0);
|
insert(row, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -1222,17 +1212,17 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
|
|
||||||
// Use set_value rather than clamp_value since we want to
|
// Use set_value rather than clamp_value since we want to
|
||||||
// scroll to the top of the window.
|
// scroll to the top of the window.
|
||||||
get_adjustment().set_value(y);
|
this.listbox.get_adjustment().set_value(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scroll_to_anchor(EmailRow row, int anchor_y) {
|
private void scroll_to_anchor(EmailRow row, int anchor_y) {
|
||||||
Gtk.Allocation? alloc = null;
|
Gtk.Allocation? alloc = null;
|
||||||
row.get_allocation(out alloc);
|
row.get_allocation(out alloc);
|
||||||
|
|
||||||
int x = 0, y = 0;
|
double x = 0, y = 0;
|
||||||
row.view.primary_message.web_view_translate_coordinates(row, x, anchor_y, out x, out y);
|
row.view.primary_message.web_view_translate_coordinates(row, 0, anchor_y, out x, out y);
|
||||||
|
|
||||||
Gtk.Adjustment adj = get_adjustment();
|
Gtk.Adjustment adj = this.listbox.get_adjustment();
|
||||||
y = alloc.y + y;
|
y = alloc.y + y;
|
||||||
adj.set_value(y);
|
adj.set_value(y);
|
||||||
|
|
||||||
|
|
@ -1244,15 +1234,18 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
private void check_mark_read() {
|
private void check_mark_read() {
|
||||||
Gee.List<Geary.EmailIdentifier> email_ids =
|
Gee.List<Geary.EmailIdentifier> email_ids =
|
||||||
new Gee.LinkedList<Geary.EmailIdentifier>();
|
new Gee.LinkedList<Geary.EmailIdentifier>();
|
||||||
Gtk.Adjustment adj = get_adjustment();
|
Gtk.Adjustment adj = this.listbox.get_adjustment();
|
||||||
int top_bound = (int) adj.value;
|
int top_bound = (int) adj.value;
|
||||||
int bottom_bound = top_bound + (int) adj.page_size;
|
int bottom_bound = top_bound + (int) adj.page_size;
|
||||||
|
|
||||||
this.foreach((child) => {
|
for (int i = 0; true; i++) {
|
||||||
|
unowned var row = this.listbox.get_row_at_index(i) as EmailRow;
|
||||||
|
if (row == null)
|
||||||
|
break;
|
||||||
|
|
||||||
// Don't bother with not-yet-loaded emails since the
|
// Don't bother with not-yet-loaded emails since the
|
||||||
// size of the body will be off, affecting the visibility
|
// size of the body will be off, affecting the visibility
|
||||||
// of emails further down the conversation.
|
// of emails further down the conversation.
|
||||||
EmailRow? row = child as EmailRow;
|
|
||||||
ConversationEmail? view = (row != null) ? row.view : null;
|
ConversationEmail? view = (row != null) ? row.view : null;
|
||||||
Geary.Email? email = (view != null) ? view.email : null;
|
Geary.Email? email = (view != null) ? view.email : null;
|
||||||
if (row != null &&
|
if (row != null &&
|
||||||
|
|
@ -1261,8 +1254,8 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
!view.is_manually_read &&
|
!view.is_manually_read &&
|
||||||
email.is_unread().is_certain()) {
|
email.is_unread().is_certain()) {
|
||||||
ConversationMessage conversation_message = view.primary_message;
|
ConversationMessage conversation_message = view.primary_message;
|
||||||
int body_top = 0;
|
double body_top = 0;
|
||||||
int body_left = 0;
|
double body_left = 0;
|
||||||
conversation_message.web_view_translate_coordinates(
|
conversation_message.web_view_translate_coordinates(
|
||||||
this,
|
this,
|
||||||
0, 0,
|
0, 0,
|
||||||
|
|
@ -1270,12 +1263,12 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
);
|
);
|
||||||
|
|
||||||
int body_height = conversation_message.web_view_get_allocated_height();
|
int body_height = conversation_message.web_view_get_allocated_height();
|
||||||
int body_bottom = body_top + body_height;
|
int body_bottom = (int) body_top + body_height;
|
||||||
|
|
||||||
// Only mark the email as read if it's actually visible
|
// Only mark the email as read if it's actually visible
|
||||||
if (body_height > 0 &&
|
if (body_height > 0 &&
|
||||||
body_bottom > top_bound &&
|
body_bottom > top_bound &&
|
||||||
body_top + MARK_READ_PADDING < bottom_bound) {
|
(int) body_top + MARK_READ_PADDING < bottom_bound) {
|
||||||
email_ids.add(view.email.id);
|
email_ids.add(view.email.id);
|
||||||
|
|
||||||
// Since it can take some time for the new flags
|
// Since it can take some time for the new flags
|
||||||
|
|
@ -1284,7 +1277,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
view.is_manually_read = true;
|
view.is_manually_read = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
if (email_ids.size > 0) {
|
if (email_ids.size > 0) {
|
||||||
mark_email(email_ids, null, Geary.EmailFlags.UNREAD);
|
mark_email(email_ids, null, Geary.EmailFlags.UNREAD);
|
||||||
|
|
@ -1401,7 +1394,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
// be appended last. Finally, don't let rows with active
|
// be appended last. Finally, don't let rows with active
|
||||||
// composers be collapsed.
|
// composers be collapsed.
|
||||||
if (row.is_expanded) {
|
if (row.is_expanded) {
|
||||||
if (get_row_at_index(row.get_index() + 1) != null) {
|
if (this.listbox.get_row_at_index(row.get_index() + 1) != null) {
|
||||||
row.collapse();
|
row.collapse();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1481,15 +1474,19 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
|
||||||
Geary.Email email = view.email;
|
Geary.Email email = view.email;
|
||||||
var ids = new Gee.LinkedList<Geary.EmailIdentifier>();
|
var ids = new Gee.LinkedList<Geary.EmailIdentifier>();
|
||||||
ids.add(email.id);
|
ids.add(email.id);
|
||||||
this.foreach((row) => {
|
for (int i = 0; true; i++) {
|
||||||
if (row.get_visible()) {
|
unowned var row = this.listbox.get_row_at_index(i) as EmailRow;
|
||||||
Geary.Email other = ((EmailRow) row).view.email;
|
if (row == null)
|
||||||
if (Geary.Email.compare_sent_date_ascending(
|
break;
|
||||||
email, other) < 0) {
|
|
||||||
ids.add(other.id);
|
if (row.visible) {
|
||||||
}
|
Geary.Email other = row.view.email;
|
||||||
|
if (Geary.Email.compare_sent_date_ascending(
|
||||||
|
email, other) < 0) {
|
||||||
|
ids.add(other.id);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
mark_email(ids, Geary.EmailFlags.UNREAD, null);
|
mark_email(ids, Geary.EmailFlags.UNREAD, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
* embeds at least one instance of this class.
|
* embeds at least one instance of this class.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/conversation-message.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/conversation-message.ui")]
|
||||||
public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
public class ConversationMessage : Gtk.Box, Geary.BaseInterface {
|
||||||
|
|
||||||
|
|
||||||
private const string FROM_CLASS = "geary-from";
|
private const string FROM_CLASS = "geary-from";
|
||||||
|
|
@ -65,9 +65,6 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
|
|
||||||
private string search_value;
|
private string search_value;
|
||||||
|
|
||||||
private Gtk.Bin container;
|
|
||||||
|
|
||||||
|
|
||||||
public ContactFlowBoxChild(Application.Contact contact,
|
public ContactFlowBoxChild(Application.Contact contact,
|
||||||
Geary.RFC822.MailboxAddress source,
|
Geary.RFC822.MailboxAddress source,
|
||||||
Type address_type = Type.OTHER) {
|
Type address_type = Type.OTHER) {
|
||||||
|
|
@ -77,40 +74,34 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
this.search_value = source.to_searchable_string().casefold();
|
this.search_value = source.to_searchable_string().casefold();
|
||||||
|
|
||||||
// Update prelight state when mouse-overed.
|
// Update prelight state when mouse-overed.
|
||||||
Gtk.EventBox events = new Gtk.EventBox();
|
Gtk.EventControllerMotion controller = new Gtk.EventControllerMotion();
|
||||||
events.add_events(
|
controller.enter.connect(on_prelight_enter);
|
||||||
Gdk.EventMask.ENTER_NOTIFY_MASK |
|
controller.leave.connect(on_prelight_leave);
|
||||||
Gdk.EventMask.LEAVE_NOTIFY_MASK
|
add_controller(controller);
|
||||||
);
|
|
||||||
events.set_visible_window(false);
|
|
||||||
events.enter_notify_event.connect(on_prelight_in_event);
|
|
||||||
events.leave_notify_event.connect(on_prelight_out_event);
|
|
||||||
|
|
||||||
add(events);
|
|
||||||
this.container = events;
|
|
||||||
set_halign(Gtk.Align.START);
|
set_halign(Gtk.Align.START);
|
||||||
|
|
||||||
this.contact.changed.connect(on_contact_changed);
|
this.contact.changed.connect(on_contact_changed);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void destroy() {
|
public override void dispose() {
|
||||||
this.contact.changed.disconnect(on_contact_changed);
|
this.contact.changed.disconnect(on_contact_changed);
|
||||||
base.destroy();
|
base.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool highlight_search_term(string term) {
|
public bool highlight_search_term(string term) {
|
||||||
bool found = term in this.search_value;
|
bool found = term in this.search_value;
|
||||||
if (found) {
|
if (found) {
|
||||||
get_style_context().add_class(MATCH_CLASS);
|
add_css_class(MATCH_CLASS);
|
||||||
} else {
|
} else {
|
||||||
get_style_context().remove_class(MATCH_CLASS);
|
remove_css_class(MATCH_CLASS);
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unmark_search_terms() {
|
public void unmark_search_terms() {
|
||||||
get_style_context().remove_class(MATCH_CLASS);
|
remove_css_class(MATCH_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update() {
|
private void update() {
|
||||||
|
|
@ -120,28 +111,26 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
// both cases, but we can't yet include CSS classes in
|
// both cases, but we can't yet include CSS classes in
|
||||||
// Pango markup. See Bug 766763.
|
// Pango markup. See Bug 766763.
|
||||||
|
|
||||||
Gtk.Grid address_parts = new Gtk.Grid();
|
var address_parts = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
|
||||||
|
|
||||||
bool is_spoofed = this.source.is_spoofed();
|
bool is_spoofed = this.source.is_spoofed();
|
||||||
if (is_spoofed) {
|
if (is_spoofed) {
|
||||||
Gtk.Image spoof_img = new Gtk.Image.from_icon_name(
|
var spoof_img = new Gtk.Image.from_icon_name("dialog-warning-symbolic");
|
||||||
"dialog-warning-symbolic", Gtk.IconSize.SMALL_TOOLBAR
|
|
||||||
);
|
|
||||||
this.set_tooltip_text(
|
this.set_tooltip_text(
|
||||||
_("This email address may have been forged")
|
_("This email address may have been forged")
|
||||||
);
|
);
|
||||||
address_parts.add(spoof_img);
|
address_parts.append(spoof_img);
|
||||||
get_style_context().add_class(SPOOF_CLASS);
|
add_css_class(SPOOF_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.Label primary = new Gtk.Label(null);
|
Gtk.Label primary = new Gtk.Label(null);
|
||||||
primary.ellipsize = Pango.EllipsizeMode.END;
|
primary.ellipsize = Pango.EllipsizeMode.END;
|
||||||
primary.set_halign(Gtk.Align.START);
|
primary.set_halign(Gtk.Align.START);
|
||||||
primary.get_style_context().add_class(PRIMARY_CLASS);
|
primary.add_css_class(PRIMARY_CLASS);
|
||||||
if (this.address_type == Type.FROM) {
|
if (this.address_type == Type.FROM) {
|
||||||
primary.get_style_context().add_class(FROM_CLASS);
|
primary.add_css_class(FROM_CLASS);
|
||||||
}
|
}
|
||||||
address_parts.add(primary);
|
address_parts.append(primary);
|
||||||
|
|
||||||
string display_address = this.source.to_address_display("", "");
|
string display_address = this.source.to_address_display("", "");
|
||||||
|
|
||||||
|
|
@ -173,32 +162,24 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
Gtk.Label secondary = new Gtk.Label(null);
|
Gtk.Label secondary = new Gtk.Label(null);
|
||||||
secondary.ellipsize = Pango.EllipsizeMode.END;
|
secondary.ellipsize = Pango.EllipsizeMode.END;
|
||||||
secondary.set_halign(Gtk.Align.START);
|
secondary.set_halign(Gtk.Align.START);
|
||||||
secondary.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
secondary.add_css_class("dim-label");
|
||||||
secondary.set_text(display_address);
|
secondary.set_text(display_address);
|
||||||
address_parts.add(secondary);
|
address_parts.append(secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.Widget? existing_ui = this.container.get_child();
|
this.child = address_parts;
|
||||||
if (existing_ui != null) {
|
|
||||||
this.container.remove(existing_ui);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.container.add(address_parts);
|
|
||||||
show_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_contact_changed() {
|
private void on_contact_changed() {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_prelight_in_event(Gdk.Event event) {
|
private void on_prelight_enter(Gtk.EventControllerMotion controller, double x, double y) {
|
||||||
set_state_flags(Gtk.StateFlags.PRELIGHT, false);
|
set_state_flags(Gtk.StateFlags.PRELIGHT, false);
|
||||||
return Gdk.EVENT_STOP;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_prelight_out_event(Gdk.Event event) {
|
private void on_prelight_leave(Gtk.EventControllerMotion controller) {
|
||||||
unset_state_flags(Gtk.StateFlags.PRELIGHT);
|
unset_state_flags(Gtk.StateFlags.PRELIGHT);
|
||||||
return Gdk.EVENT_STOP;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +188,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
* A FlowBox that limits its contents to 12 items until a link is
|
* A FlowBox that limits its contents to 12 items until a link is
|
||||||
* clicked to expand it. Used for to, cc, and bcc fields.
|
* clicked to expand it. Used for to, cc, and bcc fields.
|
||||||
*/
|
*/
|
||||||
public class ContactList : Gtk.FlowBox, Geary.BaseInterface {
|
public class ContactList : Adw.Bin, Geary.BaseInterface {
|
||||||
/**
|
/**
|
||||||
* The number of results that will be displayed when not expanded.
|
* The number of results that will be displayed when not expanded.
|
||||||
* Note this is actually one less than the cutoff, which is 12; we
|
* Note this is actually one less than the cutoff, which is 12; we
|
||||||
|
|
@ -216,43 +197,50 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
*/
|
*/
|
||||||
private const int SHORT_RESULTS = 11;
|
private const int SHORT_RESULTS = 11;
|
||||||
|
|
||||||
|
private Gtk.FlowBox flowbox = new Gtk.FlowBox();
|
||||||
|
|
||||||
private Gtk.Label show_more;
|
private Gtk.Label show_more;
|
||||||
private Gtk.Label show_less;
|
private Gtk.Label show_less;
|
||||||
private bool expanded = false;
|
private bool expanded = false;
|
||||||
private int children = 0;
|
private int children = 0;
|
||||||
|
|
||||||
|
public signal void child_activated(Gtk.FlowBoxChild child);
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
this.show_more = this.create_label();
|
this.child = this.flowbox;
|
||||||
this.show_more.activate_link.connect(() => {
|
this.flowbox.column_spacing = 2;
|
||||||
this.set_expanded(true);
|
this.flowbox.max_children_per_line = 4;
|
||||||
});
|
this.flowbox.selection_mode = Gtk.SelectionMode.NONE;
|
||||||
base.add(this.show_more);
|
this.flowbox.child_activated.connect((f, c) => { child_activated(c); });
|
||||||
|
|
||||||
this.show_less = this.create_label();
|
this.show_more = create_label();
|
||||||
|
this.show_more.activate_link.connect(() => {
|
||||||
|
set_expanded(true);
|
||||||
|
});
|
||||||
|
this.flowbox.append(this.show_more);
|
||||||
|
|
||||||
|
this.show_less = create_label();
|
||||||
// Translators: Label text displayed when there are too
|
// Translators: Label text displayed when there are too
|
||||||
// many email addresses to be shown by default in an
|
// many email addresses to be shown by default in an
|
||||||
// email's header, but they are all being shown anyway.
|
// email's header, but they are all being shown anyway.
|
||||||
this.show_less.label = "<a href=''>%s</a>".printf(_("Show less"));
|
this.show_less.label = "<a href=''>%s</a>".printf(_("Show less"));
|
||||||
this.show_less.activate_link.connect(() => {
|
this.show_less.activate_link.connect(() => {
|
||||||
this.set_expanded(false);
|
set_expanded(false);
|
||||||
});
|
});
|
||||||
base.add(this.show_less);
|
this.flowbox.append(this.show_less);
|
||||||
|
|
||||||
this.set_filter_func(this.filter_func);
|
this.flowbox.set_filter_func(this.filter_func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override void add(Gtk.Widget child) {
|
public void add(Gtk.Widget child) {
|
||||||
// insert before the show_more and show_less labels
|
// insert before the show_more and show_less labels
|
||||||
int length = (int) this.get_children().length();
|
this.flowbox.insert(child, n_children() - 2);
|
||||||
base.insert(child, length - 2);
|
|
||||||
|
|
||||||
this.children ++;
|
this.children ++;
|
||||||
|
|
||||||
if (this.children >= SHORT_RESULTS && this.children <= SHORT_RESULTS + 2) {
|
if (this.children >= SHORT_RESULTS && this.children <= SHORT_RESULTS + 2) {
|
||||||
this.invalidate_filter();
|
this.flowbox.invalidate_filter();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.show_more.label = "<a href=''>%s</a>".printf(
|
this.show_more.label = "<a href=''>%s</a>".printf(
|
||||||
|
|
@ -264,19 +252,27 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int n_children() {
|
||||||
|
int ret = 0;
|
||||||
|
unowned var child = this.flowbox.get_first_child();
|
||||||
|
while (child != null) {
|
||||||
|
ret++;
|
||||||
|
child = child.get_next_sibling();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
private Gtk.Label create_label() {
|
private Gtk.Label create_label() {
|
||||||
var label = new Gtk.Label("");
|
var label = new Gtk.Label("");
|
||||||
label.visible = true;
|
label.visible = true;
|
||||||
label.use_markup = true;
|
label.use_markup = true;
|
||||||
label.track_visited_links = false;
|
|
||||||
label.halign = START;
|
label.halign = START;
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set_expanded(bool expanded) {
|
private void set_expanded(bool expanded) {
|
||||||
this.expanded = expanded;
|
this.expanded = expanded;
|
||||||
this.invalidate_filter();
|
this.flowbox.invalidate_filter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool filter_func(Gtk.FlowBoxChild child) {
|
private bool filter_func(Gtk.FlowBoxChild child) {
|
||||||
|
|
@ -306,10 +302,10 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Container for preview and full header widgets. */
|
/** Container for preview and full header widgets. */
|
||||||
[GtkChild] internal unowned Gtk.Grid summary { get; }
|
[GtkChild] internal unowned Gtk.Box summary { get; }
|
||||||
|
|
||||||
/** Container for message body components. */
|
/** Container for message body components. */
|
||||||
[GtkChild] internal unowned Gtk.Grid body_container { get; }
|
[GtkChild] internal unowned Gtk.Box body_container { get; }
|
||||||
|
|
||||||
/** Conainer for message InfoBar widgets. */
|
/** Conainer for message InfoBar widgets. */
|
||||||
[GtkChild] internal unowned Components.InfoBarStack info_bars { get; }
|
[GtkChild] internal unowned Components.InfoBarStack info_bars { get; }
|
||||||
|
|
@ -338,28 +334,28 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
|
|
||||||
private GLib.DateTime? local_date = null;
|
private GLib.DateTime? local_date = null;
|
||||||
|
|
||||||
[GtkChild] private unowned Hdy.Avatar avatar;
|
[GtkChild] private unowned Adw.Avatar avatar;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Revealer compact_revealer;
|
[GtkChild] private unowned Gtk.Box compact_header;
|
||||||
[GtkChild] private unowned Gtk.Label compact_from;
|
[GtkChild] private unowned Gtk.Label compact_from;
|
||||||
[GtkChild] private unowned Gtk.Label compact_date;
|
[GtkChild] private unowned Gtk.Label compact_date;
|
||||||
[GtkChild] private unowned Gtk.Label compact_body;
|
[GtkChild] private unowned Gtk.Label compact_body;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Revealer header_revealer;
|
[GtkChild] private unowned Gtk.Box expanded_header;
|
||||||
[GtkChild] private unowned Gtk.FlowBox from;
|
[GtkChild] private unowned Gtk.FlowBox from;
|
||||||
[GtkChild] private unowned Gtk.Label subject;
|
[GtkChild] private unowned Gtk.Label subject;
|
||||||
private string subject_searchable = "";
|
private string subject_searchable = "";
|
||||||
[GtkChild] private unowned Gtk.Label date;
|
[GtkChild] private unowned Gtk.Label date;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid sender_header;
|
[GtkChild] private unowned Gtk.Box sender_header;
|
||||||
[GtkChild] private unowned Gtk.FlowBox sender_address;
|
[GtkChild] private unowned Gtk.FlowBox sender_address;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid reply_to_header;
|
[GtkChild] private unowned Gtk.Box reply_to_header;
|
||||||
[GtkChild] private unowned Gtk.FlowBox reply_to_addresses;
|
[GtkChild] private unowned Gtk.FlowBox reply_to_addresses;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Grid to_header;
|
[GtkChild] private unowned ContactList to_list;
|
||||||
[GtkChild] private unowned Gtk.Grid cc_header;
|
[GtkChild] private unowned ContactList cc_list;
|
||||||
[GtkChild] private unowned Gtk.Grid bcc_header;
|
[GtkChild] private unowned ContactList bcc_list;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.Revealer body_revealer;
|
[GtkChild] private unowned Gtk.Revealer body_revealer;
|
||||||
[GtkChild] private unowned Gtk.ProgressBar body_progress;
|
[GtkChild] private unowned Gtk.ProgressBar body_progress;
|
||||||
|
|
@ -371,7 +367,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
private string empty_from_label;
|
private string empty_from_label;
|
||||||
|
|
||||||
// The web_view's context menu
|
// The web_view's context menu
|
||||||
private Gtk.Menu? context_menu = null;
|
private Gtk.PopoverMenu? context_menu = null;
|
||||||
|
|
||||||
// Menu models for creating the context menu
|
// Menu models for creating the context menu
|
||||||
private MenuModel context_menu_link;
|
private MenuModel context_menu_link;
|
||||||
|
|
@ -549,7 +545,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
// when the message has no from address.
|
// when the message has no from address.
|
||||||
this.empty_from_label = _("No sender");
|
this.empty_from_label = _("No sender");
|
||||||
|
|
||||||
this.compact_from.get_style_context().add_class(FROM_CLASS);
|
this.compact_from.add_css_class(FROM_CLASS);
|
||||||
|
|
||||||
if (preview != null) {
|
if (preview != null) {
|
||||||
string clean_preview = preview;
|
string clean_preview = preview;
|
||||||
|
|
@ -595,10 +591,11 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
if (viewer != null && viewer.previous_web_view != null) {
|
if (viewer != null && viewer.previous_web_view != null) {
|
||||||
this.web_view = new ConversationWebView.with_related_view(
|
this.web_view = new ConversationWebView.with_related_view(
|
||||||
this.config,
|
this.config,
|
||||||
|
null, /// XXX GTK4 is null okay here? I honestly think not ...
|
||||||
viewer.previous_web_view
|
viewer.previous_web_view
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.web_view = new ConversationWebView(this.config);
|
this.web_view = new ConversationWebView(this.config, null); /// XXX GTK4 is null okay here? I honestly think not ...
|
||||||
}
|
}
|
||||||
if (viewer != null) {
|
if (viewer != null) {
|
||||||
viewer.previous_web_view = this.web_view;
|
viewer.previous_web_view = this.web_view;
|
||||||
|
|
@ -618,7 +615,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
this.web_view.set_hexpand(true);
|
this.web_view.set_hexpand(true);
|
||||||
this.web_view.set_vexpand(true);
|
this.web_view.set_vexpand(true);
|
||||||
this.web_view.show();
|
this.web_view.show();
|
||||||
this.body_container.add(this.web_view);
|
this.body_container.append(this.web_view);
|
||||||
add_action(ACTION_COPY_SELECTION, false).activate.connect(() => {
|
add_action(ACTION_COPY_SELECTION, false).activate.connect(() => {
|
||||||
web_view.copy_clipboard();
|
web_view.copy_clipboard();
|
||||||
});
|
});
|
||||||
|
|
@ -634,13 +631,13 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
base_unref();
|
base_unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void destroy() {
|
public override void dispose() {
|
||||||
this.show_progress_timeout.reset();
|
this.show_progress_timeout.reset();
|
||||||
this.hide_progress_timeout.reset();
|
this.hide_progress_timeout.reset();
|
||||||
this.progress_pulse.reset();
|
this.progress_pulse.reset();
|
||||||
this.resources.clear();
|
this.resources.clear();
|
||||||
this.searchable_addresses.clear();
|
this.searchable_addresses.clear();
|
||||||
base.destroy();
|
base.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async string? get_selection_for_quoting() throws Error {
|
public async string? get_selection_for_quoting() throws Error {
|
||||||
|
|
@ -696,10 +693,10 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
web_view.zoom_reset();
|
web_view.zoom_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void web_view_translate_coordinates(Gtk.Widget widget, int x, int anchor_y, out int x1, out int y1) {
|
public void web_view_translate_coordinates(Gtk.Widget widget, int x, int anchor_y, out double x1, out double y1) {
|
||||||
if (this.web_view == null)
|
if (this.web_view == null)
|
||||||
initialize_web_view();
|
initialize_web_view();
|
||||||
web_view.translate_coordinates(widget, x, anchor_y, out x1, out y1);
|
this.web_view.translate_coordinates(widget, x, anchor_y, out x1, out y1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int web_view_get_allocated_height() {
|
public int web_view_get_allocated_height() {
|
||||||
|
|
@ -714,8 +711,8 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
public void show_message_body(bool include_transitions=true) {
|
public void show_message_body(bool include_transitions=true) {
|
||||||
if (this.web_view == null)
|
if (this.web_view == null)
|
||||||
initialize_web_view();
|
initialize_web_view();
|
||||||
set_revealer(this.compact_revealer, false, include_transitions);
|
this.compact_header.visible = false;
|
||||||
set_revealer(this.header_revealer, true, include_transitions);
|
this.expanded_header.visible = true;
|
||||||
set_revealer(this.body_revealer, true, include_transitions);
|
set_revealer(this.body_revealer, true, include_transitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -723,8 +720,8 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
* Hides the complete message and shows the compact headers.
|
* Hides the complete message and shows the compact headers.
|
||||||
*/
|
*/
|
||||||
public void hide_message_body() {
|
public void hide_message_body() {
|
||||||
compact_revealer.set_reveal_child(true);
|
this.compact_header.visible = true;
|
||||||
header_revealer.set_reveal_child(false);
|
this.expanded_header.visible = false;
|
||||||
body_revealer.set_reveal_child(false);
|
body_revealer.set_reveal_child(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -830,7 +827,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
*/
|
*/
|
||||||
public async void load_contacts(GLib.Cancellable cancellable)
|
public async void load_contacts(GLib.Cancellable cancellable)
|
||||||
throws GLib.Error {
|
throws GLib.Error {
|
||||||
var main = this.get_toplevel() as Application.MainWindow;
|
var main = this.get_root() as Application.MainWindow;
|
||||||
if (main != null && !cancellable.is_cancelled()) {
|
if (main != null && !cancellable.is_cancelled()) {
|
||||||
// Load the primary contact and avatar
|
// Load the primary contact and avatar
|
||||||
if (this.primary_originator != null) {
|
if (this.primary_originator != null) {
|
||||||
|
|
@ -843,10 +840,14 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
this.avatar,
|
this.avatar,
|
||||||
"text",
|
"text",
|
||||||
BindingFlags.SYNC_CREATE);
|
BindingFlags.SYNC_CREATE);
|
||||||
this.primary_contact.bind_property("avatar",
|
|
||||||
this.avatar,
|
if (this.primary_contact.avatar != null) {
|
||||||
"loadable-icon",
|
var icon_stream = yield this.primary_contact.avatar.load_async(48, null);
|
||||||
BindingFlags.SYNC_CREATE);
|
var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(icon_stream, 48, 48, true);
|
||||||
|
this.avatar.custom_image = Gdk.Texture.for_pixbuf(pixbuf);
|
||||||
|
} else {
|
||||||
|
this.avatar.custom_image = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -864,13 +865,13 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
cancellable
|
cancellable
|
||||||
);
|
);
|
||||||
yield fill_header_addresses(
|
yield fill_header_addresses(
|
||||||
this.to_header, headers.to, cancellable
|
this.to_list, headers.to, cancellable
|
||||||
);
|
);
|
||||||
yield fill_header_addresses(
|
yield fill_header_addresses(
|
||||||
this.cc_header, headers.cc, cancellable
|
this.cc_list, headers.cc, cancellable
|
||||||
);
|
);
|
||||||
yield fill_header_addresses(
|
yield fill_header_addresses(
|
||||||
this.bcc_header, headers.bcc, cancellable
|
this.bcc_list, headers.bcc, cancellable
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -930,10 +931,10 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
string match = raw_match.casefold();
|
string match = raw_match.casefold();
|
||||||
|
|
||||||
if (this.subject_searchable.contains(match)) {
|
if (this.subject_searchable.contains(match)) {
|
||||||
this.subject.get_style_context().add_class(MATCH_CLASS);
|
this.subject.add_css_class(MATCH_CLASS);
|
||||||
++headers_found;
|
++headers_found;
|
||||||
} else {
|
} else {
|
||||||
this.subject.get_style_context().remove_class(MATCH_CLASS);
|
this.subject.remove_css_class(MATCH_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (ContactFlowBoxChild address in this.searchable_addresses) {
|
foreach (ContactFlowBoxChild address in this.searchable_addresses) {
|
||||||
|
|
@ -1052,17 +1053,16 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
ContactFlowBoxChild.Type.FROM
|
ContactFlowBoxChild.Type.FROM
|
||||||
);
|
);
|
||||||
this.searchable_addresses.add(child);
|
this.searchable_addresses.add(child);
|
||||||
this.from.add(child);
|
this.from.append(child);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Gtk.Label label = new Gtk.Label(null);
|
Gtk.Label label = new Gtk.Label(null);
|
||||||
label.set_text(this.empty_from_label);
|
label.set_text(this.empty_from_label);
|
||||||
|
|
||||||
Gtk.FlowBoxChild child = new Gtk.FlowBoxChild();
|
Gtk.FlowBoxChild child = new Gtk.FlowBoxChild();
|
||||||
child.add(label);
|
child.child = label;
|
||||||
child.set_halign(Gtk.Align.START);
|
child.set_halign(Gtk.Align.START);
|
||||||
child.show_all();
|
this.from.append(child);
|
||||||
this.from.add(child);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the Sender header addresses if present, but only if
|
// Show the Sender header addresses if present, but only if
|
||||||
|
|
@ -1075,7 +1075,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
);
|
);
|
||||||
this.searchable_addresses.add(child);
|
this.searchable_addresses.add(child);
|
||||||
this.sender_header.show();
|
this.sender_header.show();
|
||||||
this.sender_address.add(child);
|
this.sender_address.append(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show any Reply-To header addresses if present, but only if
|
// Show any Reply-To header addresses if present, but only if
|
||||||
|
|
@ -1088,31 +1088,36 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
address
|
address
|
||||||
);
|
);
|
||||||
this.searchable_addresses.add(child);
|
this.searchable_addresses.add(child);
|
||||||
this.reply_to_addresses.add(child);
|
this.reply_to_addresses.append(child);
|
||||||
this.reply_to_header.show();
|
this.reply_to_header.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void fill_header_addresses(Gtk.Grid header,
|
private async void fill_header_addresses(ContactList contact_list,
|
||||||
Geary.RFC822.MailboxAddresses? addresses,
|
Geary.RFC822.MailboxAddresses? addresses,
|
||||||
GLib.Cancellable? cancellable)
|
GLib.Cancellable? cancellable)
|
||||||
throws GLib.Error {
|
throws GLib.Error {
|
||||||
if (addresses != null && addresses.size > 0) {
|
|
||||||
ContactList box = header.get_children().nth(0).data as ContactList;
|
// We set the visibility on the parent, as there's usually a label that
|
||||||
if (box != null) {
|
// needs to become (in)visible too.
|
||||||
foreach (Geary.RFC822.MailboxAddress address in addresses) {
|
unowned Gtk.Box header = contact_list.get_parent() as Gtk.Box;
|
||||||
ContactFlowBoxChild child = new ContactFlowBoxChild(
|
|
||||||
yield this.contacts.load(address, cancellable),
|
if (addresses == null || addresses.size <= 0) {
|
||||||
address
|
header.visible = false;
|
||||||
);
|
return;
|
||||||
this.searchable_addresses.add(child);
|
|
||||||
box.add(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
header.set_visible(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (Geary.RFC822.MailboxAddress address in addresses) {
|
||||||
|
ContactFlowBoxChild child = new ContactFlowBoxChild(
|
||||||
|
yield this.contacts.load(address, cancellable),
|
||||||
|
address
|
||||||
|
);
|
||||||
|
this.searchable_addresses.add(child);
|
||||||
|
contact_list.add(child);
|
||||||
|
}
|
||||||
|
header.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This delegate is called from within
|
// This delegate is called from within
|
||||||
|
|
@ -1191,7 +1196,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
this.body_placeholder = placeholder;
|
this.body_placeholder = placeholder;
|
||||||
if (this.web_view != null)
|
if (this.web_view != null)
|
||||||
this.web_view.hide();
|
this.web_view.hide();
|
||||||
this.body_container.add(placeholder);
|
this.body_container.append(placeholder);
|
||||||
show_message_body(true);
|
show_message_body(true);
|
||||||
} else {
|
} else {
|
||||||
if (this.web_view != null)
|
if (this.web_view != null)
|
||||||
|
|
@ -1250,7 +1255,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_address_box_child_activated(Gtk.FlowBox box,
|
private void on_address_box_child_activated(Gtk.Widget _unused,
|
||||||
Gtk.FlowBoxChild child) {
|
Gtk.FlowBoxChild child) {
|
||||||
ContactFlowBoxChild address_child = child as ContactFlowBoxChild;
|
ContactFlowBoxChild address_child = child as ContactFlowBoxChild;
|
||||||
if (address_child != null) {
|
if (address_child != null) {
|
||||||
|
|
@ -1284,10 +1289,9 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
|
|
||||||
private bool on_context_menu(WebKit.WebView view,
|
private bool on_context_menu(WebKit.WebView view,
|
||||||
WebKit.ContextMenu context_menu,
|
WebKit.ContextMenu context_menu,
|
||||||
Gdk.Event event,
|
|
||||||
WebKit.HitTestResult hit_test) {
|
WebKit.HitTestResult hit_test) {
|
||||||
if (this.context_menu != null) {
|
if (this.context_menu != null) {
|
||||||
this.context_menu.detach();
|
this.context_menu.popdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a new context menu every time the user clicks because
|
// Build a new context menu every time the user clicks because
|
||||||
|
|
@ -1332,9 +1336,15 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
model.append_section(null, context_menu_inspector);
|
model.append_section(null, context_menu_inspector);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.context_menu = new Gtk.Menu.from_model(model);
|
this.context_menu = new Gtk.PopoverMenu.from_model(model);
|
||||||
this.context_menu.attach_to_widget(this, null);
|
this.context_menu.set_parent(this);
|
||||||
this.context_menu.popup_at_pointer(event);
|
var event = context_menu.get_event();
|
||||||
|
double x = 0, y = 0;
|
||||||
|
if (event != null && event.get_position(out x, out y)) {
|
||||||
|
Gdk.Rectangle rect = { (int) x, (int) y, 1, 1 };
|
||||||
|
this.context_menu.set_pointing_to(rect);
|
||||||
|
}
|
||||||
|
this.context_menu.popup();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1378,7 +1388,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
// Escape text and especially URLs since we got them from the
|
// Escape text and especially URLs since we got them from the
|
||||||
// HREF, and Gtk.Label.set_markup is a strict parser.
|
// HREF, and Gtk.Label.set_markup is a strict parser.
|
||||||
|
|
||||||
var main = get_toplevel() as Application.MainWindow;
|
var main = get_root() as Application.MainWindow;
|
||||||
|
|
||||||
good_link.set_markup(
|
good_link.set_markup(
|
||||||
Markup.printf_escaped("<a href=\"%s\">%s</a>", text_href, text_label)
|
Markup.printf_escaped("<a href=\"%s\">%s</a>", text_href, text_label)
|
||||||
|
|
@ -1400,7 +1410,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
link_popover.set_relative_to(this.web_view);
|
link_popover.set_parent(this.web_view);
|
||||||
link_popover.set_pointing_to(location);
|
link_popover.set_pointing_to(location);
|
||||||
link_popover.closed.connect_after(() => { link_popover.destroy(); });
|
link_popover.closed.connect_after(() => { link_popover.destroy(); });
|
||||||
link_popover.popup();
|
link_popover.popup();
|
||||||
|
|
@ -1424,18 +1434,13 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
_("Showing remote images allows the sender to track you")
|
_("Showing remote images allows the sender to track you")
|
||||||
);
|
);
|
||||||
|
|
||||||
var menu_image = new Gtk.Image();
|
|
||||||
menu_image.icon_name = "view-more-symbolic";
|
|
||||||
|
|
||||||
var menu_button = new Gtk.MenuButton();
|
var menu_button = new Gtk.MenuButton();
|
||||||
menu_button.use_popover = true;
|
menu_button.icon_name = "view-more-symbolic";
|
||||||
menu_button.image = menu_image;
|
|
||||||
menu_button.menu_model = this.show_images_menu;
|
menu_button.menu_model = this.show_images_menu;
|
||||||
menu_button.halign = Gtk.Align.END;
|
menu_button.halign = Gtk.Align.END;
|
||||||
menu_button.hexpand =true;
|
menu_button.hexpand = true;
|
||||||
menu_button.show_all();
|
|
||||||
|
|
||||||
this.remote_images_info_bar.get_action_area().add(menu_button);
|
this.remote_images_info_bar.get_action_area().append(menu_button);
|
||||||
} else {
|
} else {
|
||||||
this.remote_images_info_bar = new Components.InfoBar(
|
this.remote_images_info_bar = new Components.InfoBar(
|
||||||
// Translators: Info bar status message
|
// Translators: Info bar status message
|
||||||
|
|
@ -1456,9 +1461,15 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_copy_link(Variant? param) {
|
private void on_copy_link(Variant? param) {
|
||||||
Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
|
Gdk.Clipboard clipboard = get_clipboard();
|
||||||
clipboard.set_text(param.get_string(), -1);
|
clipboard.set_text(param.get_string());
|
||||||
clipboard.store();
|
clipboard.store_async.begin(Priority.DEFAULT, null, (obj, res) => {
|
||||||
|
try {
|
||||||
|
clipboard.store_async.end(res);
|
||||||
|
} catch (Error err) {
|
||||||
|
debug("Couldn't copy link to clipboard: %s", err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_copy_email_address(Variant? param) {
|
private void on_copy_email_address(Variant? param) {
|
||||||
|
|
@ -1466,9 +1477,15 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
if (value.has_prefix(MAILTO_URI_PREFIX)) {
|
if (value.has_prefix(MAILTO_URI_PREFIX)) {
|
||||||
value = value.substring(MAILTO_URI_PREFIX.length, -1);
|
value = value.substring(MAILTO_URI_PREFIX.length, -1);
|
||||||
}
|
}
|
||||||
Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
|
Gdk.Clipboard clipboard = get_clipboard();
|
||||||
clipboard.set_text(value, -1);
|
clipboard.set_text(value);
|
||||||
clipboard.store();
|
clipboard.store_async.begin(Priority.DEFAULT, null, (obj, res) => {
|
||||||
|
try {
|
||||||
|
clipboard.store_async.end(res);
|
||||||
|
} catch (Error err) {
|
||||||
|
debug("Couldn't copy email address to clipboard: %s", err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_save_image(Variant? param) {
|
private void on_save_image(Variant? param) {
|
||||||
|
|
@ -1520,7 +1537,8 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
show_images(false);
|
show_images(false);
|
||||||
if (this.primary_contact != null) {
|
if (this.primary_contact != null) {
|
||||||
var email_addresses = this.primary_contact.email_addresses;
|
var email_addresses = this.primary_contact.email_addresses;
|
||||||
foreach (Geary.RFC822.MailboxAddress email in email_addresses) {
|
for (uint i = 0; i < email_addresses.get_n_items(); i++) {
|
||||||
|
var email = (Geary.RFC822.MailboxAddress) email_addresses.get_item(i);
|
||||||
this.config.add_images_trusted_domain(email.domain);
|
this.config.add_images_trusted_domain(email.domain);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1547,7 +1565,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var main = this.get_toplevel() as Application.MainWindow;
|
var main = this.get_root() as Application.MainWindow;
|
||||||
if (main != null) {
|
if (main != null) {
|
||||||
main.application.show_uri.begin(link);
|
main.application.show_uri.begin(link);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
* Displays the messages in a conversation and in-window composers.
|
* Displays the messages in a conversation and in-window composers.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/conversation-viewer.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/conversation-viewer.ui")]
|
||||||
public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
public class ConversationViewer : Adw.Bin, Geary.BaseInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current conversation listbox, if any.
|
* The current conversation listbox, if any.
|
||||||
|
|
@ -37,14 +37,19 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
private Gee.Set<Geary.App.Conversation>? selection_while_composing = null;
|
private Gee.Set<Geary.App.Conversation>? selection_while_composing = null;
|
||||||
private GLib.Cancellable? find_cancellable = null;
|
private GLib.Cancellable? find_cancellable = null;
|
||||||
|
|
||||||
|
[GtkChild] public unowned Components.ConversationHeaderBar headerbar;
|
||||||
|
|
||||||
|
[GtkChild] private unowned Gtk.Stack stack;
|
||||||
|
|
||||||
// Stack pages
|
// Stack pages
|
||||||
[GtkChild] private unowned Gtk.Spinner loading_page;
|
[GtkChild] private unowned Adw.Spinner loading_page;
|
||||||
[GtkChild] private unowned Gtk.Grid no_conversations_page;
|
[GtkChild] private unowned Adw.StatusPage no_conversations_page;
|
||||||
[GtkChild] private unowned Gtk.Grid conversation_page;
|
[GtkChild] private unowned Gtk.Box conversation_page;
|
||||||
[GtkChild] private unowned Gtk.Grid multiple_conversations_page;
|
[GtkChild] private unowned Adw.StatusPage multiple_conversations_page;
|
||||||
[GtkChild] private unowned Gtk.Grid empty_folder_page;
|
[GtkChild] private unowned Adw.StatusPage empty_folder_page;
|
||||||
[GtkChild] private unowned Gtk.Grid empty_search_page;
|
[GtkChild] private unowned Adw.StatusPage empty_search_page;
|
||||||
[GtkChild] private unowned Gtk.Grid composer_page;
|
[GtkChild] private unowned Adw.Bin composer_page;
|
||||||
|
|
||||||
[GtkChild] private unowned Gtk.ScrolledWindow conversation_scroller;
|
[GtkChild] private unowned Gtk.ScrolledWindow conversation_scroller;
|
||||||
|
|
||||||
[GtkChild] internal unowned Gtk.SearchBar conversation_find_bar;
|
[GtkChild] internal unowned Gtk.SearchBar conversation_find_bar;
|
||||||
|
|
@ -75,70 +80,6 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
base_ref();
|
base_ref();
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
Hdy.StatusPage no_conversations =
|
|
||||||
new Hdy.StatusPage();
|
|
||||||
no_conversations.icon_name = "folder-symbolic";
|
|
||||||
// Translators: Title label for placeholder when no
|
|
||||||
// conversations have been selected.
|
|
||||||
no_conversations.title = _("No Conversations Selected");
|
|
||||||
// Translators: Sub-title label for placeholder when no
|
|
||||||
// conversations have been selected.
|
|
||||||
no_conversations.description = _(
|
|
||||||
"Selecting a conversation from the list will display it here."
|
|
||||||
);
|
|
||||||
no_conversations.hexpand = true;
|
|
||||||
no_conversations.vexpand = true;
|
|
||||||
no_conversations.show ();
|
|
||||||
this.no_conversations_page.add(no_conversations);
|
|
||||||
|
|
||||||
Hdy.StatusPage multi_conversations =
|
|
||||||
new Hdy.StatusPage();
|
|
||||||
multi_conversations.icon_name = "folder-symbolic";
|
|
||||||
// Translators: Title label for placeholder when multiple
|
|
||||||
// conversations have been selected.
|
|
||||||
multi_conversations.title = _("Multiple Conversations Selected");
|
|
||||||
// Translators: Sub-title label for placeholder when multiple
|
|
||||||
// conversations have been selected.
|
|
||||||
multi_conversations.description = _(
|
|
||||||
"Choosing an action will apply to all selected conversations."
|
|
||||||
);
|
|
||||||
multi_conversations.hexpand = true;
|
|
||||||
multi_conversations.vexpand = true;
|
|
||||||
multi_conversations.show ();
|
|
||||||
this.multiple_conversations_page.add(multi_conversations);
|
|
||||||
|
|
||||||
Hdy.StatusPage empty_folder =
|
|
||||||
new Hdy.StatusPage();
|
|
||||||
empty_folder.icon_name = "folder-symbolic";
|
|
||||||
// Translators: Title label for placeholder when no
|
|
||||||
// conversations have exist in a folder.
|
|
||||||
empty_folder.title = _("No Conversations Found");
|
|
||||||
// Translators: Sub-title label for placeholder when no
|
|
||||||
// conversations have exist in a folder.
|
|
||||||
empty_folder.description = _(
|
|
||||||
"This folder does not contain any conversations."
|
|
||||||
);
|
|
||||||
empty_folder.hexpand = true;
|
|
||||||
empty_folder.vexpand = true;
|
|
||||||
empty_folder.show ();
|
|
||||||
this.empty_folder_page.add(empty_folder);
|
|
||||||
|
|
||||||
Hdy.StatusPage empty_search =
|
|
||||||
new Hdy.StatusPage();
|
|
||||||
empty_search.icon_name = "folder-symbolic";
|
|
||||||
// Translators: Title label for placeholder when no
|
|
||||||
// conversations have been found in a search.
|
|
||||||
empty_search.title = _("No Conversations Found");
|
|
||||||
// Translators: Sub-title label for placeholder when no
|
|
||||||
// conversations have been found in a search.
|
|
||||||
empty_search.description = _(
|
|
||||||
"Your search returned no results, try refining your search terms."
|
|
||||||
);
|
|
||||||
empty_search.hexpand = true;
|
|
||||||
empty_search.vexpand = true;
|
|
||||||
empty_search.show ();
|
|
||||||
this.empty_search_page.add(empty_search);
|
|
||||||
|
|
||||||
this.conversation_find_undo = new Components.EntryUndo(
|
this.conversation_find_undo = new Components.EntryUndo(
|
||||||
this.conversation_find_entry
|
this.conversation_find_entry
|
||||||
);
|
);
|
||||||
|
|
@ -155,10 +96,10 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
* Puts the view into composer mode, showing a full-height composer.
|
* Puts the view into composer mode, showing a full-height composer.
|
||||||
*/
|
*/
|
||||||
public void do_compose(Composer.Widget composer) {
|
public void do_compose(Composer.Widget composer) {
|
||||||
var main_window = get_toplevel() as Application.MainWindow;
|
var main_window = get_root() as Application.MainWindow;
|
||||||
if (main_window != null) {
|
if (main_window != null) {
|
||||||
Composer.Box box = new Composer.Box(
|
Composer.Box box = new Composer.Box(
|
||||||
composer, main_window.conversation_headerbar
|
composer, this.headerbar
|
||||||
);
|
);
|
||||||
this.current_composer = composer;
|
this.current_composer = composer;
|
||||||
|
|
||||||
|
|
@ -169,7 +110,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
conversation_list.unselect_all();
|
conversation_list.unselect_all();
|
||||||
|
|
||||||
box.vanished.connect(on_composer_closed);
|
box.vanished.connect(on_composer_closed);
|
||||||
this.composer_page.add(box);
|
this.composer_page.child = box;
|
||||||
set_visible_child(this.composer_page);
|
set_visible_child(this.composer_page);
|
||||||
composer.update_window_title();
|
composer.update_window_title();
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +155,6 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
* Shows the loading UI.
|
* Shows the loading UI.
|
||||||
*/
|
*/
|
||||||
public void show_loading() {
|
public void show_loading() {
|
||||||
this.loading_page.start();
|
|
||||||
set_visible_child(this.loading_page);
|
set_visible_child(this.loading_page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,13 +223,16 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
this.conversation_find_prev.set_sensitive(false);
|
this.conversation_find_prev.set_sensitive(false);
|
||||||
new_list.search.matches_updated.connect((count) => {
|
new_list.search.matches_updated.connect((count) => {
|
||||||
bool found = count > 0;
|
bool found = count > 0;
|
||||||
|
//XXX GTK4 - Gtk.SearchEntry doesn't have a icon API anymore
|
||||||
|
#if 0
|
||||||
this.conversation_find_entry.set_icon_from_icon_name(
|
this.conversation_find_entry.set_icon_from_icon_name(
|
||||||
Gtk.EntryIconPosition.PRIMARY,
|
Gtk.EntryIconPosition.PRIMARY,
|
||||||
found || Geary.String.is_empty(this.conversation_find_entry.text)
|
found || Geary.String.is_empty(this.conversation_find_entry.text)
|
||||||
? "edit-find-symbolic" : "computer-fail-symbolic"
|
? "edit-find-symbolic" : "computer-fail-symbolic"
|
||||||
);
|
);
|
||||||
this.conversation_find_next.set_sensitive(found);
|
#endif
|
||||||
this.conversation_find_prev.set_sensitive(found);
|
this.conversation_find_next.sensitive = found;
|
||||||
|
this.conversation_find_prev.sensitive = found;
|
||||||
});
|
});
|
||||||
add_new_list(new_list);
|
add_new_list(new_list);
|
||||||
set_visible_child(this.conversation_page);
|
set_visible_child(this.conversation_page);
|
||||||
|
|
@ -318,10 +261,9 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
// are not set on the list - it makes changing focus jumpy
|
// are not set on the list - it makes changing focus jumpy
|
||||||
// when a row or its web_view are larger than the viewport.
|
// when a row or its web_view are larger than the viewport.
|
||||||
Gtk.Viewport viewport = new Gtk.Viewport(null, null);
|
Gtk.Viewport viewport = new Gtk.Viewport(null, null);
|
||||||
viewport.show();
|
viewport.child = list;
|
||||||
viewport.add(list);
|
|
||||||
|
|
||||||
this.conversation_scroller.add(viewport);
|
this.conversation_scroller.child = viewport;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any existing conversation list, cancelling its loading
|
// Remove any existing conversation list, cancelling its loading
|
||||||
|
|
@ -329,7 +271,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
// Remove the viewport that contains the current list
|
// Remove the viewport that contains the current list
|
||||||
Gtk.Widget? scrolled_child = this.conversation_scroller.get_child();
|
Gtk.Widget? scrolled_child = this.conversation_scroller.get_child();
|
||||||
if (scrolled_child != null) {
|
if (scrolled_child != null) {
|
||||||
conversation_scroller.remove(scrolled_child);
|
this.conversation_scroller.child = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the scrollbars to their initial positions
|
// Reset the scrollbars to their initial positions
|
||||||
|
|
@ -344,10 +286,14 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
return scrolled_child;
|
return scrolled_child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Gtk.Widget get_visible_child() {
|
||||||
|
return this.stack.visible_child;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the currently visible page of the stack.
|
* Sets the currently visible page of the stack.
|
||||||
*/
|
*/
|
||||||
private new void set_visible_child(Gtk.Widget widget) {
|
private void set_visible_child(Gtk.Widget widget) {
|
||||||
debug("Showing: %s", widget.get_name());
|
debug("Showing: %s", widget.get_name());
|
||||||
Gtk.Widget current = get_visible_child();
|
Gtk.Widget current = get_visible_child();
|
||||||
if (current == this.conversation_page) {
|
if (current == this.conversation_page) {
|
||||||
|
|
@ -358,12 +304,8 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
// etc.
|
// etc.
|
||||||
remove_current_list();
|
remove_current_list();
|
||||||
}
|
}
|
||||||
} else if (current == this.loading_page) {
|
|
||||||
// Stop the spinner running so it doesn't trigger repaints
|
|
||||||
// and wake up Geary even when idle. See Bug 783025.
|
|
||||||
this.loading_page.stop();
|
|
||||||
}
|
}
|
||||||
base.set_visible_child(widget);
|
this.stack.set_visible_child(widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void update_find_results() {
|
private async void update_find_results() {
|
||||||
|
|
@ -471,7 +413,9 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private bool on_conversation_scroll() {
|
private bool on_conversation_scroll(Gtk.EventControllerScroll controller,
|
||||||
|
double dx,
|
||||||
|
double dy) {
|
||||||
if (this.current_list != null) {
|
if (this.current_list != null) {
|
||||||
this.current_list.mark_visible_read();
|
this.current_list.mark_visible_read();
|
||||||
}
|
}
|
||||||
|
|
@ -484,7 +428,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
|
||||||
set_visible_child(this.conversation_page);
|
set_visible_child(this.conversation_page);
|
||||||
|
|
||||||
// Restore the old selection
|
// Restore the old selection
|
||||||
var main_window = get_toplevel() as Application.MainWindow;
|
var main_window = get_root() as Application.MainWindow;
|
||||||
if (main_window != null) {
|
if (main_window != null) {
|
||||||
main_window.update_title();
|
main_window.update_title();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ public class ConversationWebView : Components.WebView {
|
||||||
*
|
*
|
||||||
* A new WebKitGTK WebProcess will be constructed for this view.
|
* A new WebKitGTK WebProcess will be constructed for this view.
|
||||||
*/
|
*/
|
||||||
public ConversationWebView(Application.Configuration config) {
|
public ConversationWebView(Application.Configuration config, GLib.File? cache_dir) {
|
||||||
base(config);
|
base(config, cache_dir);
|
||||||
init();
|
init();
|
||||||
|
|
||||||
// These only need to be added when creating a new WebProcess,
|
// These only need to be added when creating a new WebProcess,
|
||||||
|
|
@ -79,6 +79,7 @@ public class ConversationWebView : Components.WebView {
|
||||||
*/
|
*/
|
||||||
internal ConversationWebView.with_related_view(
|
internal ConversationWebView.with_related_view(
|
||||||
Application.Configuration config,
|
Application.Configuration config,
|
||||||
|
GLib.File? cache_dir,
|
||||||
ConversationWebView related
|
ConversationWebView related
|
||||||
) {
|
) {
|
||||||
base.with_related_view(config, related);
|
base.with_related_view(config, related);
|
||||||
|
|
@ -185,38 +186,46 @@ public class ConversationWebView : Components.WebView {
|
||||||
get_find_controller().search_finish();
|
get_find_controller().search_finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool key_press_event(Gdk.EventKey event) {
|
private bool on_key_pressed(Gtk.EventControllerKey controller,
|
||||||
|
uint keyval,
|
||||||
|
uint keycode,
|
||||||
|
Gdk.ModifierType state) {
|
||||||
|
//XXX GTK4 not sure what to do here
|
||||||
// WebView consumes a number of key presses for scrolling
|
// WebView consumes a number of key presses for scrolling
|
||||||
// itself internally, but we want them to navigate around in
|
// itself internally, but we want them to navigate around in
|
||||||
// ConversationListBox, so don't forward any on.
|
// ConversationListBox, so don't forward any on.
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
bool ret = Gdk.EVENT_PROPAGATE;
|
||||||
if (!(((int) event.keyval) in BLACKLISTED_KEY_CODES)) {
|
// if (!(((int) keyval) in BLACKLISTED_KEY_CODES)) {
|
||||||
ret = base.key_press_event(event);
|
// ret = base.key_press_event(event);
|
||||||
}
|
// }
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void get_preferred_height(out int minimum_height,
|
public override void measure(Gtk.Orientation orientation,
|
||||||
out int natural_height) {
|
int for_size,
|
||||||
// XXX clamp height to something not too outrageous so we
|
out int minimum,
|
||||||
// don't get an XServer error trying to allocate a massive
|
out int natural,
|
||||||
// window.
|
out int minimum_baseline,
|
||||||
const uint max_pixels = 8 * 1024 * 1024;
|
out int natural_baseline) {
|
||||||
int width = get_allocated_width();
|
if (orientation == Gtk.Orientation.HORIZONTAL) {
|
||||||
int height = this.preferred_height;
|
// We always want the view to be sized according to the available
|
||||||
if (height * width > max_pixels) {
|
// space in the parent, not by the width of the web view.
|
||||||
height = (int) Math.floor(max_pixels / (double) width);
|
minimum = natural = 0;
|
||||||
|
} else {
|
||||||
|
// XXX clamp height to something not too outrageous so we
|
||||||
|
// don't get an XServer error trying to allocate a massive
|
||||||
|
// window.
|
||||||
|
const uint max_pixels = 8 * 1024 * 1024;
|
||||||
|
int width = get_allocated_width();
|
||||||
|
int height = this.preferred_height;
|
||||||
|
if (height * width > max_pixels) {
|
||||||
|
height = (int) Math.floor(max_pixels / (double) width);
|
||||||
|
}
|
||||||
|
|
||||||
|
minimum = natural = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
minimum_height = natural_height = height;
|
minimum_baseline = natural_baseline = -1;
|
||||||
}
|
|
||||||
|
|
||||||
// Overridden since we always what the view to be sized according
|
|
||||||
// to the available space in the parent, not by the width of the
|
|
||||||
// web view.
|
|
||||||
public override void get_preferred_width(out int minimum_height,
|
|
||||||
out int natural_height) {
|
|
||||||
minimum_height = natural_height = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
|
|
@ -225,6 +234,10 @@ public class ConversationWebView : Components.WebView {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.notify["preferred-height"].connect(() => queue_resize());
|
this.notify["preferred-height"].connect(() => queue_resize());
|
||||||
|
|
||||||
|
Gtk.EventControllerKey controller = new Gtk.EventControllerKey();
|
||||||
|
controller.key_pressed.connect(on_key_pressed);
|
||||||
|
add_controller(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_deceptive_link_clicked(GLib.Variant? parameters) {
|
private void on_deceptive_link_clicked(GLib.Variant? parameters) {
|
||||||
|
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
|
||||||
*
|
|
||||||
* This software is licensed under the GNU Lesser General Public License
|
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class AlertDialog : Object {
|
|
||||||
private Gtk.MessageDialog dialog;
|
|
||||||
|
|
||||||
public AlertDialog(Gtk.Window? parent, Gtk.MessageType message_type, string title,
|
|
||||||
string? description, string? ok_button, string? cancel_button, string? tertiary_button,
|
|
||||||
Gtk.ResponseType tertiary_response_type, string? ok_action_type,
|
|
||||||
string? tertiary_action_type = "", Gtk.ResponseType? default_response = null) {
|
|
||||||
|
|
||||||
dialog = new Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT, message_type,
|
|
||||||
Gtk.ButtonsType.NONE, "");
|
|
||||||
|
|
||||||
dialog.text = title;
|
|
||||||
dialog.secondary_text = description;
|
|
||||||
|
|
||||||
if (!Geary.String.is_empty_or_whitespace(tertiary_button)) {
|
|
||||||
Gtk.Widget? button = dialog.add_button(tertiary_button, tertiary_response_type);
|
|
||||||
if (!Geary.String.is_empty_or_whitespace(tertiary_action_type)) {
|
|
||||||
button.get_style_context().add_class(tertiary_action_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Geary.String.is_empty_or_whitespace(cancel_button))
|
|
||||||
dialog.add_button(cancel_button, Gtk.ResponseType.CANCEL);
|
|
||||||
|
|
||||||
if (!Geary.String.is_empty_or_whitespace(ok_button)) {
|
|
||||||
Gtk.Widget? button = dialog.add_button(ok_button, Gtk.ResponseType.OK);
|
|
||||||
if (!Geary.String.is_empty_or_whitespace(ok_action_type)) {
|
|
||||||
button.get_style_context().add_class(ok_action_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (default_response != null) {
|
|
||||||
dialog.set_default_response(default_response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void use_secondary_markup(bool markup) {
|
|
||||||
dialog.secondary_use_markup = markup;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Gtk.Box get_message_area() {
|
|
||||||
return (Gtk.Box) dialog.get_message_area();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_focus_response(Gtk.ResponseType response) {
|
|
||||||
Gtk.Widget? to_focus = dialog.get_widget_for_response(response);
|
|
||||||
if (to_focus != null)
|
|
||||||
to_focus.grab_focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runs dialog, destroys it, and returns selected response
|
|
||||||
public Gtk.ResponseType run() {
|
|
||||||
Gtk.ResponseType response = (Gtk.ResponseType) dialog.run();
|
|
||||||
|
|
||||||
dialog.destroy();
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConfirmationDialog : AlertDialog {
|
|
||||||
public ConfirmationDialog(Gtk.Window? parent, string title, string? description,
|
|
||||||
string? ok_button, string? ok_action_type = "") {
|
|
||||||
base (parent, Gtk.MessageType.WARNING, title, description, ok_button, Stock._CANCEL,
|
|
||||||
null, Gtk.ResponseType.NONE, ok_action_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TernaryConfirmationDialog : AlertDialog {
|
|
||||||
public TernaryConfirmationDialog(Gtk.Window? parent, string title, string? description,
|
|
||||||
string? ok_button, string? tertiary_button, Gtk.ResponseType tertiary_response_type,
|
|
||||||
string? ok_action_type = "", string? tertiary_action_type = "",
|
|
||||||
Gtk.ResponseType? default_response = null) {
|
|
||||||
|
|
||||||
base (parent, Gtk.MessageType.WARNING, title, description, ok_button, Stock._CANCEL,
|
|
||||||
tertiary_button, tertiary_response_type, ok_action_type, tertiary_action_type,
|
|
||||||
default_response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ErrorDialog : AlertDialog {
|
|
||||||
public ErrorDialog(Gtk.Window? parent, string title, string? description) {
|
|
||||||
base (parent, Gtk.MessageType.ERROR, title, description, Stock._OK, null, null,
|
|
||||||
Gtk.ResponseType.NONE, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuestionDialog : AlertDialog {
|
|
||||||
public bool is_checked { get; private set; default = false; }
|
|
||||||
|
|
||||||
private Gtk.CheckButton? checkbutton = null;
|
|
||||||
|
|
||||||
public QuestionDialog(Gtk.Window? parent, string title, string? description,
|
|
||||||
string yes_button, string no_button) {
|
|
||||||
base (parent, Gtk.MessageType.QUESTION, title, description, yes_button, no_button, null,
|
|
||||||
Gtk.ResponseType.NONE, "suggested-action");
|
|
||||||
}
|
|
||||||
|
|
||||||
public QuestionDialog.with_checkbox(Gtk.Window? parent, string title, string? description,
|
|
||||||
string yes_button, string no_button, string checkbox_label, bool checkbox_default) {
|
|
||||||
this (parent, title, description, yes_button, no_button);
|
|
||||||
|
|
||||||
checkbutton = new Gtk.CheckButton.with_mnemonic(checkbox_label);
|
|
||||||
checkbutton.active = checkbox_default;
|
|
||||||
checkbutton.toggled.connect(on_checkbox_toggled);
|
|
||||||
|
|
||||||
get_message_area().pack_start(checkbutton);
|
|
||||||
|
|
||||||
// this must be done once all the packing is completed
|
|
||||||
get_message_area().show_all();
|
|
||||||
|
|
||||||
// the check box may have grabbed keyboard focus, so we put it back to the button
|
|
||||||
set_focus_response(Gtk.ResponseType.OK);
|
|
||||||
|
|
||||||
is_checked = checkbox_default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_checkbox_toggled() {
|
|
||||||
is_checked = checkbutton.active;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
|
||||||
*
|
|
||||||
* This software is licensed under the GNU Lesser General Public License
|
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A FileChooser-like object for choosing attachments for a message.
|
|
||||||
*/
|
|
||||||
public class AttachmentDialog : Object {
|
|
||||||
|
|
||||||
private const int PREVIEW_SIZE = 180;
|
|
||||||
private const int PREVIEW_PADDING = 3;
|
|
||||||
|
|
||||||
private Application.Configuration config;
|
|
||||||
|
|
||||||
private Gtk.FileChooserNative? chooser = null;
|
|
||||||
|
|
||||||
private Gtk.Image preview_image = new Gtk.Image();
|
|
||||||
|
|
||||||
public delegate bool Attacher(File attachment_file, bool alert_errors = true);
|
|
||||||
|
|
||||||
public AttachmentDialog(Gtk.Window? parent, Application.Configuration config) {
|
|
||||||
this.config = config;
|
|
||||||
this.chooser = new Gtk.FileChooserNative(_("Choose a file"), parent, Gtk.FileChooserAction.OPEN, _("_Attach"), Stock._CANCEL);
|
|
||||||
|
|
||||||
this.chooser.set_local_only(false);
|
|
||||||
this.chooser.set_select_multiple(true);
|
|
||||||
|
|
||||||
// preview widget is not supported on Win32 (this will fallback to gtk file chooser)
|
|
||||||
// and possibly by some org.freedesktop.portal.FileChooser (preview will be ignored).
|
|
||||||
this.chooser.set_preview_widget(this.preview_image);
|
|
||||||
this.chooser.use_preview_label = false;
|
|
||||||
|
|
||||||
this.chooser.update_preview.connect(on_update_preview);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add_filter(owned Gtk.FileFilter filter) {
|
|
||||||
this.chooser.add_filter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SList<File> get_files() {
|
|
||||||
return this.chooser.get_files();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int run() {
|
|
||||||
return this.chooser.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hide() {
|
|
||||||
this.chooser.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroy() {
|
|
||||||
this.chooser.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_update_preview() {
|
|
||||||
string? filename = chooser.get_preview_filename();
|
|
||||||
if (filename == null) {
|
|
||||||
chooser.set_preview_widget_active(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read the image format data first
|
|
||||||
int width = 0;
|
|
||||||
int height = 0;
|
|
||||||
Gdk.PixbufFormat? format = Gdk.Pixbuf.get_file_info(filename, out width, out height);
|
|
||||||
|
|
||||||
if (format == null) {
|
|
||||||
chooser.set_preview_widget_active(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the image is too big, resize it
|
|
||||||
Gdk.Pixbuf pixbuf;
|
|
||||||
try {
|
|
||||||
pixbuf = new Gdk.Pixbuf.from_file_at_scale(filename, PREVIEW_SIZE, PREVIEW_SIZE, true);
|
|
||||||
} catch (Error e) {
|
|
||||||
chooser.set_preview_widget_active(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pixbuf == null) {
|
|
||||||
chooser.set_preview_widget_active(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pixbuf = pixbuf.apply_embedded_orientation();
|
|
||||||
|
|
||||||
// distribute the extra space around the image
|
|
||||||
int extra_space = PREVIEW_SIZE - pixbuf.width;
|
|
||||||
int smaller_half = extra_space/2;
|
|
||||||
int larger_half = extra_space - smaller_half;
|
|
||||||
|
|
||||||
// pad the image manually (avoids rounding errors)
|
|
||||||
preview_image.set_margin_start(PREVIEW_PADDING + smaller_half);
|
|
||||||
preview_image.set_margin_end(PREVIEW_PADDING + larger_half);
|
|
||||||
|
|
||||||
// show the preview
|
|
||||||
preview_image.set_from_pixbuf(pixbuf);
|
|
||||||
chooser.set_preview_widget_active(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class CertificateWarningDialog {
|
[GtkTemplate (ui = "/org/gnome/Geary/certificate-warning-dialog.ui")]
|
||||||
|
public class CertificateWarningDialog : Adw.AlertDialog {
|
||||||
|
|
||||||
public enum Result {
|
public enum Result {
|
||||||
DONT_TRUST,
|
DONT_TRUST,
|
||||||
TRUST,
|
TRUST,
|
||||||
|
|
@ -13,59 +15,49 @@ public class CertificateWarningDialog {
|
||||||
|
|
||||||
private const string BULLET = "• ";
|
private const string BULLET = "• ";
|
||||||
|
|
||||||
private Gtk.Dialog dialog;
|
[GtkChild] private unowned Gtk.Label top_label;
|
||||||
|
[GtkChild] private unowned Gtk.Label warnings_label;
|
||||||
|
[GtkChild] private unowned Gtk.Label trust_label;
|
||||||
|
[GtkChild] private unowned Gtk.Label dont_trust_label;
|
||||||
|
[GtkChild] private unowned Gtk.Label contact_label;
|
||||||
|
|
||||||
public CertificateWarningDialog(Gtk.Window? parent,
|
public CertificateWarningDialog(Geary.AccountInformation account,
|
||||||
Geary.AccountInformation account,
|
|
||||||
Geary.ServiceInformation service,
|
Geary.ServiceInformation service,
|
||||||
Geary.Endpoint endpoint,
|
Geary.Endpoint endpoint,
|
||||||
bool is_validation) {
|
bool is_validation) {
|
||||||
Gtk.Builder builder = GioUtil.create_builder("certificate_warning_dialog.glade");
|
this.title = _("Untrusted Connection: %s").printf(account.display_name);
|
||||||
|
|
||||||
dialog = (Gtk.Dialog) builder.get_object("CertificateWarningDialog");
|
this.top_label.label = _("The identity of the %s mail server at %s:%u could not be verified.").printf(
|
||||||
dialog.transient_for = parent;
|
|
||||||
dialog.modal = true;
|
|
||||||
|
|
||||||
Gtk.Label title_label = (Gtk.Label) builder.get_object("untrusted_connection_label");
|
|
||||||
Gtk.Label top_label = (Gtk.Label) builder.get_object("top_label");
|
|
||||||
Gtk.Label warnings_label = (Gtk.Label) builder.get_object("warnings_label");
|
|
||||||
Gtk.Label trust_label = (Gtk.Label) builder.get_object("trust_label");
|
|
||||||
Gtk.Label dont_trust_label = (Gtk.Label) builder.get_object("dont_trust_label");
|
|
||||||
Gtk.Label contact_label = (Gtk.Label) builder.get_object("contact_label");
|
|
||||||
|
|
||||||
title_label.label = _("Untrusted Connection: %s").printf(account.display_name);
|
|
||||||
|
|
||||||
top_label.label = _("The identity of the %s mail server at %s:%u could not be verified.").printf(
|
|
||||||
service.protocol.to_value(), service.host, service.port);
|
service.protocol.to_value(), service.host, service.port);
|
||||||
|
|
||||||
warnings_label.label = generate_warning_list(
|
this.warnings_label.label = generate_warning_list(
|
||||||
endpoint.tls_validation_warnings
|
endpoint.tls_validation_warnings
|
||||||
);
|
);
|
||||||
warnings_label.use_markup = true;
|
this.warnings_label.use_markup = true;
|
||||||
|
|
||||||
trust_label.label =
|
this.trust_label.label =
|
||||||
"<b>"
|
"<b>"
|
||||||
+_("Selecting “Trust This Server” or “Always Trust This Server” may cause your username and password to be transmitted insecurely.")
|
+_("Selecting “Trust This Server” or “Always Trust This Server” may cause your username and password to be transmitted insecurely.")
|
||||||
+ "</b>";
|
+ "</b>";
|
||||||
trust_label.use_markup = true;
|
this.trust_label.use_markup = true;
|
||||||
|
|
||||||
if (is_validation) {
|
if (is_validation) {
|
||||||
// could be a new or existing account
|
// could be a new or existing account
|
||||||
dont_trust_label.label =
|
this.dont_trust_label.label =
|
||||||
"<b>"
|
"<b>"
|
||||||
+ _("Selecting “Don’t Trust This Server” will cause Geary not to access this server.")
|
+ _("Selecting “Don’t Trust This Server” will cause Geary not to access this server.")
|
||||||
+ "</b> "
|
+ "</b> "
|
||||||
+ _("Geary will not add or update this email account.");
|
+ _("Geary will not add or update this email account.");
|
||||||
} else {
|
} else {
|
||||||
// a registered account
|
// a registered account
|
||||||
dont_trust_label.label =
|
this.dont_trust_label.label =
|
||||||
"<b>"
|
"<b>"
|
||||||
+ _("Selecting “Don’t Trust This Server” will cause Geary to stop accessing this account.")
|
+ _("Selecting “Don’t Trust This Server” will cause Geary to stop accessing this account.")
|
||||||
+ "</b> ";
|
+ "</b> ";
|
||||||
}
|
}
|
||||||
dont_trust_label.use_markup = true;
|
this.dont_trust_label.use_markup = true;
|
||||||
|
|
||||||
contact_label.label =
|
this.contact_label.label =
|
||||||
_("Contact your system administrator or email service provider if you have any question about these issues.");
|
_("Contact your system administrator or email service provider if you have any question about these issues.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,17 +88,14 @@ public class CertificateWarningDialog {
|
||||||
return builder.str;
|
return builder.str;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result run() {
|
public async Result run(Gtk.Window? parent) {
|
||||||
dialog.show_all();
|
string response = yield choose(parent, null);
|
||||||
int response = dialog.run();
|
|
||||||
dialog.destroy();
|
|
||||||
|
|
||||||
// these values are defined in the Glade file
|
|
||||||
switch (response) {
|
switch (response) {
|
||||||
case 1:
|
case "trust":
|
||||||
return Result.TRUST;
|
return Result.TRUST;
|
||||||
|
|
||||||
case 2:
|
case "always-trust":
|
||||||
return Result.ALWAYS_TRUST;
|
return Result.ALWAYS_TRUST;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,9 @@
|
||||||
* Displays technical details when a problem has been reported.
|
* Displays technical details when a problem has been reported.
|
||||||
*/
|
*/
|
||||||
[GtkTemplate (ui = "/org/gnome/Geary/problem-details-dialog.ui")]
|
[GtkTemplate (ui = "/org/gnome/Geary/problem-details-dialog.ui")]
|
||||||
public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
public class Dialogs.ProblemDetailsDialog : Adw.Dialog {
|
||||||
|
|
||||||
|
|
||||||
private const string ACTION_CLOSE = "problem-details-close";
|
|
||||||
private const string ACTION_SEARCH_TOGGLE = "toggle-search";
|
private const string ACTION_SEARCH_TOGGLE = "toggle-search";
|
||||||
private const string ACTION_SEARCH_ACTIVATE = "activate-search";
|
private const string ACTION_SEARCH_ACTIVATE = "activate-search";
|
||||||
|
|
||||||
|
|
@ -21,14 +20,11 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
||||||
};
|
};
|
||||||
|
|
||||||
private const ActionEntry[] WINDOW_ACTIONS = {
|
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_TOGGLE, on_logs_search_toggled, null, "false" },
|
||||||
{ ACTION_SEARCH_ACTIVATE, on_logs_search_activated },
|
{ ACTION_SEARCH_ACTIVATE, on_logs_search_activated },
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void add_accelerators(Application.Client app) {
|
public static void add_accelerators(Application.Client app) {
|
||||||
app.add_window_accelerators(ACTION_CLOSE, { "Escape" } );
|
|
||||||
app.add_window_accelerators(ACTION_SEARCH_ACTIVATE, { "<Ctrl>F" } );
|
app.add_window_accelerators(ACTION_SEARCH_ACTIVATE, { "<Ctrl>F" } );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,14 +44,8 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
||||||
private Geary.ServiceInformation? service;
|
private Geary.ServiceInformation? service;
|
||||||
|
|
||||||
|
|
||||||
public ProblemDetailsDialog(Gtk.Window? parent,
|
public ProblemDetailsDialog(Application.Client application,
|
||||||
Application.Client application,
|
|
||||||
Geary.ProblemReport report) {
|
Geary.ProblemReport report) {
|
||||||
Object(
|
|
||||||
transient_for: parent,
|
|
||||||
use_header_bar: 1
|
|
||||||
);
|
|
||||||
|
|
||||||
Geary.AccountProblemReport? account_report =
|
Geary.AccountProblemReport? account_report =
|
||||||
report as Geary.AccountProblemReport;
|
report as Geary.AccountProblemReport;
|
||||||
Geary.ServiceProblemReport? service_report =
|
Geary.ServiceProblemReport? service_report =
|
||||||
|
|
@ -79,9 +69,7 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
||||||
error, account, service
|
error, account, service
|
||||||
);
|
);
|
||||||
|
|
||||||
this.log_pane = new Components.InspectorLogView(
|
this.log_pane = new Components.InspectorLogView(account);
|
||||||
application.config, account
|
|
||||||
);
|
|
||||||
this.log_pane.load(report.earliest_log, report.latest_log);
|
this.log_pane.load(report.earliest_log, report.latest_log);
|
||||||
this.log_pane.record_selection_changed.connect(
|
this.log_pane.record_selection_changed.connect(
|
||||||
on_logs_selection_changed
|
on_logs_selection_changed
|
||||||
|
|
@ -101,11 +89,15 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
||||||
this.stack.add_titled(this.system_pane, "system_pane", _("System"));
|
this.stack.add_titled(this.system_pane, "system_pane", _("System"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool key_press_event(Gdk.EventKey event) {
|
[GtkCallback]
|
||||||
|
private bool on_key_pressed(Gtk.EventControllerKey controller,
|
||||||
|
uint keyval,
|
||||||
|
uint keycode,
|
||||||
|
Gdk.ModifierType state) {
|
||||||
bool ret = Gdk.EVENT_PROPAGATE;
|
bool ret = Gdk.EVENT_PROPAGATE;
|
||||||
|
|
||||||
if (this.log_pane.search_mode_enabled &&
|
if (this.log_pane.search_mode_enabled &&
|
||||||
event.keyval == Gdk.Key.Escape) {
|
keyval == Gdk.Key.Escape) {
|
||||||
// Manually deactivate search so the button stays in sync
|
// Manually deactivate search so the button stays in sync
|
||||||
this.search_button.set_active(false);
|
this.search_button.set_active(false);
|
||||||
ret = Gdk.EVENT_STOP;
|
ret = Gdk.EVENT_STOP;
|
||||||
|
|
@ -115,18 +107,19 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
||||||
this.log_pane.search_mode_enabled) {
|
this.log_pane.search_mode_enabled) {
|
||||||
// Ensure <Space> and others are passed to the search
|
// Ensure <Space> and others are passed to the search
|
||||||
// entry before getting used as an accelerator.
|
// entry before getting used as an accelerator.
|
||||||
ret = this.log_pane.handle_key_press(event);
|
ret = controller.forward(this.log_pane);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == Gdk.EVENT_PROPAGATE) {
|
//XXX GTK4 not sure how to handle this
|
||||||
ret = base.key_press_event(event);
|
// if (ret == Gdk.EVENT_PROPAGATE) {
|
||||||
}
|
// ret = base.key_press_event(event);
|
||||||
|
// }
|
||||||
|
|
||||||
if (ret == Gdk.EVENT_PROPAGATE &&
|
if (ret == Gdk.EVENT_PROPAGATE &&
|
||||||
!this.log_pane.search_mode_enabled) {
|
!this.log_pane.search_mode_enabled) {
|
||||||
// Nothing has handled the event yet, and search is not
|
// Nothing has handled the event yet, and search is not
|
||||||
// active, so see if we want to activate it now.
|
// active, so see if we want to activate it now.
|
||||||
ret = this.log_pane.handle_key_press(event);
|
ret = controller.forward(this.log_pane);
|
||||||
if (ret == Gdk.EVENT_STOP) {
|
if (ret == Gdk.EVENT_STOP) {
|
||||||
this.search_button.set_active(true);
|
this.search_button.set_active(true);
|
||||||
}
|
}
|
||||||
|
|
@ -135,10 +128,9 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void save(string path,
|
private async void save(GLib.File dest,
|
||||||
GLib.Cancellable? cancellable)
|
GLib.Cancellable? cancellable)
|
||||||
throws GLib.Error {
|
throws GLib.Error {
|
||||||
GLib.File dest = GLib.File.new_for_path(path);
|
|
||||||
GLib.FileIOStream dest_io = yield dest.replace_readwrite_async(
|
GLib.FileIOStream dest_io = yield dest.replace_readwrite_async(
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
|
|
@ -207,39 +199,29 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
||||||
|
|
||||||
string clipboard_value = (string) bytes.get_data();
|
string clipboard_value = (string) bytes.get_data();
|
||||||
if (!Geary.String.is_empty(clipboard_value)) {
|
if (!Geary.String.is_empty(clipboard_value)) {
|
||||||
get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(clipboard_value, -1);
|
get_clipboard().set_text(clipboard_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GtkCallback]
|
[GtkCallback]
|
||||||
private void on_save_as_clicked() {
|
private void on_save_as_clicked() {
|
||||||
Gtk.FileChooserNative chooser = new Gtk.FileChooserNative(
|
save_as.begin();
|
||||||
_("Save As"),
|
}
|
||||||
this,
|
|
||||||
Gtk.FileChooserAction.SAVE,
|
private async void save_as() {
|
||||||
_("Save As"),
|
var dialog = new Gtk.FileDialog();
|
||||||
_("Cancel")
|
dialog.title = _("Save As");
|
||||||
);
|
dialog.accept_label = _("Save As");
|
||||||
chooser.set_current_name(
|
dialog.initial_name = new DateTime.now_local().format(
|
||||||
new GLib.DateTime.now_local().format(
|
"Geary Problem Report - %F %T.txt"
|
||||||
"Geary Problem Report - %F %T.txt"
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (chooser.run() == Gtk.ResponseType.ACCEPT) {
|
try {
|
||||||
this.save.begin(
|
File? file = yield dialog.save(get_root() as Gtk.Window, null);
|
||||||
chooser.get_filename(),
|
if (file != null)
|
||||||
null,
|
yield this.save(file, null);
|
||||||
(obj, res) => {
|
} catch (Error err) {
|
||||||
try {
|
warning("Failed to save problem report data: %s", err.message);
|
||||||
this.save.end(res);
|
|
||||||
} catch (GLib.Error err) {
|
|
||||||
warning(
|
|
||||||
"Failed to save problem report data: %s", err.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,9 +239,4 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
||||||
private void on_logs_search_activated() {
|
private void on_logs_search_activated() {
|
||||||
this.search_button.set_active(true);
|
this.search_button.set_active(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_close() {
|
|
||||||
destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,74 +8,50 @@
|
||||||
* Displays a dialog for collecting the user's password, without allowing them to change their
|
* Displays a dialog for collecting the user's password, without allowing them to change their
|
||||||
* other data.
|
* other data.
|
||||||
*/
|
*/
|
||||||
public class PasswordDialog {
|
[GtkTemplate (ui = "/org/gnome/Geary/password-dialog.ui")]
|
||||||
// We can't keep these in the glade file, because Gnome doesn't want markup in translatable
|
public class PasswordDialog : Adw.AlertDialog {
|
||||||
// strings, and Glade doesn't support the "larger" size attribute. See this bug report for
|
|
||||||
// details: https://bugzilla.gnome.org/show_bug.cgi?id=679006
|
|
||||||
private const string PRIMARY_TEXT_MARKUP = "<span weight=\"bold\" size=\"larger\">%s</span>";
|
|
||||||
private const string PRIMARY_TEXT_FIRST_TRY = _("Geary requires your email password to continue");
|
|
||||||
|
|
||||||
private Gtk.Dialog dialog;
|
[GtkChild] private unowned Adw.PreferencesGroup prefs_group;
|
||||||
private Gtk.Entry entry_password;
|
[GtkChild] private unowned Adw.ActionRow username_row;
|
||||||
private Gtk.CheckButton check_remember_password;
|
[GtkChild] private unowned Adw.PasswordEntryRow password_row;
|
||||||
private Gtk.Button ok_button;
|
[GtkChild] private unowned Adw.SwitchRow remember_password_row;
|
||||||
|
|
||||||
public string password { get; private set; default = ""; }
|
|
||||||
public bool remember_password { get; private set; }
|
|
||||||
|
|
||||||
public PasswordDialog(Gtk.Window? parent,
|
public PasswordDialog(Gtk.Window? parent,
|
||||||
Geary.AccountInformation account,
|
Geary.AccountInformation account,
|
||||||
Geary.ServiceInformation service,
|
Geary.ServiceInformation service,
|
||||||
Geary.Credentials? credentials) {
|
Geary.Credentials? credentials) {
|
||||||
Gtk.Builder builder = GioUtil.create_builder("password-dialog.glade");
|
|
||||||
|
|
||||||
dialog = (Gtk.Dialog) builder.get_object("PasswordDialog");
|
|
||||||
dialog.transient_for = parent;
|
|
||||||
dialog.set_type_hint(Gdk.WindowTypeHint.DIALOG);
|
|
||||||
dialog.set_default_response(Gtk.ResponseType.OK);
|
|
||||||
|
|
||||||
entry_password = (Gtk.Entry) builder.get_object("entry: password");
|
|
||||||
check_remember_password = (Gtk.CheckButton) builder.get_object("check: remember_password");
|
|
||||||
|
|
||||||
Gtk.Label label_username = (Gtk.Label) builder.get_object("label: username");
|
|
||||||
Gtk.Label label_smtp = (Gtk.Label) builder.get_object("label: smtp");
|
|
||||||
|
|
||||||
// Load translated text for labels with markup unsupported by glade.
|
|
||||||
Gtk.Label primary_text_label = (Gtk.Label) builder.get_object("primary_text_label");
|
|
||||||
primary_text_label.set_markup(PRIMARY_TEXT_MARKUP.printf(PRIMARY_TEXT_FIRST_TRY));
|
|
||||||
|
|
||||||
if (credentials != null) {
|
if (credentials != null) {
|
||||||
label_username.set_text(credentials.user);
|
this.username_row.subtitle = credentials.user;
|
||||||
entry_password.set_text(credentials.token ?? "");
|
this.password_row.text = credentials.token ?? "";
|
||||||
}
|
}
|
||||||
check_remember_password.active = service.remember_password;
|
this.remember_password_row.active = service.remember_password;
|
||||||
|
|
||||||
if ((service.protocol == Geary.Protocol.SMTP)) {
|
if ((service.protocol == Geary.Protocol.SMTP)) {
|
||||||
label_smtp.show();
|
this.prefs_group.title = _("SMTP Credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
ok_button = (Gtk.Button) builder.get_object("authenticate_button");
|
|
||||||
|
|
||||||
refresh_ok_button_sensitivity();
|
refresh_ok_button_sensitivity();
|
||||||
entry_password.changed.connect(refresh_ok_button_sensitivity);
|
this.password_row.changed.connect(refresh_ok_button_sensitivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refresh_ok_button_sensitivity() {
|
private void refresh_ok_button_sensitivity() {
|
||||||
ok_button.sensitive = !Geary.String.is_empty_or_whitespace(entry_password.get_text());
|
string password = this.password_row.text;
|
||||||
|
set_response_enabled("authenticate", !Geary.String.is_empty_or_whitespace(password));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool run() {
|
public async string? get_password(Gtk.Window? parent,
|
||||||
dialog.show();
|
out bool remember_password) {
|
||||||
|
string response = yield choose(parent, null);
|
||||||
|
|
||||||
Gtk.ResponseType response = (Gtk.ResponseType) dialog.run();
|
if (response == "cancel") {
|
||||||
if (response == Gtk.ResponseType.OK) {
|
remember_password = false;
|
||||||
password = entry_password.get_text();
|
return null;
|
||||||
remember_password = check_remember_password.active;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.destroy();
|
remember_password = this.remember_password_row.active;
|
||||||
|
string password = this.password_row.text;
|
||||||
return (response == Gtk.ResponseType.OK);
|
close();
|
||||||
|
return password;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,8 @@ public class FolderList.FolderEntry :
|
||||||
entry_changed();
|
entry_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX GTK4 I have no idea yet
|
||||||
|
#if 0
|
||||||
public bool internal_drop_received(Sidebar.Tree parent,
|
public bool internal_drop_received(Sidebar.Tree parent,
|
||||||
Gdk.DragContext context,
|
Gdk.DragContext context,
|
||||||
Gtk.SelectionData data) {
|
Gtk.SelectionData data) {
|
||||||
|
|
@ -104,6 +106,7 @@ public class FolderList.FolderEntry :
|
||||||
}
|
}
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
public override int get_count() {
|
public override int get_count() {
|
||||||
switch (this.context.displayed_count) {
|
switch (this.context.displayed_count) {
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@
|
||||||
public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
|
public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
|
||||||
|
|
||||||
|
|
||||||
public const Gtk.TargetEntry[] TARGET_ENTRY_LIST = {
|
|
||||||
{ "application/x-geary-mail", Gtk.TargetFlags.SAME_APP, 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
private const int INBOX_ORDINAL = -2; // First account branch is zero
|
private const int INBOX_ORDINAL = -2; // First account branch is zero
|
||||||
private const int SEARCH_ORDINAL = -1;
|
private const int SEARCH_ORDINAL = -1;
|
||||||
|
|
||||||
|
|
@ -29,7 +25,9 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
|
||||||
|
|
||||||
|
|
||||||
public Tree() {
|
public Tree() {
|
||||||
base(TARGET_ENTRY_LIST, Gdk.DragAction.COPY | Gdk.DragAction.MOVE, drop_handler);
|
//XXX GTK4 need to set up proper GdkContentFormats here
|
||||||
|
base(new Gdk.ContentFormats({ "application/x-geary-mail" }),
|
||||||
|
Gdk.DragAction.COPY | Gdk.DragAction.MOVE);
|
||||||
base_ref();
|
base_ref();
|
||||||
set_activate_on_single_click(true);
|
set_activate_on_single_click(true);
|
||||||
entry_selected.connect(on_entry_selected);
|
entry_selected.connect(on_entry_selected);
|
||||||
|
|
@ -37,9 +35,12 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
|
||||||
|
|
||||||
// GtkTreeView binds Ctrl+N to "move cursor to next". Not so interested in that, so we'll
|
// GtkTreeView binds Ctrl+N to "move cursor to next". Not so interested in that, so we'll
|
||||||
// remove it.
|
// remove it.
|
||||||
|
//XXX GTK4
|
||||||
|
#if 0
|
||||||
unowned Gtk.BindingSet? binding_set = Gtk.BindingSet.find("GtkTreeView");
|
unowned Gtk.BindingSet? binding_set = Gtk.BindingSet.find("GtkTreeView");
|
||||||
assert(binding_set != null);
|
assert(binding_set != null);
|
||||||
Gtk.BindingEntry.remove(binding_set, Gdk.Key.N, Gdk.ModifierType.CONTROL_MASK);
|
Gtk.BindingEntry.remove(binding_set, Gdk.Key.N, Gdk.ModifierType.CONTROL_MASK);
|
||||||
|
#endif
|
||||||
|
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
}
|
}
|
||||||
|
|
@ -48,9 +49,20 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
|
||||||
base_unref();
|
base_unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void get_preferred_width(out int minimum_size, out int natural_size) {
|
public override void measure(Gtk.Orientation orientation,
|
||||||
minimum_size = 360;
|
int for_size,
|
||||||
natural_size = 500;
|
out int minimum,
|
||||||
|
out int natural,
|
||||||
|
out int minimum_baseline,
|
||||||
|
out int natural_baseline) {
|
||||||
|
if (orientation == Gtk.Orientation.HORIZONTAL) {
|
||||||
|
minimum = 180;
|
||||||
|
natural = 300;
|
||||||
|
} else {
|
||||||
|
//XXX GTK4 - I have no idea what to put here
|
||||||
|
}
|
||||||
|
|
||||||
|
minimum_baseline = natural_baseline = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_has_new(Geary.Folder folder, bool has_new) {
|
public void set_has_new(Geary.Folder folder, bool has_new) {
|
||||||
|
|
@ -68,10 +80,6 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drop_handler(Gdk.DragContext context, Sidebar.Entry? entry,
|
|
||||||
Gtk.SelectionData data, uint info, uint time) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private FolderEntry? get_folder_entry(Geary.Folder folder) {
|
private FolderEntry? get_folder_entry(Geary.Folder folder) {
|
||||||
AccountBranch? account_branch = account_branches.get(folder.account);
|
AccountBranch? account_branch = account_branches.get(folder.account);
|
||||||
return (account_branch == null ? null :
|
return (account_branch == null ? null :
|
||||||
|
|
@ -80,7 +88,7 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
|
||||||
|
|
||||||
public override bool accept_cursor_changed() {
|
public override bool accept_cursor_changed() {
|
||||||
bool can_switch = true;
|
bool can_switch = true;
|
||||||
var parent = get_toplevel() as Application.MainWindow;
|
var parent = get_root() as Application.MainWindow;
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
can_switch = parent.close_composer(false);
|
can_switch = parent.close_composer(false);
|
||||||
}
|
}
|
||||||
|
|
@ -221,6 +229,8 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
|
||||||
folder_selected(null);
|
folder_selected(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX GTK4 I'm not sur eif this is needed still?
|
||||||
|
#if 0
|
||||||
public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) {
|
public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) {
|
||||||
// Run the base version first.
|
// Run the base version first.
|
||||||
bool ret = base.drag_motion(context, x, y, time);
|
bool ret = base.drag_motion(context, x, y, time);
|
||||||
|
|
@ -236,6 +246,7 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
public void set_search(Geary.Engine engine,
|
public void set_search(Geary.Engine engine,
|
||||||
Geary.App.SearchFolder search_folder) {
|
Geary.App.SearchFolder search_folder) {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,10 @@ client_vala_sources = files(
|
||||||
'accounts/accounts-editor-list-pane.vala',
|
'accounts/accounts-editor-list-pane.vala',
|
||||||
'accounts/accounts-editor-row.vala',
|
'accounts/accounts-editor-row.vala',
|
||||||
'accounts/accounts-editor-servers-pane.vala',
|
'accounts/accounts-editor-servers-pane.vala',
|
||||||
|
'accounts/accounts-mailbox-editor-dialog.vala',
|
||||||
|
'accounts/accounts-service-information-widget.vala',
|
||||||
'accounts/accounts-signature-web-view.vala',
|
'accounts/accounts-signature-web-view.vala',
|
||||||
|
'accounts/accounts-tls-combo-row.vala',
|
||||||
'accounts/accounts-manager.vala',
|
'accounts/accounts-manager.vala',
|
||||||
|
|
||||||
'client-action.vala',
|
'client-action.vala',
|
||||||
|
|
@ -49,31 +52,29 @@ client_vala_sources = files(
|
||||||
'components/components-attachment-pane.vala',
|
'components/components-attachment-pane.vala',
|
||||||
'components/components-conversation-actions.vala',
|
'components/components-conversation-actions.vala',
|
||||||
'components/components-entry-undo.vala',
|
'components/components-entry-undo.vala',
|
||||||
'components/components-headerbar-application.vala',
|
|
||||||
'components/components-headerbar-conversation-list.vala',
|
|
||||||
'components/components-headerbar-conversation.vala',
|
'components/components-headerbar-conversation.vala',
|
||||||
'components/components-info-bar-stack.vala',
|
'components/components-info-bar-stack.vala',
|
||||||
'components/components-info-bar.vala',
|
'components/components-info-bar.vala',
|
||||||
'components/components-inspector.vala',
|
'components/components-inspector.vala',
|
||||||
'components/components-in-app-notification.vala',
|
|
||||||
'components/components-inspector-error-view.vala',
|
'components/components-inspector-error-view.vala',
|
||||||
'components/components-inspector-log-view.vala',
|
'components/components-inspector-log-view.vala',
|
||||||
'components/components-inspector-system-view.vala',
|
'components/components-inspector-system-view.vala',
|
||||||
'components/components-placeholder-pane.vala',
|
'components/components-placeholder-pane.vala',
|
||||||
'components/components-preferences-window.vala',
|
'components/components-preferences-dialog.vala',
|
||||||
'components/components-problem-report-info-bar.vala',
|
'components/components-problem-report-info-bar.vala',
|
||||||
'components/components-reflow-box.c',
|
'components/components-reflow-box.c',
|
||||||
'components/components-search-bar.vala',
|
'components/components-search-bar.vala',
|
||||||
'components/components-validator.vala',
|
'components/components-validator.vala',
|
||||||
|
'components/components-validator-group.vala',
|
||||||
'components/components-web-view.vala',
|
'components/components-web-view.vala',
|
||||||
'components/count-badge.vala',
|
'components/count-badge.vala',
|
||||||
'components/folder-popover.vala',
|
'components/folder-popover.vala',
|
||||||
'components/folder-popover-row.vala',
|
'components/folder-popover-row.vala',
|
||||||
'components/icon-factory.vala',
|
|
||||||
'components/monitored-progress-bar.vala',
|
'components/monitored-progress-bar.vala',
|
||||||
'components/monitored-spinner.vala',
|
'components/monitored-spinner.vala',
|
||||||
'components/stock.vala',
|
'components/stock.vala',
|
||||||
|
|
||||||
|
'composer/composer-addresses-row.vala',
|
||||||
'composer/composer-application-interface.vala',
|
'composer/composer-application-interface.vala',
|
||||||
'composer/composer-box.vala',
|
'composer/composer-box.vala',
|
||||||
'composer/composer-container.vala',
|
'composer/composer-container.vala',
|
||||||
|
|
@ -100,8 +101,6 @@ client_vala_sources = files(
|
||||||
'conversation-viewer/conversation-viewer.vala',
|
'conversation-viewer/conversation-viewer.vala',
|
||||||
'conversation-viewer/conversation-web-view.vala',
|
'conversation-viewer/conversation-web-view.vala',
|
||||||
|
|
||||||
'dialogs/alert-dialog.vala',
|
|
||||||
'dialogs/attachment-dialog.vala',
|
|
||||||
'dialogs/certificate-warning-dialog.vala',
|
'dialogs/certificate-warning-dialog.vala',
|
||||||
'dialogs/dialogs-problem-details-dialog.vala',
|
'dialogs/dialogs-problem-details-dialog.vala',
|
||||||
'dialogs/password-dialog.vala',
|
'dialogs/password-dialog.vala',
|
||||||
|
|
@ -162,18 +161,18 @@ client_dependencies = [
|
||||||
gio,
|
gio,
|
||||||
gmime,
|
gmime,
|
||||||
goa,
|
goa,
|
||||||
gspell,
|
|
||||||
gtk,
|
gtk,
|
||||||
icu_uc,
|
icu_uc,
|
||||||
javascriptcoregtk,
|
javascriptcoregtk,
|
||||||
json_glib,
|
json_glib,
|
||||||
libhandy,
|
libadwaita,
|
||||||
libmath,
|
libmath,
|
||||||
libpeas,
|
libpeas,
|
||||||
libsecret,
|
libsecret,
|
||||||
|
libspelling,
|
||||||
libxml,
|
libxml,
|
||||||
posix,
|
posix,
|
||||||
webkit2gtk,
|
webkitgtk,
|
||||||
]
|
]
|
||||||
|
|
||||||
client_build_dir = meson.current_build_dir()
|
client_build_dir = meson.current_build_dir()
|
||||||
|
|
@ -191,7 +190,7 @@ client_vala_args += [
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
if webkit2gtk.version().version_compare('<2.31')
|
if webkitgtk.version().version_compare('<2.31')
|
||||||
client_vala_args += [ '--define=WEBKIT_PLUGINS_SUPPORTED' ]
|
client_vala_args += [ '--define=WEBKIT_PLUGINS_SUPPORTED' ]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ public class Plugin.DesktopNotifications :
|
||||||
Email email
|
Email email
|
||||||
) throws GLib.Error {
|
) throws GLib.Error {
|
||||||
string title = to_notitication_title(folder.account, total);
|
string title = to_notitication_title(folder.account, total);
|
||||||
GLib.Icon icon = null;
|
GLib.Icon? icon = null;
|
||||||
Geary.RFC822.MailboxAddress? originator = email.get_primary_originator();
|
Geary.RFC822.MailboxAddress? originator = email.get_primary_originator();
|
||||||
if (originator != null) {
|
if (originator != null) {
|
||||||
ContactStore contacts =
|
ContactStore contacts =
|
||||||
|
|
@ -132,20 +132,44 @@ public class Plugin.DesktopNotifications :
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int window_scale = 1;
|
Gdk.Texture texture;
|
||||||
Gdk.Display? display = Gdk.Display.get_default();
|
if (icon != null) {
|
||||||
if (display != null) {
|
var icon_stream = yield ((LoadableIcon) icon).load_async(32, null);
|
||||||
Gdk.Monitor? monitor = display.get_primary_monitor();
|
var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(icon_stream, 32, 32, false);
|
||||||
if (monitor != null) {
|
texture = Gdk.Texture.for_pixbuf(pixbuf);
|
||||||
window_scale = monitor.scale_factor;
|
} else {
|
||||||
}
|
texture = generate_fallback_avatar(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
var avatar = new Hdy.Avatar(32, title, true);
|
issue_arrived_notification(title, body, texture, folder, email.identifier);
|
||||||
avatar.loadable_icon = icon as GLib.LoadableIcon;
|
}
|
||||||
icon = yield avatar.draw_to_pixbuf_async(32, window_scale, null);
|
|
||||||
|
|
||||||
issue_arrived_notification(title, body, icon, folder, email.identifier);
|
private Gdk.Texture generate_fallback_avatar(string title) {
|
||||||
|
Gsk.Renderer renderer = new Gsk.VulkanRenderer();
|
||||||
|
try {
|
||||||
|
renderer.realize(null);
|
||||||
|
} catch (GLib.Error error) {
|
||||||
|
warning("Couldn't realize vulkan renderer: %s", error.message);
|
||||||
|
renderer = new Gsk.CairoRenderer();
|
||||||
|
try {
|
||||||
|
renderer.realize(null);
|
||||||
|
} catch (GLib.Error error) {
|
||||||
|
warning("Couldn't realize Cairo renderer: %s", error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var avatar = new Adw.Avatar(32, title, true);
|
||||||
|
var paintable = new Gtk.WidgetPaintable(avatar);
|
||||||
|
|
||||||
|
// Ideally we could use Adw.Avatar.draw_to_texture(),
|
||||||
|
// but that unfortunately relies on a Gtk.Native existing already
|
||||||
|
var snapshot = new Gtk.Snapshot();
|
||||||
|
paintable.snapshot(snapshot, 32, 32);
|
||||||
|
Gsk.RenderNode node = snapshot.to_node();
|
||||||
|
Gdk.Texture texture = renderer.render_texture(node, null);
|
||||||
|
|
||||||
|
renderer.unrealize();
|
||||||
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notify_general(Folder folder, int total, int added) {
|
private void notify_general(Folder folder, int total, int added) {
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ public class Plugin.MailMerge :
|
||||||
|
|
||||||
private async void merge_email(EmailIdentifier id,
|
private async void merge_email(EmailIdentifier id,
|
||||||
GLib.File? default_csv_file) {
|
GLib.File? default_csv_file) {
|
||||||
var csv_file = default_csv_file ?? show_merge_data_chooser();
|
var csv_file = default_csv_file ?? yield show_merge_data_chooser();
|
||||||
if (csv_file != null) {
|
if (csv_file != null) {
|
||||||
try {
|
try {
|
||||||
var csv_input = yield csv_file.read_async(
|
var csv_input = yield csv_file.read_async(
|
||||||
|
|
@ -297,7 +297,7 @@ public class Plugin.MailMerge :
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void load_composer_data(Composer composer) {
|
private async void load_composer_data(Composer composer) {
|
||||||
var data = show_merge_data_chooser();
|
var data = yield show_merge_data_chooser();
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
var insert_field_action = new GLib.SimpleAction(
|
var insert_field_action = new GLib.SimpleAction(
|
||||||
ACTION_INSERT_FIELD,
|
ACTION_INSERT_FIELD,
|
||||||
|
|
@ -388,26 +388,20 @@ public class Plugin.MailMerge :
|
||||||
return action_bar;
|
return action_bar;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GLib.File? show_merge_data_chooser() {
|
private async GLib.File? show_merge_data_chooser() {
|
||||||
var chooser = new Gtk.FileChooserNative(
|
var dialog = new Gtk.FileDialog();
|
||||||
/// Translators: File chooser title after invoking mail
|
/// Translators: Filechooser title after invoking mail merge in composer
|
||||||
/// merge in composer
|
dialog.title = _("Mail Merge");
|
||||||
_("Mail Merge"),
|
|
||||||
null, OPEN,
|
|
||||||
_("_Open"),
|
|
||||||
_("_Cancel")
|
|
||||||
);
|
|
||||||
var csv_filter = new Gtk.FileFilter();
|
var csv_filter = new Gtk.FileFilter();
|
||||||
/// Translators: File chooser filer label
|
/// Translators: File chooser filer label
|
||||||
csv_filter.set_filter_name(_("Comma separated values (CSV)"));
|
csv_filter.set_filter_name(_("Comma separated values (CSV)"));
|
||||||
csv_filter.add_mime_type("text/csv");
|
csv_filter.add_mime_type("text/csv");
|
||||||
chooser.add_filter(csv_filter);
|
var filters = new GLib.ListStore(typeof(Gtk.FileFilter));
|
||||||
|
filters.append(csv_filter);
|
||||||
|
dialog.filters = filters;
|
||||||
|
|
||||||
return (
|
return yield dialog.open(null, null);
|
||||||
chooser.run() == Gtk.ResponseType.ACCEPT
|
|
||||||
? chooser.get_file()
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insert_field(Composer composer, string field) {
|
private void insert_field(Composer composer, string field) {
|
||||||
|
|
|
||||||
|
|
@ -59,4 +59,4 @@ plugin_test = executable(
|
||||||
install: false
|
install: false
|
||||||
)
|
)
|
||||||
|
|
||||||
test(plugin_name + '-test', plugin_test)
|
# test(plugin_name + '-test', plugin_test)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
plugin_dependencies = [
|
plugin_dependencies = [
|
||||||
config_dep,
|
config_dep,
|
||||||
folks,
|
folks,
|
||||||
gdk,
|
|
||||||
client_dep,
|
client_dep,
|
||||||
engine_dep,
|
engine_dep,
|
||||||
gee,
|
gee,
|
||||||
|
|
@ -14,10 +13,10 @@ plugin_dependencies = [
|
||||||
goa,
|
goa,
|
||||||
gtk,
|
gtk,
|
||||||
javascriptcoregtk,
|
javascriptcoregtk,
|
||||||
libhandy,
|
libadwaita,
|
||||||
libmath,
|
libmath,
|
||||||
libpeas,
|
libpeas,
|
||||||
webkit2gtk,
|
webkitgtk,
|
||||||
]
|
]
|
||||||
|
|
||||||
plugin_c_args = geary_c_args
|
plugin_c_args = geary_c_args
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ public class Sidebar.Header : Sidebar.Grouping, Sidebar.EmphasizableEntry {
|
||||||
|
|
||||||
public interface Sidebar.Contextable : Object {
|
public interface Sidebar.Contextable : Object {
|
||||||
// Return null if the context menu should not be invoked for this event
|
// Return null if the context menu should not be invoked for this event
|
||||||
public abstract Gtk.Menu? get_sidebar_context_menu(Gdk.EventButton event);
|
//XXX GTK4 is this used?
|
||||||
|
// public abstract Gtk.PopoverMenu? get_sidebar_context_menu(Gdk.EventButton event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,24 +27,19 @@ public class SidebarCountCellRenderer : Gtk.CellRenderer {
|
||||||
natural_size = minimum_size;
|
natural_size = minimum_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area,
|
public override void snapshot(Gtk.Snapshot snapshot,
|
||||||
Gdk.Rectangle cell_area, Gtk.CellRendererState flags) {
|
Gtk.Widget widget,
|
||||||
unread_count.count = counter;
|
Gdk.Rectangle background_area,
|
||||||
|
Gdk.Rectangle cell_area,
|
||||||
|
Gtk.CellRendererState flags) {
|
||||||
|
this.unread_count.count = this.counter;
|
||||||
|
|
||||||
|
Graphene.Rect cell_rect = { { cell_area.x, cell_area.y } , { cell_area.width, cell_area.height } };
|
||||||
|
Cairo.Context ctx = snapshot.append_cairo(cell_rect);
|
||||||
// Compute x and y locations to right-align and vertically center the count.
|
// Compute x and y locations to right-align and vertically center the count.
|
||||||
int x = cell_area.x + (cell_area.width - unread_count.get_width(widget)) - HORIZONTAL_MARGIN;
|
int x = cell_area.x + (cell_area.width - unread_count.get_width(widget)) - HORIZONTAL_MARGIN;
|
||||||
int y = cell_area.y + ((cell_area.height - unread_count.get_height(widget)) / 2);
|
int y = cell_area.y + ((cell_area.height - unread_count.get_height(widget)) / 2);
|
||||||
unread_count.render(widget, ctx, x, y, false);
|
unread_count.render(widget, ctx, x, y, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is implemented because it's required; ignore it and look at get_preferred_width() instead.
|
|
||||||
public override void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset,
|
|
||||||
out int y_offset, out int width, out int height) {
|
|
||||||
// Set values to avoid compiler warning.
|
|
||||||
x_offset = 0;
|
|
||||||
y_offset = 0;
|
|
||||||
width = 0;
|
|
||||||
height = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,16 @@ public interface Sidebar.DestroyableEntry : Sidebar.Entry {
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Sidebar.InternalDropTargetEntry : Sidebar.Entry {
|
public interface Sidebar.InternalDropTargetEntry : Sidebar.Entry {
|
||||||
|
//XXX GTK4 I have no idea yet
|
||||||
|
#if 0
|
||||||
// Returns true if drop was successful
|
// Returns true if drop was successful
|
||||||
public abstract bool internal_drop_received(Sidebar.Tree parent,
|
public abstract bool internal_drop_received(Sidebar.Tree parent,
|
||||||
Gdk.DragContext context,
|
Gdk.DragContext context,
|
||||||
Gtk.SelectionData data);
|
Gtk.SelectionData data);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Sidebar.InternalDragSourceEntry : Sidebar.Entry {
|
public interface Sidebar.InternalDragSourceEntry : Sidebar.Entry {
|
||||||
public abstract void prepare_selection_data(Gtk.SelectionData data);
|
//XXX GTK4: is this even used?
|
||||||
|
// public abstract void prepare_selection_data(Gtk.SelectionData data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,7 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
|
|
||||||
// Only one ExternalDropHandler can be registered with the Tree; it's responsible for completing
|
// Only one ExternalDropHandler can be registered with the Tree; it's responsible for completing
|
||||||
// the "drag-data-received" signal properly.
|
// the "drag-data-received" signal properly.
|
||||||
public delegate void ExternalDropHandler(Gdk.DragContext context, Sidebar.Entry? entry,
|
public delegate void ExternalDropHandler(Gtk.DropTarget context, Sidebar.Entry? entry);
|
||||||
Gtk.SelectionData data, uint info, uint time);
|
|
||||||
|
|
||||||
private class EntryWrapper : Object {
|
private class EntryWrapper : Object {
|
||||||
public Sidebar.Entry entry;
|
public Sidebar.Entry entry;
|
||||||
|
|
@ -72,7 +71,7 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
private int editing_disabled = 0;
|
private int editing_disabled = 0;
|
||||||
private bool mask_entry_selected_signal = false;
|
private bool mask_entry_selected_signal = false;
|
||||||
private weak EntryWrapper? selected_wrapper = null;
|
private weak EntryWrapper? selected_wrapper = null;
|
||||||
private Gtk.Menu? default_context_menu = null;
|
private Gtk.PopoverMenu? default_context_menu = null;
|
||||||
private bool is_internal_drag_in_progress = false;
|
private bool is_internal_drag_in_progress = false;
|
||||||
private Sidebar.Entry? internal_drag_source_entry = null;
|
private Sidebar.Entry? internal_drag_source_entry = null;
|
||||||
private Gtk.TreeRowReference? old_path_ref = null;
|
private Gtk.TreeRowReference? old_path_ref = null;
|
||||||
|
|
@ -89,11 +88,10 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
|
|
||||||
public signal void branch_shown(Sidebar.Branch branch, bool shown);
|
public signal void branch_shown(Sidebar.Branch branch, bool shown);
|
||||||
|
|
||||||
public Tree(Gtk.TargetEntry[] target_entries, Gdk.DragAction actions,
|
public Tree(Gdk.ContentFormats formats, Gdk.DragAction actions, Gtk.IconTheme? theme = null) {
|
||||||
ExternalDropHandler drop_handler, Gtk.IconTheme? theme = null) {
|
|
||||||
set_model(store);
|
set_model(store);
|
||||||
icon_theme = theme;
|
icon_theme = theme;
|
||||||
get_style_context().add_class("sidebar");
|
add_css_class("navigation-sidebar");
|
||||||
|
|
||||||
text_column = new Gtk.TreeViewColumn();
|
text_column = new Gtk.TreeViewColumn();
|
||||||
text_column.set_expand(true);
|
text_column.set_expand(true);
|
||||||
|
|
@ -131,7 +129,7 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
// It Would Be Nice if the target entries and actions were gleaned by querying each
|
// It Would Be Nice if the target entries and actions were gleaned by querying each
|
||||||
// Sidebar.Entry as it was added, but that's a tad too complicated for our needs
|
// Sidebar.Entry as it was added, but that's a tad too complicated for our needs
|
||||||
// currently
|
// currently
|
||||||
enable_model_drag_dest(target_entries, actions);
|
enable_model_drag_dest(formats, actions);
|
||||||
|
|
||||||
// Drag source removed as per http://redmine.yorba.org/issues/4701
|
// Drag source removed as per http://redmine.yorba.org/issues/4701
|
||||||
//
|
//
|
||||||
|
|
@ -143,11 +141,23 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
|
|
||||||
this.drop_handler = drop_handler;
|
this.drop_handler = drop_handler;
|
||||||
|
|
||||||
popup_menu.connect(on_context_menu_keypress);
|
Gtk.DragSource drag_source = new Gtk.DragSource();
|
||||||
|
drag_source.drag_begin.connect(on_drag_source_begin);
|
||||||
|
drag_source.drag_end.connect(on_drag_source_end);
|
||||||
|
drag_source.prepare.connect(on_drag_source_prepare);
|
||||||
|
add_controller(drag_source);
|
||||||
|
|
||||||
drag_begin.connect(on_drag_begin);
|
//XXX GTK4 - need to figure out the params still
|
||||||
drag_end.connect(on_drag_end);
|
Gtk.DropTarget drop_target = new Gtk.DropTarget(Type.INVALID, Gdk.DragAction.COPY | Gdk.DragAction.MOVE);
|
||||||
drag_motion.connect(on_drag_motion);
|
drop_target.enter.connect(on_drop_target_enter);
|
||||||
|
add_controller(drop_target);
|
||||||
|
|
||||||
|
var key_controller = new Gtk.EventControllerKey();
|
||||||
|
key_controller.key_pressed.connect(on_key_pressed);
|
||||||
|
add_controller(key_controller);
|
||||||
|
var click_gesture = new Gtk.GestureClick();
|
||||||
|
click_gesture.pressed.connect(on_button_pressed);
|
||||||
|
add_controller(click_gesture);
|
||||||
}
|
}
|
||||||
|
|
||||||
~Tree() {
|
~Tree() {
|
||||||
|
|
@ -172,29 +182,31 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
renderer.visible = counter_renderer != null && counter_renderer.counter > 0;
|
renderer.visible = counter_renderer != null && counter_renderer.counter > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_drag_begin(Gdk.DragContext ctx) {
|
private void on_drag_source_begin(Gtk.DragSource drag_source, Gdk.Drag drag) {
|
||||||
is_internal_drag_in_progress = true;
|
this.is_internal_drag_in_progress = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_drag_end(Gdk.DragContext ctx) {
|
private void on_drag_source_end(Gtk.DragSource drag_source, Gdk.Drag drag, bool delete_data) {
|
||||||
is_internal_drag_in_progress = false;
|
this.is_internal_drag_in_progress = false;
|
||||||
internal_drag_source_entry = null;
|
this.internal_drag_source_entry = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_drag_motion (Gdk.DragContext context, int x, int y, uint time_) {
|
private Gdk.DragAction on_drop_target_enter(Gtk.DropTarget drop_target, double x, double y) {
|
||||||
if (is_internal_drag_in_progress && internal_drag_source_entry == null) {
|
if (this.is_internal_drag_in_progress && this.internal_drag_source_entry == null) {
|
||||||
Gtk.TreePath? path;
|
Gtk.TreePath? path;
|
||||||
Gtk.TreeViewDropPosition position;
|
Gtk.TreeViewDropPosition position;
|
||||||
get_dest_row_at_pos(x, y, out path, out position);
|
get_dest_row_at_pos((int) x, (int) y, out path, out position);
|
||||||
|
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
EntryWrapper wrapper = get_wrapper_at_path(path);
|
EntryWrapper wrapper = get_wrapper_at_path(path);
|
||||||
if (wrapper != null)
|
if (wrapper != null) {
|
||||||
internal_drag_source_entry = wrapper.entry;
|
this.internal_drag_source_entry = wrapper.entry;
|
||||||
|
return Gdk.DragAction.COPY | Gdk.DragAction.MOVE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool has_wrapper(Sidebar.Entry entry) {
|
private bool has_wrapper(Sidebar.Entry entry) {
|
||||||
|
|
@ -231,8 +243,8 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
return get_wrapper_at_iter(iter);
|
return get_wrapper_at_iter(iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_default_context_menu(Gtk.Menu context_menu) {
|
public void set_default_context_menu(Gtk.PopoverMenu context_menu) {
|
||||||
default_context_menu = context_menu;
|
this.default_context_menu = context_menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that this method will result in the "entry-selected" signal to fire if mask_signal
|
// Note that this method will result in the "entry-selected" signal to fire if mask_signal
|
||||||
|
|
@ -296,7 +308,7 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void row_activated(Gtk.TreePath path, Gtk.TreeViewColumn column) {
|
public override void row_activated(Gtk.TreePath path, Gtk.TreeViewColumn? column) {
|
||||||
if (column != text_column)
|
if (column != text_column)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -750,17 +762,10 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
return (wrapper != null) ? (wrapper.entry is Sidebar.SelectableEntry) : false;
|
return (wrapper != null) ? (wrapper.entry is Sidebar.SelectableEntry) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Gtk.TreePath? get_path_from_event(Gdk.EventButton event) {
|
private Gtk.TreePath? get_path_from_position(double x, double y) {
|
||||||
int x, y;
|
|
||||||
Gdk.ModifierType mask;
|
|
||||||
event.window.get_device_position(
|
|
||||||
event.get_seat().get_pointer(),
|
|
||||||
out x, out y, out mask
|
|
||||||
);
|
|
||||||
|
|
||||||
int cell_x, cell_y;
|
int cell_x, cell_y;
|
||||||
Gtk.TreePath path;
|
Gtk.TreePath path;
|
||||||
return get_path_at_pos(x, y, out path, null, out cell_x, out cell_y) ? path : null;
|
return get_path_at_pos((int) x, (int) y, out path, null, out cell_x, out cell_y) ? path : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Gtk.TreePath? get_current_path() {
|
private Gtk.TreePath? get_current_path() {
|
||||||
|
|
@ -771,63 +776,57 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
return rows.length() != 0 ? rows.nth_data(0) : null;
|
return rows.length() != 0 ? rows.nth_data(0) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool on_context_menu_keypress() {
|
private bool popup_context_menu(Gtk.TreePath path, Gdk.Rectangle? area = null) {
|
||||||
GLib.List<Gtk.TreePath> rows = get_selection().get_selected_rows(null);
|
|
||||||
if (rows == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Gtk.TreePath? path = rows.data;
|
|
||||||
if (path == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
scroll_to_cell(path, null, false, 0, 0);
|
|
||||||
|
|
||||||
return popup_context_menu(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool popup_context_menu(Gtk.TreePath path, Gdk.EventButton? event = null) {
|
|
||||||
EntryWrapper? wrapper = get_wrapper_at_path(path);
|
EntryWrapper? wrapper = get_wrapper_at_path(path);
|
||||||
if (wrapper == null)
|
if (wrapper == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
//XXX GTK4
|
||||||
|
#if 0
|
||||||
Sidebar.Contextable? contextable = wrapper.entry as Sidebar.Contextable;
|
Sidebar.Contextable? contextable = wrapper.entry as Sidebar.Contextable;
|
||||||
if (contextable == null)
|
if (contextable == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Gtk.Menu? context_menu = contextable.get_sidebar_context_menu(event);
|
Gtk.PopoverMenu? context_menu = contextable.get_sidebar_context_menu(event);
|
||||||
if (context_menu == null)
|
if (context_menu == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
context_menu.popup_at_pointer(event);
|
if (area != null)
|
||||||
|
context_menu.set_pointing_to(area);
|
||||||
|
context_menu.popup();
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool popup_default_context_menu(Gdk.EventButton event) {
|
private void popup_default_context_menu(Gdk.Rectangle area) {
|
||||||
if (default_context_menu != null)
|
if (this.default_context_menu == null)
|
||||||
default_context_menu.popup_at_pointer(event);
|
return;
|
||||||
return true;
|
this.default_context_menu.set_pointing_to(area);
|
||||||
|
this.default_context_menu.popup();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool button_press_event(Gdk.EventButton event) {
|
private void on_button_pressed(Gtk.GestureClick click_gesture, int n_pressed, double x, double y) {
|
||||||
Gtk.TreePath? path = get_path_from_event(event);
|
Gtk.TreePath? path = get_path_from_position(x, y);
|
||||||
|
|
||||||
if (event.button == 3 && event.type == Gdk.EventType.BUTTON_PRESS) {
|
var button = click_gesture.get_current_button();
|
||||||
|
if (button == Gdk.BUTTON_SECONDARY && n_pressed == 1) {
|
||||||
|
Gdk.Rectangle rect = { (int) x, (int) y, 1, 1 };
|
||||||
// single right click
|
// single right click
|
||||||
if (path != null)
|
if (path != null)
|
||||||
popup_context_menu(path, event);
|
popup_context_menu(path, rect);
|
||||||
else
|
else
|
||||||
popup_default_context_menu(event);
|
popup_default_context_menu(rect);
|
||||||
} else if (event.button == 1 && event.type == Gdk.EventType.BUTTON_PRESS) {
|
} else if (button == Gdk.BUTTON_PRIMARY) {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
old_path_ref = null;
|
old_path_ref = null;
|
||||||
return base.button_press_event(event);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EntryWrapper? wrapper = get_wrapper_at_path(path);
|
EntryWrapper? wrapper = get_wrapper_at_path(path);
|
||||||
|
|
||||||
if (wrapper == null) {
|
if (wrapper == null) {
|
||||||
old_path_ref = null;
|
old_path_ref = null;
|
||||||
return base.button_press_event(event);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this a click on an already-highlighted tree item?
|
// Is this a click on an already-highlighted tree item?
|
||||||
|
|
@ -836,7 +835,7 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
// yes, don't allow single-click editing, but
|
// yes, don't allow single-click editing, but
|
||||||
// pass the event on for dragging.
|
// pass the event on for dragging.
|
||||||
text_renderer.editable = false;
|
text_renderer.editable = false;
|
||||||
return base.button_press_event(event);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Got click on different tree item, make sure it is editable
|
// Got click on different tree item, make sure it is editable
|
||||||
|
|
@ -849,13 +848,11 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
// Remember what tree item is highlighted for next time.
|
// Remember what tree item is highlighted for next time.
|
||||||
old_path_ref = new Gtk.TreeRowReference(store, path);
|
old_path_ref = new Gtk.TreeRowReference(store, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.button_press_event(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool key_press_event(Gdk.EventKey event) {
|
private bool on_key_pressed(Gtk.EventControllerKey key_controller, uint keyval, uint keycode, Gdk.ModifierType state) {
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
switch (Gdk.keyval_name(event.keyval)) {
|
switch (Gdk.keyval_name(keyval)) {
|
||||||
case "F2":
|
case "F2":
|
||||||
handled = rename_in_place();
|
handled = rename_in_place();
|
||||||
break;
|
break;
|
||||||
|
|
@ -865,9 +862,6 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
handled = (path != null) ? destroy_path(path) : false;
|
handled = (path != null) ? destroy_path(path) : false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!handled) {
|
|
||||||
handled = base.key_press_event(event);
|
|
||||||
}
|
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -905,35 +899,40 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void drag_data_get(Gdk.DragContext context, Gtk.SelectionData selection_data,
|
private Gdk.ContentProvider? on_drag_source_prepare(Gtk.DragSource drag_source,
|
||||||
uint info, uint time) {
|
double x,
|
||||||
InternalDragSourceEntry? drag_source = null;
|
double y) {
|
||||||
|
InternalDragSourceEntry? drag_source_entry = null;
|
||||||
|
|
||||||
if (internal_drag_source_entry != null) {
|
if (internal_drag_source_entry != null) {
|
||||||
Sidebar.SelectableEntry selectable =
|
Sidebar.SelectableEntry selectable =
|
||||||
internal_drag_source_entry as Sidebar.SelectableEntry;
|
internal_drag_source_entry as Sidebar.SelectableEntry;
|
||||||
if (selectable == null) {
|
if (selectable == null) {
|
||||||
drag_source = internal_drag_source_entry as InternalDragSourceEntry;
|
drag_source_entry = internal_drag_source_entry as InternalDragSourceEntry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drag_source == null) {
|
if (drag_source_entry == null) {
|
||||||
Gtk.TreePath? selected_path = get_selected_path();
|
Gtk.TreePath? selected_path = get_selected_path();
|
||||||
if (selected_path == null)
|
if (selected_path == null)
|
||||||
return;
|
return null;
|
||||||
|
|
||||||
EntryWrapper? wrapper = get_wrapper_at_path(selected_path);
|
EntryWrapper? wrapper = get_wrapper_at_path(selected_path);
|
||||||
if (wrapper == null)
|
if (wrapper == null)
|
||||||
return;
|
return null;
|
||||||
|
|
||||||
drag_source = wrapper.entry as InternalDragSourceEntry;
|
drag_source_entry = wrapper.entry as InternalDragSourceEntry;
|
||||||
if (drag_source == null)
|
if (drag_source_entry == null)
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
drag_source.prepare_selection_data(selection_data);
|
//XXX GTK4, it looks like nothing is implementing this?
|
||||||
|
// drag_source_entry.prepare_selection_data(selection_data);
|
||||||
|
return null; //XXX GTK4 what do I return here?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX GTK4 not sure how to do this yet
|
||||||
|
#if 0
|
||||||
public override void drag_data_received(Gdk.DragContext context, int x, int y,
|
public override void drag_data_received(Gdk.DragContext context, int x, int y,
|
||||||
Gtk.SelectionData selection_data, uint info, uint time) {
|
Gtk.SelectionData selection_data, uint info, uint time) {
|
||||||
|
|
||||||
|
|
@ -974,10 +973,13 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX GTK4 I have no idea yet
|
||||||
|
#if 0
|
||||||
bool success = targetable.internal_drop_received(
|
bool success = targetable.internal_drop_received(
|
||||||
this, context, selection_data
|
this, context, selection_data
|
||||||
);
|
);
|
||||||
Gtk.drag_finish(context, success, false, time);
|
Gtk.drag_finish(context, success, false, time);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) {
|
public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) {
|
||||||
|
|
@ -998,6 +1000,7 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
|
|
||||||
return has_dest;
|
return has_dest;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Returns true if path is renameable, and selects the path as well.
|
// Returns true if path is renameable, and selects the path as well.
|
||||||
private bool can_rename_path(Gtk.TreePath path) {
|
private bool can_rename_path(Gtk.TreePath path) {
|
||||||
|
|
@ -1038,7 +1041,7 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
if (editable is Gtk.Entry) {
|
if (editable is Gtk.Entry) {
|
||||||
text_entry = (Gtk.Entry) editable;
|
text_entry = (Gtk.Entry) editable;
|
||||||
text_entry.editing_done.connect(on_editing_done);
|
text_entry.editing_done.connect(on_editing_done);
|
||||||
text_entry.focus_out_event.connect(on_editing_focus_out);
|
// text_entry.focus_out_event.connect(on_editing_focus_out);
|
||||||
text_entry.editable = true;
|
text_entry.editable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1047,7 +1050,7 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
text_entry.editable = false;
|
text_entry.editable = false;
|
||||||
|
|
||||||
text_entry.editing_done.disconnect(on_editing_done);
|
text_entry.editing_done.disconnect(on_editing_done);
|
||||||
text_entry.focus_out_event.disconnect(on_editing_focus_out);
|
// text_entry.focus_out_event.disconnect(on_editing_focus_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_editing_done() {
|
private void on_editing_done() {
|
||||||
|
|
@ -1061,14 +1064,17 @@ public class Sidebar.Tree : Gtk.TreeView {
|
||||||
}
|
}
|
||||||
|
|
||||||
text_entry.editing_done.disconnect(on_editing_done);
|
text_entry.editing_done.disconnect(on_editing_done);
|
||||||
text_entry.focus_out_event.disconnect(on_editing_focus_out);
|
// text_entry.focus_out_event.disconnect(on_editing_focus_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX GTK4 I(m not sure how to remove the focus controller again, so commenting out for now
|
||||||
|
#if 0
|
||||||
private bool on_editing_focus_out(Gdk.EventFocus event) {
|
private bool on_editing_focus_out(Gdk.EventFocus event) {
|
||||||
// We'll return false here, in case other parts of the app
|
// We'll return false here, in case other parts of the app
|
||||||
// want to know if the button press event that caused
|
// want to know if the button press event that caused
|
||||||
// us to lose focus have been fully handled.
|
// us to lose focus have been fully handled.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ namespace Util.Contact {
|
||||||
return true;
|
return true;
|
||||||
// Contact domain trusted
|
// Contact domain trusted
|
||||||
} else {
|
} else {
|
||||||
foreach (Geary.RFC822.MailboxAddress email in email_addresses) {
|
for (uint i = 0; i < email_addresses.get_n_items(); i++) {
|
||||||
|
var email = (Geary.RFC822.MailboxAddress) email_addresses.get_item(i);
|
||||||
if (email.domain in domains) {
|
if (email.domain in domains) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,7 @@ namespace Util.Gtk {
|
||||||
*/
|
*/
|
||||||
public inline int get_border_box_height(global::Gtk.Widget widget) {
|
public inline int get_border_box_height(global::Gtk.Widget widget) {
|
||||||
global::Gtk.StyleContext style = widget.get_style_context();
|
global::Gtk.StyleContext style = widget.get_style_context();
|
||||||
global::Gtk.StateFlags flags = style.get_state();
|
global::Gtk.Border margin = style.get_margin();
|
||||||
global::Gtk.Border margin = style.get_margin(flags);
|
|
||||||
|
|
||||||
return widget.get_allocated_height() - margin.top - margin.bottom;
|
return widget.get_allocated_height() - margin.top - margin.bottom;
|
||||||
}
|
}
|
||||||
|
|
@ -218,15 +217,6 @@ namespace Util.Gtk {
|
||||||
return new_url;
|
return new_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Gdk.RGBA rgba(double red, double green, double blue, double alpha) {
|
|
||||||
return Gdk.RGBA() {
|
|
||||||
red = red,
|
|
||||||
green = green,
|
|
||||||
blue = blue,
|
|
||||||
alpha = alpha
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Connect this to Gtk.Widget.query_tooltip signal, will only show tooltip if label ellipsized */
|
/* Connect this to Gtk.Widget.query_tooltip signal, will only show tooltip if label ellipsized */
|
||||||
public bool query_tooltip_label(global::Gtk.Widget widget, int x, int y, bool keyboard, global::Gtk.Tooltip tooltip) {
|
public bool query_tooltip_label(global::Gtk.Widget widget, int x, int y, bool keyboard, global::Gtk.Tooltip tooltip) {
|
||||||
global::Gtk.Label label = widget as global::Gtk.Label;
|
global::Gtk.Label label = widget as global::Gtk.Label;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
/**
|
/**
|
||||||
* Initialises GearyWebExtension for WebKit web processes.
|
* Initialises GearyWebExtension for WebKit web processes.
|
||||||
*/
|
*/
|
||||||
public void webkit_web_extension_initialize_with_user_data(WebKit.WebExtension extension,
|
public void webkit_web_process_extension_initialize_with_user_data(WebKit.WebProcessExtension extension,
|
||||||
Variant data) {
|
Variant data) {
|
||||||
bool logging_enabled = data.get_boolean();
|
bool logging_enabled = data.get_boolean();
|
||||||
|
|
||||||
Geary.Logging.init();
|
Geary.Logging.init();
|
||||||
|
|
@ -26,7 +26,7 @@ public void webkit_web_extension_initialize_with_user_data(WebKit.WebExtension e
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A WebExtension that manages Geary-specific behaviours in web processes.
|
* A WebProcessExtension that manages Geary-specific behaviours in web processes.
|
||||||
*/
|
*/
|
||||||
public class GearyWebExtension : Object {
|
public class GearyWebExtension : Object {
|
||||||
|
|
||||||
|
|
@ -43,15 +43,17 @@ public class GearyWebExtension : Object {
|
||||||
private const string EXTENSION_CLASS_SEND = "send";
|
private const string EXTENSION_CLASS_SEND = "send";
|
||||||
private const string EXTENSION_CLASS_ALLOW_REMOTE_LOAD = "allowRemoteResourceLoad";
|
private const string EXTENSION_CLASS_ALLOW_REMOTE_LOAD = "allowRemoteResourceLoad";
|
||||||
|
|
||||||
private WebKit.WebExtension extension;
|
private WebKit.WebProcessExtension extension;
|
||||||
|
|
||||||
|
|
||||||
public GearyWebExtension(WebKit.WebExtension extension) {
|
public GearyWebExtension(WebKit.WebProcessExtension extension) {
|
||||||
this.extension = extension;
|
this.extension = extension;
|
||||||
extension.page_created.connect(on_page_created);
|
extension.page_created.connect(on_page_created);
|
||||||
WebKit.ScriptWorld.get_default().window_object_cleared.connect(on_window_object_cleared);
|
WebKit.ScriptWorld.get_default().window_object_cleared.connect(on_window_object_cleared);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//XXX GTK4 - it seems this is no longer possible?
|
||||||
|
#if 0
|
||||||
private void on_console_message(WebKit.WebPage page,
|
private void on_console_message(WebKit.WebPage page,
|
||||||
WebKit.ConsoleMessage message) {
|
WebKit.ConsoleMessage message) {
|
||||||
string source = message.get_source_id();
|
string source = message.get_source_id();
|
||||||
|
|
@ -63,6 +65,7 @@ public class GearyWebExtension : Object {
|
||||||
message.get_text()
|
message.get_text()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private bool on_send_request(WebKit.WebPage page,
|
private bool on_send_request(WebKit.WebPage page,
|
||||||
WebKit.URIRequest request,
|
WebKit.URIRequest request,
|
||||||
|
|
@ -128,9 +131,10 @@ public class GearyWebExtension : Object {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_page_created(WebKit.WebExtension extension,
|
private void on_page_created(WebKit.WebProcessExtension extension,
|
||||||
WebKit.WebPage page) {
|
WebKit.WebPage page) {
|
||||||
page.console_message_sent.connect(on_console_message);
|
//XXX GTK4
|
||||||
|
// page.console_message_sent.connect(on_console_message);
|
||||||
page.send_request.connect(on_send_request);
|
page.send_request.connect(on_send_request);
|
||||||
page.user_message_received.connect(on_page_message_received);
|
page.user_message_received.connect(on_page_message_received);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
51
src/console/imap-console.ui
Normal file
51
src/console/imap-console.ui
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="ImapConsole" parent="AdwApplicationWindow">
|
||||||
|
<property name="title">IMAP Console</property>
|
||||||
|
<property name="default-width">800</property>
|
||||||
|
<property name="default-height">600</property>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="AdwToolbarView">
|
||||||
|
<property name="top-bar-style">raised</property>
|
||||||
|
|
||||||
|
<child type="top">
|
||||||
|
<object class="AdwHeaderBar">
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<property name="content">
|
||||||
|
<object class="GtkBox" id="layout">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">4</property>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow" id="scrolled_console">
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="hscrollbar-policy">automatic</property>
|
||||||
|
<property name="vscrollbar-policy">automatic</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTextView" id="console">
|
||||||
|
<property name="editable">False</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="cmdline">
|
||||||
|
<signal name="activate" handler="on_activate"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkStatusbar" id="statusbar">
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
||||||
|
|
@ -12,12 +12,13 @@ errordomain CommandException {
|
||||||
|
|
||||||
const int IMAP_TIMEOUT_SEC = 60 * 15;
|
const int IMAP_TIMEOUT_SEC = 60 * 15;
|
||||||
|
|
||||||
class ImapConsole : Gtk.Window {
|
[GtkTemplate (ui = "/org/gnome/GearyConsole/imap-console.ui")]
|
||||||
|
class ImapConsole : Adw.ApplicationWindow {
|
||||||
private const int KEEPALIVE_SEC = 60 * 10;
|
private const int KEEPALIVE_SEC = 60 * 10;
|
||||||
|
|
||||||
private Gtk.TextView console = new Gtk.TextView();
|
[GtkChild] private unowned Gtk.TextView console;
|
||||||
private Gtk.Entry cmdline = new Gtk.Entry();
|
[GtkChild] private unowned Gtk.Entry cmdline;
|
||||||
private Gtk.Statusbar statusbar = new Gtk.Statusbar();
|
[GtkChild] private unowned Gtk.Statusbar statusbar;
|
||||||
|
|
||||||
private uint statusbar_ctx = 0;
|
private uint statusbar_ctx = 0;
|
||||||
private uint statusbar_msg_id = 0;
|
private uint statusbar_msg_id = 0;
|
||||||
|
|
@ -27,41 +28,26 @@ class ImapConsole : Gtk.Window {
|
||||||
Geary.Imap.Tag, Geary.Imap.StatusResponse>();
|
Geary.Imap.Tag, Geary.Imap.StatusResponse>();
|
||||||
private Geary.Nonblocking.Event recvd_response_event = new Geary.Nonblocking.Event();
|
private Geary.Nonblocking.Event recvd_response_event = new Geary.Nonblocking.Event();
|
||||||
|
|
||||||
public ImapConsole() {
|
public ImapConsole(Adw.Application app) {
|
||||||
title = "IMAP Console";
|
Object(application: app);
|
||||||
destroy.connect(() => { Gtk.main_quit(); });
|
|
||||||
set_default_size(800, 600);
|
|
||||||
|
|
||||||
Gtk.Box layout = new Gtk.Box(Gtk.Orientation.VERTICAL, 4);
|
|
||||||
|
|
||||||
console.editable = false;
|
|
||||||
Gtk.ScrolledWindow scrolled_console = new Gtk.ScrolledWindow(null, null);
|
|
||||||
scrolled_console.add(console);
|
|
||||||
scrolled_console.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
|
|
||||||
layout.pack_start(scrolled_console, true, true, 0);
|
|
||||||
|
|
||||||
cmdline.activate.connect(on_activate);
|
|
||||||
layout.pack_start(cmdline, false, false, 0);
|
|
||||||
|
|
||||||
statusbar_ctx = statusbar.get_context_id("status");
|
|
||||||
layout.pack_end(statusbar, false, false, 0);
|
|
||||||
|
|
||||||
add(layout);
|
|
||||||
|
|
||||||
|
this.cmdline.activate.connect(on_activate);
|
||||||
|
this.statusbar_ctx = statusbar.get_context_id("status");
|
||||||
cmdline.grab_focus();
|
cmdline.grab_focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_activate() {
|
[GtkCallback]
|
||||||
|
private void on_activate(Gtk.Entry cmdline) {
|
||||||
exec(cmdline.buffer.text);
|
exec(cmdline.buffer.text);
|
||||||
cmdline.buffer.delete_text(0, -1);
|
cmdline.buffer.delete_text(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clear_status() {
|
private void clear_status() {
|
||||||
if (statusbar_msg_id == 0)
|
if (this.statusbar_msg_id == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
statusbar.remove(statusbar_ctx, statusbar_msg_id);
|
this.statusbar.remove(this.statusbar_ctx, this.statusbar_msg_id);
|
||||||
statusbar_msg_id = 0;
|
this.statusbar_msg_id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void status(string text) {
|
private void status(string text) {
|
||||||
|
|
@ -71,7 +57,7 @@ class ImapConsole : Gtk.Window {
|
||||||
if (!msg.has_suffix(".") && !msg.has_prefix("usage"))
|
if (!msg.has_suffix(".") && !msg.has_prefix("usage"))
|
||||||
msg += ".";
|
msg += ".";
|
||||||
|
|
||||||
statusbar_msg_id = statusbar.push(statusbar_ctx, msg);
|
this.statusbar_msg_id = this.statusbar.push(this.statusbar_ctx, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exception(Error err) {
|
private void exception(Error err) {
|
||||||
|
|
@ -606,7 +592,8 @@ class ImapConsole : Gtk.Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void quit(string cmd, string[] args) throws Error {
|
private void quit(string cmd, string[] args) throws Error {
|
||||||
Gtk.main_quit();
|
GLib.Application app = GLib.Application.get_default();
|
||||||
|
app.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool keepalive_on = false;
|
private bool keepalive_on = false;
|
||||||
|
|
@ -692,14 +679,16 @@ class ImapConsole : Gtk.Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void main(string[] args) {
|
int main(string[] args) {
|
||||||
Gtk.init(ref args);
|
|
||||||
|
|
||||||
Geary.Logging.init();
|
Geary.Logging.init();
|
||||||
Geary.Logging.log_to(stdout);
|
Geary.Logging.log_to(stdout);
|
||||||
|
|
||||||
ImapConsole console = new ImapConsole();
|
Adw.Application app = new Adw.Application(null, GLib.ApplicationFlags.DEFAULT_FLAGS);
|
||||||
console.show_all();
|
|
||||||
|
|
||||||
Gtk.main();
|
app.activate.connect((gapp) => {
|
||||||
|
ImapConsole console = new ImapConsole(app);
|
||||||
|
console.present();
|
||||||
|
});
|
||||||
|
|
||||||
|
return app.run(args);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,17 @@ console_dependencies = [
|
||||||
gtk,
|
gtk,
|
||||||
gee,
|
gee,
|
||||||
gmime,
|
gmime,
|
||||||
webkit2gtk,
|
libadwaita,
|
||||||
|
webkitgtk,
|
||||||
engine_dep,
|
engine_dep,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
console_resources = gnome.compile_resources('org.gnome.GearyConsole',
|
||||||
|
files('org.gnome.GearyConsole.gresource.xml'),
|
||||||
|
)
|
||||||
|
|
||||||
console = executable('geary-console',
|
console = executable('geary-console',
|
||||||
console_sources,
|
[ console_sources, console_resources ],
|
||||||
dependencies: console_dependencies,
|
dependencies: console_dependencies,
|
||||||
vala_args: geary_vala_args,
|
vala_args: geary_vala_args,
|
||||||
c_args: geary_c_args,
|
c_args: geary_c_args,
|
||||||
|
|
|
||||||
6
src/console/org.gnome.GearyConsole.gresource.xml
Normal file
6
src/console/org.gnome.GearyConsole.gresource.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<gresources>
|
||||||
|
<gresource prefix="/org/gnome/GearyConsole">
|
||||||
|
<file compressed="true" preprocess="xml-stripblanks">imap-console.ui</file>
|
||||||
|
</gresource>
|
||||||
|
</gresources>
|
||||||
|
|
@ -94,6 +94,27 @@ public class Geary.Credentials : BaseObject, Gee.Hashable<Geary.Credentials> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string to_string() {
|
||||||
|
switch (this) {
|
||||||
|
case NONE:
|
||||||
|
// Translators: ComboBox value for source of SMTP
|
||||||
|
// authentication credentials (none) when adding a new
|
||||||
|
// account
|
||||||
|
return _("No login needed");
|
||||||
|
case USE_INCOMING:
|
||||||
|
// Translators: ComboBox value for source of SMTP
|
||||||
|
// authentication credentials (use IMAP) when adding a new
|
||||||
|
// account
|
||||||
|
return _("Use same login as receiving");
|
||||||
|
case CUSTOM:
|
||||||
|
// Translators: ComboBox value for source of SMTP
|
||||||
|
// authentication credentials (custom) when adding a new
|
||||||
|
// account
|
||||||
|
return _("Use a different login");
|
||||||
|
}
|
||||||
|
return_val_if_reached("");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,21 @@ public enum Geary.TlsNegotiationMethod {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a user-displayable string of the enum value
|
||||||
|
*/
|
||||||
|
public unowned string to_string() {
|
||||||
|
switch (this) {
|
||||||
|
case Geary.TlsNegotiationMethod.NONE:
|
||||||
|
return _("None");
|
||||||
|
case Geary.TlsNegotiationMethod.START_TLS:
|
||||||
|
return _("StartTLS");
|
||||||
|
case Geary.TlsNegotiationMethod.TRANSPORT:
|
||||||
|
return _("TLS");
|
||||||
|
}
|
||||||
|
|
||||||
|
return_val_if_reached("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -621,7 +621,7 @@ public class Geary.Logging.State {
|
||||||
* this by calling {@link get_earliest_record} and then {get_next},
|
* this by calling {@link get_earliest_record} and then {get_next},
|
||||||
* and can be notified of new records via {@link set_log_listener}.
|
* and can be notified of new records via {@link set_log_listener}.
|
||||||
*/
|
*/
|
||||||
public class Geary.Logging.Record {
|
public class Geary.Logging.Record : GLib.Object {
|
||||||
|
|
||||||
|
|
||||||
/** The GLib domain of the log message, if any. */
|
/** The GLib domain of the log message, if any. */
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ mailer_dependencies = [
|
||||||
config_dep,
|
config_dep,
|
||||||
gee,
|
gee,
|
||||||
gmime,
|
gmime,
|
||||||
webkit2gtk,
|
webkitgtk,
|
||||||
engine_dep,
|
engine_dep,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ web_process = library('geary-web-process',
|
||||||
engine_dep,
|
engine_dep,
|
||||||
gee,
|
gee,
|
||||||
gmime,
|
gmime,
|
||||||
webkit2gtk_web_extension,
|
webkitgtk_web_extension,
|
||||||
],
|
],
|
||||||
vala_args: geary_vala_args,
|
vala_args: geary_vala_args,
|
||||||
c_args: geary_c_args,
|
c_args: geary_c_args,
|
||||||
|
|
@ -98,7 +98,6 @@ bin_sources += [
|
||||||
]
|
]
|
||||||
bin_dependencies = [
|
bin_dependencies = [
|
||||||
folks,
|
folks,
|
||||||
gdk,
|
|
||||||
client_dep,
|
client_dep,
|
||||||
engine_dep,
|
engine_dep,
|
||||||
gee,
|
gee,
|
||||||
|
|
@ -106,10 +105,10 @@ bin_dependencies = [
|
||||||
goa,
|
goa,
|
||||||
gtk,
|
gtk,
|
||||||
javascriptcoregtk,
|
javascriptcoregtk,
|
||||||
libhandy,
|
libadwaita,
|
||||||
libmath,
|
libmath,
|
||||||
libpeas,
|
libpeas,
|
||||||
webkit2gtk,
|
webkitgtk,
|
||||||
]
|
]
|
||||||
|
|
||||||
bin = executable('geary',
|
bin = executable('geary',
|
||||||
|
|
@ -126,31 +125,27 @@ valadoc_dependencies = [
|
||||||
enchant,
|
enchant,
|
||||||
folks,
|
folks,
|
||||||
gcr,
|
gcr,
|
||||||
gdk,
|
|
||||||
gee,
|
gee,
|
||||||
gio,
|
gio,
|
||||||
glib,
|
glib,
|
||||||
gmime,
|
gmime,
|
||||||
goa,
|
goa,
|
||||||
gspell,
|
|
||||||
gtk,
|
gtk,
|
||||||
javascriptcoregtk,
|
javascriptcoregtk,
|
||||||
json_glib,
|
json_glib,
|
||||||
libhandy,
|
libadwaita,
|
||||||
libpeas,
|
libpeas,
|
||||||
libsecret,
|
libsecret,
|
||||||
|
libspelling,
|
||||||
libxml,
|
libxml,
|
||||||
sqlite,
|
sqlite,
|
||||||
webkit2gtk
|
webkitgtk,
|
||||||
]
|
]
|
||||||
|
|
||||||
valadoc_vapi_dirs = [
|
valadoc_vapi_dirs = [
|
||||||
vapi_dir,
|
vapi_dir,
|
||||||
meson.current_build_dir()
|
meson.current_build_dir()
|
||||||
]
|
]
|
||||||
if libhandy_vapi != ''
|
|
||||||
valadoc_vapi_dirs += libhandy_vapi
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Hopefully Meson will get baked-in valadoc support, so we don't have
|
# Hopefully Meson will get baked-in valadoc support, so we don't have
|
||||||
# to resort to these kinds of hacks any more. See
|
# to resort to these kinds of hacks any more. See
|
||||||
|
|
@ -159,10 +154,10 @@ endif
|
||||||
valadoc_dep_args = []
|
valadoc_dep_args = []
|
||||||
foreach dep : valadoc_dependencies
|
foreach dep : valadoc_dependencies
|
||||||
valadoc_dep_args += '--pkg'
|
valadoc_dep_args += '--pkg'
|
||||||
if dep != libhandy
|
if dep != libadwaita
|
||||||
valadoc_dep_args += dep.name()
|
valadoc_dep_args += dep.name()
|
||||||
else
|
else
|
||||||
valadoc_dep_args += 'libhandy-1'
|
valadoc_dep_args += 'libadwaita-1'
|
||||||
endif
|
endif
|
||||||
endforeach
|
endforeach
|
||||||
valadoc_dep_args += [ '--pkg', 'config' ]
|
valadoc_dep_args += [ '--pkg', 'config' ]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
[wrap-git]
|
|
||||||
directory = libhandy
|
|
||||||
url = https://gitlab.gnome.org/GNOME/libhandy.git
|
|
||||||
revision = 1.2.1
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue