[Xfce4-commits] <postler:master> Render recipients as buttons in composer

Christian Dywan noreply at xfce.org
Sun Feb 20 21:52:01 CET 2011


Updating branch refs/heads/master
         to 95828e752aaf542ca5f941b4d0b44d25cfb42b89 (commit)
       from b46fd7a0a69a2fb82dfcbc292645f14217ce818f (commit)

commit 95828e752aaf542ca5f941b4d0b44d25cfb42b89
Author: Christian Dywan <christian at twotoasts.de>
Date:   Sun Feb 20 21:47:38 2011 +0100

    Render recipients as buttons in composer
    
    The new class FlowBox is a wrapping horizontal box which
    lays widgets out over multiple lines.
    
    The new class RecipientEntry based on FlowBox is an entry
    with autcompletion that renders valid addresses as buttons
    with mouse and keyboard support.
    
    Fixes: https://bugs.launchpad.net/postler/+bug/686721

 postler/postler-composer.vala       |   71 +++------------
 postler/postler-flowbox.vala        |  102 +++++++++++++++++++++
 postler/postler-recipiententry.vala |  168 +++++++++++++++++++++++++++++++++++
 3 files changed, 282 insertions(+), 59 deletions(-)

diff --git a/postler/postler-composer.vala b/postler/postler-composer.vala
index 627e845..c240a7e 100644
--- a/postler/postler-composer.vala
+++ b/postler/postler-composer.vala
@@ -23,8 +23,8 @@ public class Postler.Composer : Gtk.Window {
     Postler.Content content;
     Postler.Attachments attachments;
     Gtk.ComboBox combo_from;
-    Gtk.Entry entry_to;
-    Gtk.Entry entry_copy;
+    Postler.RecipientEntry entry_to;
+    Postler.RecipientEntry entry_copy;
     Gtk.Entry entry_subject;
     Gtk.Button button_send;
     uint grace_period_timer = 0;
@@ -449,52 +449,6 @@ public class Postler.Composer : Gtk.Window {
           N_("Mark message as important"), null, false }
     };
 
