~hkdb/geary/disco-3.34.1

« back to all changes in this revision

Viewing changes to src/client/composer/composer-link-popover.vala

  • Committer: hkdb
  • Date: 2019-10-08 10:54:21 UTC
  • Revision ID: hkdb@3df.io-20191008105421-3dkwnpnhcamm77to
Tags: upstream-3.34.1-disco
ImportĀ upstreamĀ versionĀ 3.34.1-disco

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2017 Michael Gratton <mike@vee.net>
 
3
 *
 
4
 * This software is licensed under the GNU Lesser General Public License
 
5
 * (version 2.1 or later). See the COPYING file in this distribution.
 
6
 */
 
7
 
 
8
 
 
9
/**
 
10
 * A popover for editing a link in the composer.
 
11
 *
 
12
 * The exact appearance of the popover will depend on the {@link
 
13
 * Type} passed to the constructor:
 
14
 *
 
15
 * - For {@link Type.NEW_LINK}, the user will be presented with an
 
16
 *   insert button and an open button.
 
17
 * - For {@link Type.EXISTING_LINK}, the user will be presented with
 
18
 *   an update, delete and open buttons.
 
19
 */
 
20
[GtkTemplate (ui = "/org/gnome/Geary/composer-link-popover.ui")]
 
21
public class ComposerLinkPopover : Gtk.Popover {
 
22
 
 
23
    private const string[] HTTP_SCHEMES = { "http", "https" };
 
24
    private const string[] OTHER_SCHEMES = {
 
25
        "aim", "apt", "bitcoin", "cvs", "ed2k", "ftp", "file", "finger",
 
26
        "git", "gtalk", "irc", "ircs", "irc6", "lastfm", "ldap", "ldaps",
 
27
        "magnet", "news", "nntp", "rsync", "sftp", "skype", "smb", "sms",
 
28
        "svn", "telnet", "tftp", "ssh", "webcal", "xmpp"
 
29
    };
 
30
 
 
31
    /** Determines which version of the UI is presented to the user. */
 
32
    public enum Type {
 
33
        /** A new link is being created. */
 
34
        NEW_LINK,
 
35
 
 
36
        /** An existing link is being edited. */
 
37
        EXISTING_LINK,
 
38
    }
 
39
 
 
40
    /** The URL displayed in the popover */
 
41
    public string link_uri { get { return this.url.get_text(); } }
 
42
 
 
43
    [GtkChild]
 
44
    private Gtk.Entry url;
 
45
 
 
46
    [GtkChild]
 
47
    private Gtk.Button insert;
 
48
 
 
49
    [GtkChild]
 
50
    private Gtk.Button update;
 
51
 
 
52
    [GtkChild]
 
53
    private Gtk.Button delete;
 
54
 
 
55
    [GtkChild]
 
56
    private Gtk.Button open;
 
57
 
 
58
    private Geary.TimeoutManager validation_timeout;
 
59
 
 
60
 
 
61
    /** Emitted when the link URL has changed. */
 
62
    public signal void link_changed(Soup.URI? uri, bool is_valid);
 
63
 
 
64
    /** Emitted when the link URL was activated. */
 
65
    public signal void link_activate();
 
66
 
 
67
    /** Emitted when the open button was activated. */
 
68
    public signal void link_open();
 
69
 
 
70
    /** Emitted when the delete button was activated. */
 
71
    public signal void link_delete();
 
72
 
 
73
 
 
74
    public ComposerLinkPopover(Type type) {
 
75
        set_default_widget(this.url);
 
76
        set_focus_child(this.url);
 
77
        switch (type) {
 
78
        case Type.NEW_LINK:
 
79
            this.update.hide();
 
80
            this.delete.hide();
 
81
            break;
 
82
        case Type.EXISTING_LINK:
 
83
            this.insert.hide();
 
84
            break;
 
85
        }
 
86
        this.validation_timeout = new Geary.TimeoutManager.milliseconds(
 
87
            150, () => { validate(); }
 
88
        );
 
89
    }
 
90
 
 
91
    public override void show() {
 
92
        base.show();
 
93
        this.url.grab_focus();
 
94
    }
 
95
 
 
96
    public override void destroy() {
 
97
        this.validation_timeout.reset();
 
98
        base.destroy();
 
99
    }
 
100
 
 
101
    public void set_link_url(string url) {
 
102
        this.url.set_text(url);
 
103
        this.validation_timeout.reset(); // Don't update on manual set
 
104
    }
 
105
 
 
106
    private void validate() {
 
107
        string? text = this.url.get_text().strip();
 
108
        bool is_empty = Geary.String.is_empty(text);
 
109
        bool is_valid = false;
 
110
        bool is_nominal = false;
 
111
        bool is_mailto = false;
 
112
        Soup.URI? url = null;
 
113
        if (!is_empty) {
 
114
            url = new Soup.URI(text);
 
115
            if (url != null) {
 
116
                is_valid = true;
 
117
 
 
118
                string? scheme = url.get_scheme();
 
119
                string? path = url.get_path();
 
120
                if (scheme in HTTP_SCHEMES) {
 
121
                    is_nominal = Geary.Inet.is_valid_display_host(url.get_host());
 
122
                } else if (scheme == "mailto") {
 
123
                    is_mailto = true;
 
124
                    is_nominal = (
 
125
                        !Geary.String.is_empty(path) &&
 
126
                        Geary.RFC822.MailboxAddress.is_valid_address(path)
 
127
                    );
 
128
                } else if (scheme in OTHER_SCHEMES) {
 
129
                    is_nominal = !Geary.String.is_empty(path);
 
130
                }
 
131
            } else if (text == "http:/" || text == "https:/") {
 
132
                // Don't let the URL entry switch to invalid and back
 
133
                // between "http:" and "http://"
 
134
                is_valid = true;
 
135
            }
 
136
        }
 
137
 
 
138
        // Don't let the user open invalid and mailto links, it's not
 
139
        // terribly useful
 
140
        this.open.set_sensitive(is_nominal && !is_mailto);
 
141
 
 
142
        Gtk.StyleContext style = this.url.get_style_context();
 
143
        Gtk.EntryIconPosition pos = Gtk.EntryIconPosition.SECONDARY;
 
144
        if (!is_valid) {
 
145
            style.add_class(Gtk.STYLE_CLASS_ERROR);
 
146
            style.remove_class(Gtk.STYLE_CLASS_WARNING);
 
147
            this.url.set_icon_from_icon_name(pos, "dialog-error-symbolic");
 
148
            this.url.set_tooltip_text(
 
149
                _("Link URL is not correctly formatted, e.g. http://example.com")
 
150
            );
 
151
        } else if (!is_nominal) {
 
152
            style.remove_class(Gtk.STYLE_CLASS_ERROR);
 
153
            style.add_class(Gtk.STYLE_CLASS_WARNING);
 
154
            this.url.set_icon_from_icon_name(pos, "dialog-warning-symbolic");
 
155
            this.url.set_tooltip_text(
 
156
                !is_mailto ? _("Invalid link URL") : _("Invalid email address")
 
157
            );
 
158
        } else {
 
159
            style.remove_class(Gtk.STYLE_CLASS_ERROR);
 
160
            style.remove_class(Gtk.STYLE_CLASS_WARNING);
 
161
            this.url.set_icon_from_icon_name(pos, null);
 
162
            this.url.set_tooltip_text("");
 
163
        }
 
164
 
 
165
        link_changed(url, is_valid && is_nominal);
 
166
    }
 
167
 
 
168
    [GtkCallback]
 
169
    private void on_url_changed() {
 
170
        this.validation_timeout.start();
 
171
    }
 
172
 
 
173
    [GtkCallback]
 
174
    private void on_activate_popover() {
 
175
        link_activate();
 
176
        popdown();
 
177
    }
 
178
 
 
179
    [GtkCallback]
 
180
    private void on_delete_clicked() {
 
181
        link_delete();
 
182
        popdown();
 
183
    }
 
184
 
 
185
    [GtkCallback]
 
186
    private void on_open_clicked() {
 
187
        link_open();
 
188
    }
 
189
 
 
190
}