[gnome-contacts: 4/8] Use Egg.ListBox for Contacts.View, not TreeView



commit 2432efa967b555bd297f203cd48b99f0753b3a57
Author: Alexander Larsson <alexl redhat com>
Date:   Mon Feb 20 01:11:49 2012 +0100

    Use Egg.ListBox for Contacts.View, not TreeView
    
    This lets us do e.g. separators, and we can drop a bunch of custom cell renderers
    and general fighting with TreeView

 src/Makefile.am               |    1 +
 src/contacts-link-dialog.vala |    3 +-
 src/contacts-list-pane.vala   |    4 +-
 src/contacts-view.vala        |  553 +++++++++++++----------------------------
 4 files changed, 173 insertions(+), 388 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 3fc3dfd..bea426a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,6 +22,7 @@ endif
 bin_PROGRAMS = gnome-contacts
 
 vala_sources = \
+	listbox/egg-list-box.vala \
 	contacts-app.vala \
 	contacts-cell-renderer-shape.vala \
 	contacts-contact.vala \
diff --git a/src/contacts-link-dialog.vala b/src/contacts-link-dialog.vala
index 03a61d4..7024168 100644
--- a/src/contacts-link-dialog.vala
+++ b/src/contacts-link-dialog.vala
@@ -220,8 +220,9 @@ public class Contacts.LinkDialog : Dialog {
     scrolled.set_vexpand (true);
     scrolled.set_hexpand (true);
     scrolled.set_shadow_type (ShadowType.NONE);
-    scrolled.add (view);
+    scrolled.add_with_viewport (view);
     list_grid.add (scrolled);
+    view.set_focus_vadjustment (scrolled.get_vadjustment ());
 
     view.selection_changed.connect ( (c) => {
 	selected_contact = c;
diff --git a/src/contacts-list-pane.vala b/src/contacts-list-pane.vala
index 50ae1fe..a0fdcd1 100644
--- a/src/contacts-list-pane.vala
+++ b/src/contacts-list-pane.vala
@@ -138,12 +138,14 @@ public class Contacts.ListPane : Frame {
     grid.set_orientation (Orientation.VERTICAL);
     this.add (grid);
 
+    contacts_view.set_focus_vadjustment (scrolled.get_vadjustment ());
+
     contacts_view.selection_changed.connect( (l, contact) => {
 	if (!ignore_selection_change)
 	  selection_changed (contact);
       });
 
-    scrolled.add (contacts_view);
+    scrolled.add_with_viewport (contacts_view);
     contacts_view.show_all ();
     scrolled.set_no_show_all (true);
 
diff --git a/src/contacts-view.vala b/src/contacts-view.vala
index 6f07fe0..3eec896 100644
--- a/src/contacts-view.vala
+++ b/src/contacts-view.vala
@@ -20,13 +20,16 @@ using Gtk;
 using Folks;
 using Gee;
 
-public class Contacts.View : TreeView {
+public class Contacts.View : Egg.ListBox {
   private class ContactData {
     public Contact contact;
-    public TreeIter iter;
-    public bool visible;
-    public bool is_first;
+    public Grid grid;
+    public Label label;
+    public ContactFrame image_frame;
     public int sort_prio;
+    public string display_name;
+    public unichar initial_letter;
+    public bool filtered;
   }
 
   public enum Subset {
@@ -36,46 +39,44 @@ public class Contacts.View : TreeView {
     ALL
   }
 
+  public enum TextDisplay {
+    NONE,
+    PRESENCE,
+    STORES
+  }
+
+  public signal void selection_changed (Contact? contact);
+
   Store contacts_store;
   Subset show_subset;
-  ListStore list_store;
+  HashMap<Contact,ContactData> contacts;
   HashSet<Contact> hidden_contacts;
+
   string []? filter_values;
-  int custom_visible_count;
-  ContactData suggestions_header_data;
-  ContactData padding_data;
-  ContactData other_header_data;
+  private TextDisplay text_display;
 
   public View (Store store, TextDisplay text_display = TextDisplay.PRESENCE) {
+    set_selection_mode (SelectionMode.BROWSE);
     contacts_store = store;
     hidden_contacts = new HashSet<Contact>();
     show_subset = Subset.ALL;
+    this.text_display = text_display;
 
-    list_store = new ListStore (2, typeof (Contact), typeof (ContactData *));
-    suggestions_header_data = new ContactData ();
-    suggestions_header_data.sort_prio = int.MAX;
-    padding_data = new ContactData ();
-    padding_data.sort_prio = 1;
-
-    other_header_data = new ContactData ();
-    other_header_data.sort_prio = -1;
-
-    list_store.set_sort_func (0, (model, iter_a, iter_b) => {
-	ContactData *aa, bb;
-	model.get (iter_a, 1, out aa);
-	model.get (iter_b, 1, out bb);
+    contacts = new HashMap<Contact,ContactData> ();
 
-	return compare_data (aa, bb);
+    this.set_sort_func ((widget_a, widget_b) => {
+	var a = widget_a.get_data<ContactData> ("data");
+	var b = widget_b.get_data<ContactData> ("data");
+	return compare_data (a, b);
       });
-    list_store.set_sort_column_id (0, SortType.ASCENDING);
+    this.set_filter_func (filter);
+    this.set_separator_funcs (update_separator);
 
     contacts_store.added.connect (contact_added_cb);
     contacts_store.removed.connect (contact_removed_cb);
     contacts_store.changed.connect (contact_changed_cb);
     foreach (var c in store.get_contacts ())
       contact_added_cb (store, c);
-
-      init_view (text_display);
   }
 
   private int compare_data (ContactData a_data, ContactData b_data) {
@@ -87,16 +88,13 @@ public class Contacts.View : TreeView {
     if (a_prio < b_prio)
       return 1;
 
-    var a = a_data.contact;
-    var b = b_data.contact;
-
-    if (is_set (a.display_name) && is_set (b.display_name))
-      return a.display_name.collate (b.display_name);
+    if (is_set (a_data.display_name) && is_set (b_data.display_name))
+      return a_data.display_name.collate (b_data.display_name);
 
     // Sort empty names last
-    if (is_set (a.display_name))
+    if (is_set (a_data.display_name))
       return -1;
-    if (is_set (b.display_name))
+    if (is_set (b_data.display_name))
       return 1;
 
     return 0;
@@ -111,425 +109,208 @@ public class Contacts.View : TreeView {
   }
 
   /* The hardcoded prio if set, otherwise 0 for the
-     main/combined group, or -2 for the separated other group */
+     main/combined group, or -1 for the separated other group */
   private int get_sort_prio (ContactData *data) {
     if (data->sort_prio != 0)
       return data->sort_prio;
 
     if (is_other (data))
-      return -2;
+      return -1;
     return 0;
   }
 
-  public string get_header_text (TreeIter iter) {
-    ContactData *data;
-    list_store.get (iter, 1, out data);
-    if (data == suggestions_header_data) {
-      /* Translators: This is the header for the list of suggested contacts to
-	 link to the current contact */
-      return ngettext ("Suggestion", "Suggestions", custom_visible_count);
-    }
-    if (data == other_header_data) {
-      /* Translators: This is the header for the list of suggested contacts to
-	 link to the current contact */
-      return _("Other Contacts");
-    }
-    return "";
-  }
-
   public void set_show_subset (Subset subset) {
     show_subset = subset;
-
-    bool new_visible = show_subset == Subset.ALL_SEPARATED;
-    if (new_visible && !other_header_data.visible) {
-      other_header_data.visible = true;
-      list_store.append (out other_header_data.iter);
-      list_store.set (other_header_data.iter, 1, other_header_data);
-    }
-    if (!new_visible && other_header_data.visible) {
-      other_header_data.visible = false;
-      list_store.remove (other_header_data.iter);
-    }
-
+    update_all_filtered ();
     refilter ();
+    resort ();
   }
 
   public void set_custom_sort_prio (Contact c, int prio) {
     /* We use negative prios internally */
     assert (prio >= 0);
 
-    var data = lookup_data (c);
-
+    var data = contacts.get (c);
     if (data == null)
       return;
-
-    // We insert a priority between 0 and 1 for the padding
-    if (prio > 0)
-      prio += 1;
     data.sort_prio = prio;
-    contact_changed_cb (contacts_store, c);
-
-    if (data.visible) {
-      if (prio > 0) {
-	if (custom_visible_count++ == 0)
-	  add_custom_headers ();
-      } else {
-	if (custom_visible_count-- == 1)
-	  remove_custom_headers ();
-      }
-    }
+    child_changed (data.grid);
+  }
+
+  public void hide_contact (Contact contact) {
+    hidden_contacts.add (contact);
+    update_all_filtered ();
+    refilter ();
+  }
+
+  public void set_filter_values (string []? values) {
+    filter_values = values;
+    update_all_filtered ();
+    refilter ();
   }
 
-  private bool apply_filter (Contact contact) {
-    if (contact.is_hidden)
+  private bool calculate_filtered (Contact c) {
+    if (c.is_hidden)
       return false;
 
-    if (contact in hidden_contacts)
+    if (c in hidden_contacts)
       return false;
 
     if ((show_subset == Subset.MAIN &&
-	 !contact.is_main) ||
+	 !c.is_main) ||
 	(show_subset == Subset.OTHER &&
-	 contact.is_main))
+	 c.is_main))
       return false;
 
     if (filter_values == null || filter_values.length == 0)
       return true;
 
-    return contact.contains_strings (filter_values);
-  }
-
-  public bool is_first (TreeIter iter) {
-    ContactData *data;
-    list_store.get (iter, 1, out data);
-    if (data != null)
-      return data->is_first;
-    return false;
-  }
-
-  private ContactData? get_previous (ContactData data) {
-    ContactData *previous = null;
-    TreeIter iter = data.iter;
-    if (list_store.iter_previous (ref iter))
-      list_store.get (iter, 1, out previous);
-    return previous;
-  }
-
-  private ContactData? get_next (ContactData data) {
-    ContactData *next = null;
-    TreeIter iter = data.iter;
-    if (list_store.iter_next (ref iter))
-      list_store.get (iter, 1, out next);
-    return next;
-  }
-
-  private void row_changed_no_resort (ContactData data) {
-    var path = list_store.get_path (data.iter);
-    list_store.row_changed (path, data.iter);
-  }
-
-  private void row_changed_resort (ContactData data) {
-    list_store.set (data.iter, 0, data.contact);
-  }
-
-  private bool update_is_first (ContactData data, ContactData? previous) {
-    bool old_is_first = data.is_first;
-
-    bool is_custom = data.sort_prio != 0;
-    bool previous_is_custom = previous != null && (previous.sort_prio != 0) ;
-
-    if (is_custom) {
-      data.is_first = false;
-    } else if (previous != null && !previous_is_custom) {
-      unichar previous_initial = previous.contact.initial_letter;
-      unichar initial = data.contact.initial_letter;
-      data.is_first = previous_initial != initial;
-    } else {
-      data.is_first = true;
-    }
-
-    if (old_is_first != data.is_first) {
-      row_changed_no_resort (data);
-      return true;
-    }
-
-    return false;
+    return c.contains_strings (filter_values);
   }
 
-  private void add_custom_headers () {
-    suggestions_header_data.visible = true;
-    list_store.append (out suggestions_header_data.iter);
-    list_store.set (suggestions_header_data.iter, 1, suggestions_header_data);
-    padding_data.visible = true;
-    list_store.append (out padding_data.iter);
-    list_store.set (padding_data.iter, 1, padding_data);
-  }
+  private void update_data (ContactData data) {
+    var c = data.contact;
+    data.display_name = c.display_name;
+    data.initial_letter = c.initial_letter;
+    data.filtered = calculate_filtered (c);
 
-  private void remove_custom_headers () {
-    suggestions_header_data.visible = false;
-    list_store.remove (suggestions_header_data.iter);
-    padding_data.visible = false;
-    list_store.remove (padding_data.iter);
+    data.label.set_markup ("<span font='16px'>" + data.display_name + "</span>");
+    data.image_frame.set_image (c.individual, c);
   }
 
-  private void add_to_model (ContactData data) {
-    list_store.append (out data.iter);
-    list_store.set (data.iter, 0, data.contact, 1, data);
-
-    if (data.sort_prio > 0) {
-      if (custom_visible_count++ == 0)
-	add_custom_headers ();
-    }
-
-    if (update_is_first (data, get_previous (data)) && data.is_first) {
-      /* The newly added row is first, the next one might not be anymore */
-      var next = get_next (data);
-      if (next != null)
-	update_is_first (next, data);
-    }
-  }
-
-  private void remove_from_model (ContactData data) {
-    if (data.sort_prio > 0) {
-      if (custom_visible_count-- == 1)
-	remove_custom_headers ();
+  private void update_all_filtered () {
+    foreach (var data in contacts.values) {
+      data.filtered = calculate_filtered (data.contact);
     }
-
-    ContactData? next = null;
-    if (data.is_first)
-      next = get_next (data);
-
-    list_store.remove (data.iter);
-    data.is_first = false;
-
-    if (next != null)
-      update_is_first (next, get_previous (next));
   }
 
-  private void update_visible (ContactData data) {
-    bool was_visible = data.visible;
-    data.visible = apply_filter (data.contact);
-
-    if (was_visible && !data.visible)
-      remove_from_model (data);
-
-    if (!was_visible && data.visible)
-      add_to_model (data);
+  private void contact_changed_cb (Store store, Contact c) {
+    var data = contacts.get (c);
+    update_data (data);
+    child_changed (data.grid);
   }
 
-  private void refilter () {
-    foreach (var c in contacts_store.get_contacts ()) {
-      update_visible (lookup_data (c));
+  private void contact_added_cb (Store store, Contact c) {
+    var data =  new ContactData();
+    data.contact = c;
+    data.grid = new Grid ();
+    data.grid.margin = 12;
+    data.grid.set_column_spacing (10);
+    data.image_frame = new ContactFrame (Contact.SMALL_AVATAR_SIZE);
+    data.label = new Label ("");
+    data.label.set_ellipsize (Pango.EllipsizeMode.END);
+    data.label.set_valign (Align.START);
+    data.label.set_halign (Align.START);
+
+    data.grid.attach (data.image_frame, 0, 0, 1, 2);
+    data.grid.attach (data.label, 1, 0, 1, 1);
+
+    if (text_display == TextDisplay.PRESENCE) {
+      var merged_presence = c.create_merged_presence_widget ();
+      merged_presence.set_halign (Align.START);
+      merged_presence.set_valign (Align.END);
+      merged_presence.set_vexpand (false);
+      merged_presence.set_margin_bottom (4);
+
+      data.grid.attach (merged_presence,  1, 1, 1, 1);
     }
-  }
-
-  public void hide_contact (Contact contact) {
-    hidden_contacts.add (contact);
-    refilter ();
-  }
-
-  public void set_filter_values (string []? values) {
-    filter_values = values;
-    refilter ();
-  }
 
-  private void contact_changed_cb (Store store, Contact c) {
-    ContactData data = lookup_data (c);
-
-    bool was_visible = data.visible;
-
-    ContactData? next = null;
-    if (data.visible)
-      next = get_next (data);
-
-    update_visible (data);
-
-    if (was_visible && data.visible) {
-      /* We just moved position in the list while visible */
+    if (text_display == TextDisplay.STORES) {
+      var stores = new Label ("");
+      stores.set_markup (Markup.printf_escaped ("<span font='12px'>%s</span>",
+						c.format_persona_stores ()));
 
-      row_changed_resort (data);
-
-      /* Update the is_first on the previous next row */
-      if (next != null)
-	update_is_first (next, get_previous (next));
-
-      /* Update the is_first on the new next row */
-      next = get_next (data);
-      if (next != null)
-	update_is_first (next, data);
+      stores.set_ellipsize (Pango.EllipsizeMode.END);
+      stores.set_halign (Align.START);
+      data.grid.attach (stores,  1, 1, 1, 1);
     }
-  }
 
-  private ContactData lookup_data (Contact c) {
-    return c.lookup<ContactData> (this);
-  }
+    update_data (data);
 
-  private void contact_added_cb (Store store, Contact c) {
-    ContactData data =  new ContactData();
-    data.contact = c;
-    data.visible = false;
-
-    c.set_lookup (this, data);
-
-    update_visible (data);
+    data.grid.set_data<ContactData> ("data", data);
+    data.grid.show_all ();
+    contacts.set (c, data);
+    this.add (data.grid);
   }
 
   private void contact_removed_cb (Store store, Contact c) {
-    var data = lookup_data (c);
-
-    if (data.visible)
-      remove_from_model (data);
-
-    c.remove_lookup<ContactData> (this);
+    var data = contacts.get (c);
+    data.grid.destroy ();
+    data.label.destroy ();
+    data.image_frame.destroy ();
+    contacts.unset (c);
   }
 
-  public bool lookup_iter (Contact c, out TreeIter iter) {
-    var data = lookup_data (c);
-    iter = data.iter;
-    return data.visible;
+  public override void child_selected (Widget? child) {
+    var data = child.get_data<ContactData> ("data");
+    var contact = data != null ? data.contact : null;
+    selection_changed (contact);
+    if (contact != null)
+      contact.fetch_contact_info ();
   }
 
+  private bool filter (Widget child) {
+    var data = child.get_data<ContactData> ("data");
 
-
-  private CellRendererShape shape;
-  public enum TextDisplay {
-    NONE,
-    PRESENCE,
-    STORES
+    return data.filtered;
   }
-  private TextDisplay text_display;
-
-  public signal void selection_changed (Contact? contact);
-
-  private void init_view (TextDisplay text_display) {
-    this.text_display = text_display;
-
-    set_model (list_store);
-    set_headers_visible (false);
-
-    var row_padding = 12;
-
-    var selection = get_selection ();
-    selection.set_mode (SelectionMode.BROWSE);
-    selection.set_select_function ( (selection, model, path, path_currently_selected) => {
-	Contact contact;
-	TreeIter iter;
-	model.get_iter (out iter, path);
-	model.get (iter, 0, out contact);
-	return contact != null;
-      });
-    selection.changed.connect (contacts_selection_changed);
-
-    var column = new TreeViewColumn ();
-    column.set_spacing (8);
-
-    var icon = new CellRendererPixbuf ();
-    icon.set_padding (0, row_padding);
-    icon.xalign = 1.0f;
-    icon.yalign = 0.0f;
-    icon.width = Contact.SMALL_AVATAR_SIZE + 12;
-    column.pack_start (icon, false);
-    column.set_cell_data_func (icon, (column, cell, model, iter) => {
-	Contact contact;
-
-	model.get (iter, 0, out contact);
-
-	if (contact == null) {
-	  cell.set ("pixbuf", null);
-	  cell.visible = false;
-	  return;
-	}
-	cell.visible = true;
-
-	if (contact != null)
-	  cell.set ("pixbuf", contact.small_avatar);
-	else
-	  cell.set ("pixbuf", null);
-      });
-
-    shape = new CellRendererShape ();
-    shape.set_padding (0, row_padding);
-
-    Pango.cairo_context_set_shape_renderer (get_pango_context (), shape.render_shape);
-
-    column.pack_start (shape, true);
-    column.set_cell_data_func (shape, (column, cell, model, iter) => {
-	Contact contact;
-
-	model.get (iter, 0, out contact);
-
-	if (contact == null) {
-	  cell.visible = false;
-	  return;
-	}
-	cell.visible = true;
-
-	var name = contact.display_name;
-	switch (text_display) {
-	default:
-	case TextDisplay.NONE:
-	  cell.set ("name", name,
-		    "show_presence", false,
-		    "message", "");
-	  break;
-	case TextDisplay.PRESENCE:
-	  cell.set ("name", name,
-		    "show_presence", true,
-		    "presence", contact.presence_type,
-		    "message", contact.presence_message,
-		    "is_phone", contact.is_phone);
-	  break;
-	case TextDisplay.STORES:
-	  string stores = contact.format_persona_stores ();
-	  cell.set ("name", name,
-		    "show_presence", false,
-		    "message", stores);
-	  break;
-	}
-      });
 
-    var text = new CellRendererText ();
-    text.set_alignment (0, 0);
-    column.pack_start (text, true);
-    text.set ("weight", Pango.Weight.BOLD);
-    column.set_cell_data_func (text, (column, cell, model, iter) => {
-	Contact contact;
-
-	model.get (iter, 0, out contact);
-	cell.visible = contact == null;
-	if (cell.visible) {
-	  string header = get_header_text (iter);
-	  cell.set ("text", header);
-	  if (header == "")
-	    cell.height = 6; // PADDING
-	  else
-	    cell.height = -1;
-	}
-      });
+  private void update_separator (ref Widget? separator,
+				 Widget widget,
+				 Widget? before_widget) {
+    var w_data = widget.get_data<ContactData> ("data");
+    ContactData? before_data = null;
+    if (before_widget != null)
+      before_data = before_widget.get_data<ContactData> ("data");
 
-    append_column (column);
-  }
+    if (before_data == null && w_data.sort_prio > 0) {
+      if (separator == null ||
+	  !(separator.get_data<bool> ("contacts-suggestions-header"))) {
+	var l = new Label ("");
+	l.set_data ("contacts-suggestions-header", true);
+	l.set_markup (Markup.printf_escaped ("<b>%s</b>", _("Suggestions")));
+	l.set_halign (Align.START);
+	separator = l;
+      }
+      return;
+    }
 
-  private void contacts_selection_changed (TreeSelection selection) {
-    TreeIter iter;
-    TreeModel model;
+    if (before_data != null && before_data.sort_prio > 0 &&
+	w_data.sort_prio == 0) {
+      if (separator == null ||
+	  !(separator.get_data<bool> ("contacts-rest-header"))) {
+	var l = new Label ("");
+	l.set_data ("contacts-rest-header", true);
+	l.set_halign (Align.START);
+	separator = l;
+      }
+      return;
+    }
 
-    Contact? contact = null;
-    if (selection.get_selected (out model, out iter)) {
-      model.get (iter, 0, out contact);
+    if (is_other (w_data) &&
+	(before_data == null || !is_other (before_data))) {
+      if (separator == null ||
+	  !(separator.get_data<bool> ("contacts-other-header"))) {
+	var l = new Label ("");
+	l.set_data ("contacts-other-header", true);
+	l.set_markup (Markup.printf_escaped ("<b>%s</b>", _("Other Contacts")));
+	l.set_halign (Align.START);
+	separator = l;
+      }
+      return;
     }
 
-    selection_changed (contact);
-    if (contact != null)
-      contact.fetch_contact_info ();
+    if (before_data != null &&
+	w_data.initial_letter != before_data.initial_letter) {
+      if (separator == null || !(separator is Separator))
+	separator = new Separator (Orientation.HORIZONTAL);
+      return;
+    }
+    separator = null;
   }
 
   public void select_contact (Contact contact) {
-    TreeIter iter;
-    if (lookup_iter (contact, out iter)) {
-      get_selection ().select_iter (iter);
-      scroll_to_cell (list_store.get_path (iter),
-		      null, true, 0.0f, 0.0f);
-    }
+    var data = contacts.get (contact);
+    select_child (data.grid);
   }
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]