-    Gtk.Entry entry_with_address_completion () {
-        var entry = new Gtk.Entry ();
-        var completion = new Gtk.EntryCompletion ();
-        completion.model = new Gtk.ListStore (1, typeof (string));
-        completion.text_column = 0;
-        completion.set_match_func ((completion, key, iter) => {
-            var model = entry.get_completion ().model as Gtk.ListStore;
-            string? contact;
-            model.get (iter, 0, out contact);
-            if (contact == null)
-                return false;
-            string? normalized = contact.normalize (-1, NormalizeMode.ALL);
-            if (normalized != null) {
-                string? casefolded = normalized.casefold (-1);
-                if (casefolded != null)
-                    return key in casefolded;
-            }
-            return false;
-        });
-        completion.inline_completion = true;
-        completion.inline_selection = true;
-        entry.set_completion (completion);
-        entry.changed.connect ((editable) => {
-            if (entry.text.length < 3)
-                return;
-            var model = entry.get_completion ().model as Gtk.ListStore;
-            if (model.iter_n_children (null) > 0)
-                return;
-            string[] contacts = dexter.autocomplete_contact (entry.text);
-            foreach (string contact in contacts) {
-                Gtk.TreeIter iter;
-                model.append (out iter);
-                model.set (iter, 0, contact);
-            }
-        });
-        entry.backspace.connect ((entry) => {
-            var model = entry.get_completion ().model as Gtk.ListStore;
-            model.clear ();
-        });
-        entry.delete_from_cursor.connect ((entry, type, count) => {
-            var model = entry.get_completion ().model as Gtk.ListStore;
-            model.clear ();
-        });
-        return entry;
-    }
-
     public Composer () {
         GLib.Object (icon_name: STOCK_MAIL_MESSAGE_NEW,
                      title: _("Compose message"));
@@ -620,16 +574,16 @@ public class Postler.Composer : Gtk.Window {
         label.xalign = 1.0f;
         label_and_arrow.pack_start (label, false, false, 4);
         sizegroup.add_widget (label_and_arrow);
-        entry_to = entry_with_address_completion ();
-        box.pack_start (entry_to, true, true, 12);
+        entry_to = new Postler.RecipientEntry ();
+        box.pack_end (entry_to, true, true, 12);
         box = new Gtk.HBox (false, 0);
         shelf.pack_start (box, false, false, 4);
         label = new Gtk.Label.with_mnemonic (_("_Copy:"));
         label.xalign = 1.0f;
         box.pack_start (label, false, false, 4);
         sizegroup.add_widget (label);
-        entry_copy = entry_with_address_completion ();
-        box.pack_start (entry_copy, true, true, 12);
+        entry_copy = new Postler.RecipientEntry ();
+        box.pack_end (entry_copy, true, true, 12);
         label.show ();
         entry_copy.show ();
         box.set_no_show_all (true);
@@ -659,9 +613,8 @@ public class Postler.Composer : Gtk.Window {
         attachments.drag_data_received.connect (drag_received);
 
         actions.get_action ("MessageSend").sensitive = false;
-        entry_to.notify["text"].connect ((object, pspec) => {
-            bool state = entry_to.text.strip ().chr (-1, '@') != null;
-            actions.get_action ("MessageSend").sensitive = state;
+        entry_to.notify["empty"].connect ((object, pspec) => {
+            actions.get_action ("MessageSend").sensitive = !entry_to.empty;
         });
 
         foreach (var account_info in accounts.get_infos ()) {
@@ -739,9 +692,9 @@ public class Postler.Composer : Gtk.Window {
             return true;
         }
         else if (name == "to")
-            entry = entry_to;
+            entry = entry_to.entry;
         else if (name == "cc") {
-            entry = entry_copy;
+            entry = entry_copy.entry;
             entry_copy.parent.show ();
         }
         else if (name == "subject")
@@ -767,8 +720,8 @@ public class Postler.Composer : Gtk.Window {
         else
             return false;
 
-        string current = entry.get_text ();
-        entry.set_text (current != "" ? current + "," + data : data);
+        string current = entry.get_text () ?? "";
+        entry.set_text (current + data + ",");
         return true;
     }
 }
diff --git a/postler/postler-flowbox.vala b/postler/postler-flowbox.vala
new file mode 100644
index 0000000..5b5e119
--- /dev/null
+++ b/postler/postler-flowbox.vala
@@ -0,0 +1,102 @@
+/*
+ Copyright (C) 2011 Christian Dywan <christian at twotoasts.de>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+namespace Postler {
+    public class FlowBox : Gtk.Container {
+        List<Gtk.Widget> children;
+        int last_row_count;
+        int last_row_height;
+
+        public FlowBox () {
+            set_has_window (false);
+        }
+
+        override void add (Gtk.Widget widget) {
+            children.append (widget);
+            widget.set_parent (this);
+            if (get_realized ())
+                widget.realize ();
+        }
+
+        override void remove (Gtk.Widget widget) {
+            children.remove (widget);
+            widget.unparent ();
+            if (widget.get_realized ())
+                widget.unrealize ();
+            queue_resize ();
+        }
+
+        override void forall_internal (bool internal, Gtk.Callback callback) {
+            foreach (var child in children)
+                callback (child);
+        }
+
+        public void reorder_child (Gtk.Widget widget, int position) {
+            children.remove (widget);
+            children.insert (widget, position);
+        }
+
+        public override void map () {
+            set_mapped (true);
+            foreach (var child in children) {
+                if (child.visible && !child.get_mapped ())
+                    child.map ();
+            }
+        }
+
+        public override void size_allocate (Gdk.Rectangle allocation) {
+            int width = 0;
+            int row_count = 1;
+            int row_height = 1;
+
+            foreach (var child in children) {
+                if (child.visible) {
+                    Gtk.Requisition child_size;
+                    child.size_request (out child_size);
+                    width += child_size.width;
+
+                    if (width > allocation.width && width != child_size.width) {
+                        row_count++;
+                        width = child_size.width;
+                    }
+                    row_height = int.max (row_height, child_size.height);
+                }
+            }
+
+            if (last_row_count != row_count || last_row_height != row_height) {
+                last_row_count = row_count;
+                last_row_height = row_height;
+                set_size_request (-1, row_height * row_count);
+            }
+
+            width = 0;
+            int row = 1;
+            foreach (var child in children) {
+                if (child.visible) {
+                    Gtk.Requisition child_size;
+                    child.size_request (out child_size);
+                    width += child_size.width;
+                    if (width > allocation.width && width != child_size.width) {
+                        row++;
+                        width = child_size.width;
+                    }
+
+                    var child_allocation = new Gdk.Rectangle ();
+                    child_allocation.width = child_size.width;
+                    child_allocation.height = row_height;
+                    child_allocation.x = allocation.x + width - child_size.width;
+                    child_allocation.y = allocation.y + row_height * (row - 1);
+                    child.size_allocate (child_allocation);
+                }
+            }
+        }
+    }
+}
diff --git a/postler/postler-recipiententry.vala b/postler/postler-recipiententry.vala
new file mode 100644
index 0000000..5ef408e
--- /dev/null
+++ b/postler/postler-recipiententry.vala
@@ -0,0 +1,168 @@
+/*
+ Copyright (C) 2011 Christian Dywan <christian at twotoasts.de>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+namespace Postler {
+    public class RecipientEntry : FlowBox {
+        Dexter.Dexter dexter = new Dexter.Dexter ();
+
+        public Gtk.Entry entry;
+        bool invalidated = true;
+        string real_text;
+        public string text { get { return get_real_text (); } }
+        public bool empty { get; private set; default = true; }
+
+        unowned string get_real_text () {
+            if (invalidated) {
+                real_text = "";
+                foreach (var button in get_children ())
+                    real_text += "," + button.tooltip_text;
+                real_text += entry.text;
+            }
+            return real_text;
+        }
+
+        public RecipientEntry () {
+            entry = new Gtk.Entry ();
+            entry.show ();
+            add (entry);
+            var completion = new Gtk.EntryCompletion ();
+            completion.model = new Gtk.ListStore (1, typeof (string));
+            completion.text_column = 0;
+            completion.set_match_func (match_function);
+            completion.match_selected.connect (match_selected);
+            completion.inline_completion = true;
+            completion.inline_selection = true;
+            entry.set_completion (completion);
+            entry.changed.connect (changed);
+            entry.focus_out_event.connect ((widget, event) => {
+                buttonize_text ();
+                return false;
+            });
+            entry.backspace.connect ((entry) => {
+                var model = entry.get_completion ().model as Gtk.ListStore;
+                model.clear ();
+
+                if (entry.get_position () == 0 && get_children ().nth_data (1) != null) {
+                    var last_button = get_children ().last ().prev.data;
+                    string address = last_button.get_tooltip_text ();
+                    last_button.destroy ();
+                    entry.text = entry.text + (entry.text != "" ? "," : "") + address;
+                }
+            });
+            entry.delete_from_cursor.connect ((entry, type, count) => {
+                var model = entry.get_completion ().model as Gtk.ListStore;
+                model.clear ();
+            });
+        }
+
+        bool match_function (Gtk.EntryCompletion completion, string key,
+            Gtk.TreeIter iter) {
+
+            var model = completion.model as Gtk.ListStore;
+            string? contact;
+            model.get (iter, 0, out contact);
+            if (contact == null)
+                return false;
+            string? normalized = contact.normalize (-1, NormalizeMode.ALL);
+            if (normalized != null) {
+                string? casefolded = normalized.casefold (-1);
+                if (casefolded != null)
+                    return key in casefolded;
+            }
+            return false;
+        }
+
+        void add_address (string address) {
+            if (address == "")
+                return;
+            /* Don't turn invalid addresses into buttons */
+            if (!address.contains ("@") || address.has_suffix ("@")) {
+                entry.text = entry.text + (entry.text != "" ? "," : "") + address;
+                return;
+            }
+
+            string[] parsed = Postler.Messages.parse_address (address);
+            var box = new Gtk.HBox (false, 0);
+            var label = new Gtk.Label (parsed[0]);
+            box.pack_start (label, true, false, 0);
+            var icon = new Gtk.Image.from_stock (Gtk.STOCK_CLOSE, Gtk.IconSize.MENU);
+            box.pack_end (icon, false, false, 0);
+            var button = new Gtk.Button ();
+            button.add (box);
+            button.set_tooltip_text (address);
+            button.clicked.connect ((button) => {
+                button.destroy ();
+            });
+            button.key_press_event.connect ((button, event) => {
+                if (event.keyval == Gdk.keyval_from_name ("Delete"))
+                    button.destroy ();
+                else if (event.keyval == Gdk.keyval_from_name ("BackSpace")) {
+                    entry.text = "," + button.tooltip_text;
+                    button.destroy ();
+                }
+                return false;
+            });
+            button.destroy.connect ((button) => {
+                invalidated = true;
+                entry.grab_focus ();
+                if (get_children ().nth_data (1) == null)
+                    empty = true;
+            });
+            add (button);
+            reorder_child (entry, -1);
+            button.show_all ();
+            empty = false;
+        }
+
+        bool match_selected (Gtk.EntryCompletion completion, Gtk.TreeModel model,
+            Gtk.TreeIter iter) {
+
+            string address;
+            model.get (iter, completion.text_column, out address);
+            
+            entry.text = "";
+            add_address (address);
+            entry.grab_focus ();
+            return true;
+        }
+
+        void buttonize_text () {
+            if (entry.text.chr (-1, '@') != null) {
+                string[] addresses = entry.text.split (",");
+                entry.text = "";
+                foreach (string address in addresses)
+                    add_address (address);
+            }
+        }
+
+        void changed (Gtk.Editable editable) {
+            invalidated = true;
+            /* Turn proper address into button when appending a comma */
+            if (entry.text.has_suffix (",")) {
+                buttonize_text ();
+                return;
+            }
+
+            if (entry.text.length < 3)
+                return;
+            var model = entry.get_completion ().model as Gtk.ListStore;
+            if (model.iter_n_children (null) > 0)
+                return;
+            string[] contacts = dexter.autocomplete_contact (entry.text);
+            foreach (string contact in contacts) {
+                Gtk.TreeIter iter;
+                model.append (out iter);
+                model.set (iter, 0, contact);
+            }
+        }
+    }
+}
+



More information about the Xfce4-commits mailing list