diff --git a/po/POTFILES.in b/po/POTFILES.in index add1424e..4cf10a10 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -51,6 +51,7 @@ src/client/components/status-bar.vala src/client/components/stock.vala src/client/composer/composer-box.vala src/client/composer/composer-container.vala +src/client/composer/composer-email-entry.vala src/client/composer/composer-embed.vala src/client/composer/composer-headerbar.vala src/client/composer/composer-link-popover.vala @@ -58,7 +59,6 @@ src/client/composer/composer-web-view.vala src/client/composer/composer-widget.vala src/client/composer/composer-window.vala src/client/composer/contact-entry-completion.vala -src/client/composer/email-entry.vala src/client/composer/spell-check-popover.vala src/client/conversation-list/conversation-list-cell-renderer.vala src/client/conversation-list/conversation-list-store.vala diff --git a/src/client/composer/composer-email-entry.vala b/src/client/composer/composer-email-entry.vala new file mode 100644 index 00000000..9502fb9a --- /dev/null +++ b/src/client/composer/composer-email-entry.vala @@ -0,0 +1,113 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2020 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * A GTK entry for entering email addresses. + */ +public class Composer.EmailEntry : Gtk.Entry { + + /** The entry's list of possibly valid email addresses. */ + public Geary.RFC822.MailboxAddresses addresses { + get { return this._addresses; } + set { + this._addresses = value; + validate_addresses(); + this.is_modified = false; + this.text = value.to_full_display(); + } + } + private Geary.RFC822.MailboxAddresses _addresses = new Geary.RFC822.MailboxAddresses(); + + /** Determines if the entry contains only valid email addresses. */ + public bool is_valid { get; private set; default = false; } + + /** Determines if the entry contains any email addresses. */ + public bool is_empty { + get { + return this._addresses.is_empty; + } + } + + /** + * Determines if the entry has been modified. + * + * The entry is considered to be modified only if the text has + * been changed after it as been constructed or if modified after + * setting {@link addresses}. + */ + public bool is_modified { get; private set; default = false; } + + private weak Composer.Widget composer; + + + public EmailEntry(Composer.Widget composer) { + changed.connect(on_changed); + key_press_event.connect(on_key_press); + this.composer = composer; + show(); + } + + /** Marks the entry as being modified. */ + public void set_modified() { + this.is_modified = true; + } + + 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() { + this.is_modified = true; + + ContactEntryCompletion? completion = + get_completion() as ContactEntryCompletion; + if (completion != null) { + completion.update_model(); + } + + if (Geary.String.is_empty(text.strip())) { + this.addresses = new Geary.RFC822.MailboxAddresses(); + this.is_valid = false; + } else { + this.addresses = + new Geary.RFC822.MailboxAddresses.from_rfc822_string(text); + this.is_valid = true; + } + } + + private bool on_key_press(Gtk.Widget widget, Gdk.EventKey event) { + bool ret = Gdk.EVENT_PROPAGATE; + if (event.keyval == Gdk.Key.Tab) { + ContactEntryCompletion? completion = ( + get_completion() as ContactEntryCompletion + ); + if (completion != null) { + completion.trigger_selection(); + composer.child_focus(Gtk.DirectionType.TAB_FORWARD); + ret = Gdk.EVENT_STOP; + } + } else { + // Keyboard shortcuts for undo/redo won't work when the + // completion UI is visible unless we explicitly check for + // them there. This may be related to the + // single-key-shortcut handling hack in the MainWindow. + Gtk.Window? window = get_toplevel() as Gtk.Window; + if (window != null) { + ret = window.activate_key(event); + } + } + return ret; + } +} diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala index d935ca46..95513538 100644 --- a/src/client/composer/composer-widget.vala +++ b/src/client/composer/composer-widget.vala @@ -255,10 +255,10 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { /** Determines if the composer is completely empty. */ public bool is_blank { get { - return this.to_entry.empty - && this.cc_entry.empty - && this.bcc_entry.empty - && this.reply_to_entry.empty + return this.to_entry.is_empty + && this.cc_entry.is_empty + && this.bcc_entry.is_empty + && this.reply_to_entry.is_empty && this.subject_entry.buffer.length == 0 && this.editor.is_empty && this.attached_files.size == 0; @@ -1017,29 +1017,28 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { else this.compose_type = ComposeType.REPLY_ALL; - this.to_entry.modified = this.cc_entry.modified = this.bcc_entry.modified = false; if (!to_entry.addresses.equal_to(reply_to_addresses)) - this.to_entry.modified = true; + this.to_entry.set_modified(); if (cc != "" && !cc_entry.addresses.equal_to(reply_cc_addresses)) - this.cc_entry.modified = true; + this.cc_entry.set_modified(); if (bcc != "") - this.bcc_entry.modified = true; + this.bcc_entry.set_modified(); // We're in compact inline mode, but there are modified email // addresses, so set us to use plain inline mode instead so // the modified addresses can be seen. If there are CC if (this.current_mode == INLINE_COMPACT && ( - this.to_entry.modified || - this.cc_entry.modified || - this.bcc_entry.modified || - this.reply_to_entry.modified)) { + this.to_entry.is_modified || + this.cc_entry.is_modified || + this.bcc_entry.is_modified || + this.reply_to_entry.is_modified)) { set_mode(INLINE); } // If there's a modified header that would normally be hidden, // show full fields. - if (this.bcc_entry.modified || - this.reply_to_entry.modified) { + if (this.bcc_entry.is_modified || + this.reply_to_entry.is_modified) { this.editor_actions.change_action_state( ACTION_SHOW_EXTENDED_HEADERS, true ); @@ -1402,7 +1401,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { if (!modify_headers) return; - bool recipients_modified = this.to_entry.modified || this.cc_entry.modified || this.bcc_entry.modified; + bool recipients_modified = this.to_entry.is_modified || this.cc_entry.is_modified || this.bcc_entry.is_modified; if (!recipients_modified) { if (type == ComposeType.REPLY || type == ComposeType.REPLY_ALL) this.to_entry.addresses = Geary.RFC822.Utils.merge_addresses(to_entry.addresses, @@ -1414,7 +1413,6 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { else this.cc_entry.addresses = Geary.RFC822.Utils.remove_addresses(this.cc_entry.addresses, this.to_entry.addresses); - this.to_entry.modified = this.cc_entry.modified = false; } if (referred.message_id != null) { @@ -1953,16 +1951,16 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { // To must be valid (and hence non-empty), the other email // fields must be either empty or valid. get_action(ACTION_SEND).set_enabled( - this.to_entry.valid && - (this.cc_entry.empty || this.cc_entry.valid) && - (this.bcc_entry.empty || this.bcc_entry.valid) && - (this.reply_to_entry.empty || this.reply_to_entry.valid) + this.to_entry.is_valid && + (this.cc_entry.is_empty || this.cc_entry.is_valid) && + (this.bcc_entry.is_empty || this.bcc_entry.is_valid) && + (this.reply_to_entry.is_empty || this.reply_to_entry.is_valid) ); } private void set_compact_header_recipients() { - bool tocc = !this.to_entry.empty && !this.cc_entry.empty, - ccbcc = !(this.to_entry.empty && this.cc_entry.empty) && !this.bcc_entry.empty; + bool tocc = !this.to_entry.is_empty && !this.cc_entry.is_empty, + ccbcc = !(this.to_entry.is_empty && this.cc_entry.is_empty) && !this.bcc_entry.is_empty; string label = this.to_entry.buffer.text + (tocc ? ", " : "") + this.cc_entry.buffer.text + (ccbcc ? ", " : "") + this.bcc_entry.buffer.text; StringBuilder tooltip = new StringBuilder(); @@ -2145,9 +2143,9 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { } private void update_extended_headers(bool reorder=true) { - bool cc = this.cc_entry.addresses != null; - bool bcc = this.bcc_entry.addresses != null; - bool reply_to = this.reply_to_entry.addresses != null; + bool cc = !this.cc_entry.is_empty; + bool bcc = !this.bcc_entry.is_empty; + bool reply_to = !this.reply_to_entry.is_empty; if (reorder) { if (cc) { diff --git a/src/client/composer/email-entry.vala b/src/client/composer/email-entry.vala deleted file mode 100644 index 2af610f7..00000000 --- a/src/client/composer/email-entry.vala +++ /dev/null @@ -1,106 +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 custom entry for e-mail addresses -public class EmailEntry : Gtk.Entry { - // Whether this entry contains a valid email address - public bool valid { get; set; default = false; } - - public bool empty { get; set; default = true; } - - public bool modified = false; - - // null or valid addresses - public Geary.RFC822.MailboxAddresses? addresses { get; set; default = null; } - - private weak Composer.Widget composer; - - private bool updating = false; - - public EmailEntry(Composer.Widget composer) { - changed.connect(on_changed); - key_press_event.connect(on_key_press); - this.composer = composer; - - notify["addresses"].connect(() => { - validate_addresses(); - if (updating) - return; - - updating = true; - modified = true; - text = (addresses == null) ? "" : addresses.to_full_display(); - updating = false; - }); - - show(); - } - - private void on_changed() { - if (updating) - return; - modified = true; - - ContactEntryCompletion? completion = get_completion() as ContactEntryCompletion; - if (completion != null) { - completion.update_model(); - } - - if (Geary.String.is_empty(text.strip())) { - updating = true; - addresses = null; - updating = false; - valid = false; - empty = true; - return; - } - - updating = true; - addresses = new Geary.RFC822.MailboxAddresses.from_rfc822_string(text); - updating = false; - } - - private void validate_addresses() { - if (addresses == null || addresses.size == 0) { - valid = false; - empty = true; - return; - } - empty = false; - - foreach (Geary.RFC822.MailboxAddress address in addresses) { - if (!address.is_valid()) { - valid = false; - return; - } - } - valid = true; - } - - private bool on_key_press(Gtk.Widget widget, Gdk.EventKey event) { - bool ret = Gdk.EVENT_PROPAGATE; - if (event.keyval == Gdk.Key.Tab) { - ContactEntryCompletion? completion = ( - get_completion() as ContactEntryCompletion - ); - if (completion != null) { - completion.trigger_selection(); - composer.child_focus(Gtk.DirectionType.TAB_FORWARD); - ret = Gdk.EVENT_STOP; - } - } else { - // Keyboard shortcuts for undo/redo won't work when the - // completion UI is visible unless we explicitly check for - // them there. This may be related to the - // single-key-shortcut handling hack in the MainWindow. - Gtk.Window? window = get_toplevel() as Gtk.Window; - if (window != null) { - ret = window.activate_key(event); - } - } - return ret; - } -} diff --git a/src/client/meson.build b/src/client/meson.build index bb61d9f5..35c876bf 100644 --- a/src/client/meson.build +++ b/src/client/meson.build @@ -53,6 +53,7 @@ geary_client_vala_sources = files( 'composer/composer-box.vala', 'composer/composer-container.vala', + 'composer/composer-email-entry.vala', 'composer/composer-embed.vala', 'composer/composer-headerbar.vala', 'composer/composer-link-popover.vala', @@ -60,7 +61,6 @@ geary_client_vala_sources = files( 'composer/composer-widget.vala', 'composer/composer-window.vala', 'composer/contact-entry-completion.vala', - 'composer/email-entry.vala', 'composer/spell-check-popover.vala', 'conversation-list/conversation-list-cell-renderer.vala',