[gnome-contacts] Initial work on the new content-pane design



commit 53c3fe198a1794cb402775768c21067e7c87bb83
Author: Erick PÃrez Castellanos <erick red gmail com>
Date:   Sun Dec 9 18:49:19 2012 -0500

    Initial work on the new content-pane design
    
    Still needed a lot of cleaning in this code.
    But it's kinda working for now.

 src/contacts-contact-pane.vala | 2151 ++++------------------------------------
 1 files changed, 189 insertions(+), 1962 deletions(-)
---
diff --git a/src/contacts-contact-pane.vala b/src/contacts-contact-pane.vala
index c4bae4f..8209931 100644
--- a/src/contacts-contact-pane.vala
+++ b/src/contacts-contact-pane.vala
@@ -20,1483 +20,149 @@ using Gtk;
 using Folks;
 using Gee;
 
-public class Contacts.FieldRow : Contacts.Row {
-  Clickable clickable;
-  int start;
-  bool has_child_focus;
-  ContactPane pane;
-
-  /* show_as_editable means we prelight, can_focus, show selected, etc.
-     It doesn't mean we can't edit the row. For instance the
-     Card row is editing when we're editing the full name, but
-     thats not represented in the UI as editing the row. */
-  protected bool show_as_editable;
-  protected bool is_editing;
-
-  public FieldRow(RowGroup group, ContactPane pane) {
-    base (group);
-    this.pane = pane;
-    set_redraw_on_allocate (true); // Since we draw the focus rect
-
-    this.button_press_event.connect ( (ev) => {
-	if (!is_editing)
-	  this.pane.exit_edit_mode (true);
-	return false;
-      });
-
-    clickable = new Clickable (this);
-    clickable.set_focus_on_click (true);
-    clickable.clicked.connect ( () => { this.clicked (); } );
-    start = 0;
-
-    /* This should really be in class construct, but that doesn't seem to work... */
-    activate_signal = GLib.Signal.lookup ("activate-row", typeof (FieldRow));
-  }
-
-  public void set_editing (bool val) {
-    is_editing = val;
-  }
-
-  public void reset () {
-    start = 0;
-  }
-
-  public signal void clicked ();
-
-  public override bool focus (DirectionType direction) {
-    var row_can_focus = get_can_focus ();
-
-    /* Non-focusable rows get the standard behvaiour */
-    if (!row_can_focus)
-      return base.focus (direction);
-
-    /* Focusable rows have to also support focusable children,
-       which is not supported by Container.focus(), so we
-       work around that. */
-
-    bool res = false;
-
-    bool recurse_into = false;
-    if (has_focus) {
-      switch (direction) {
-      case DirectionType.RIGHT:
-      case DirectionType.TAB_FORWARD:
-	recurse_into = true;
-	break;
-      }
-    } else if (this.get_focus_child () != null) {
-      recurse_into = true;
-    } else {
-      switch (direction) {
-      case DirectionType.LEFT:
-      case DirectionType.TAB_BACKWARD:
-	recurse_into = true;
-	break;
-      }
-    }
-
-    if (recurse_into) {
-      set_can_focus (false);
-      res = base.focus (direction);
-      set_can_focus (true);
-
-      if (!res && !has_focus) {
-	switch (direction) {
-	case DirectionType.LEFT:
-	case DirectionType.TAB_BACKWARD:
-	  this.grab_focus ();
-	  res = true;
-	  break;
-	}
-      }
-    } else {
-      if (!has_focus) {
-	this.grab_focus ();
-	res = true;
-      }
-    }
-
-    return res;
-  }
-
-  [CCode (action_signal = true)]
-  public virtual signal void activate_row () {
-    clickable.activate ();
-  }
-
-  public override void realize () {
-    base.realize ();
-    clickable.realize_for (event_window);
-  }
-
-  public override void unrealize () {
-    base.unrealize ();
-    clickable.unrealize ();
-  }
-
-  public override bool draw (Cairo.Context cr) {
-    Allocation allocation;
-    this.get_allocation (out allocation);
-
-    var context = this.get_style_context ();
-
-    context.save ();
-    StateFlags state = 0;
-    if (show_as_editable) {
-      state = clickable.state & (StateFlags.ACTIVE | StateFlags.PRELIGHT);
-      if (is_editing)
-	state |= StateFlags.SELECTED;
-    }
-    context.set_state (state);
-    if (state != 0)
-      context.render_background (cr,
-				 0, 0, allocation.width, allocation.height);
-
-    if (this.has_visible_focus ())
-      context.render_focus (cr, 0, 0, allocation.width, allocation.height);
-
-    context.restore ();
-
-    base.draw (cr);
-
-    return true;
-  }
-
-  public override void parent_set (Widget? old_parent) {
-    if (old_parent != null) {
-      var old_parent_container = (old_parent as Container);
-      old_parent_container.set_focus_child.disconnect (parent_set_focus_child);
-    }
-
-    var parent_container = (this.get_parent () as Container);
-    has_child_focus = parent_container != null && parent_container.get_focus_child () == this;
-    if (parent_container != null)
-      parent_container.set_focus_child.connect (parent_set_focus_child);
-  }
-
-  public virtual signal void lost_child_focus () {
-  }
-
-  public void parent_set_focus_child (Container container, Widget? widget) {
-    var old_has_child_focus = has_child_focus;
-    has_child_focus = widget == this;
-
-    if (old_has_child_focus && !has_child_focus) {
-	  Idle.add(() => {
-	      if (!has_child_focus)
-		lost_child_focus ();
-	      return false;
-	    });
-    }
-  }
-
-  public void pack (Widget w) {
-    this.attach (w, 1, start++);
-  }
-
-  public void pack_label (string s) {
-    var l = new Label (s);
-    l.set_halign (Align.START);
-    l.get_style_context ().add_class ("dim-label");
-    pack (l);
-  }
-
-  public void pack_header (string s) {
-    var l = new Label (s);
-    l.set_markup (
-      Markup.printf_escaped ("<span font='24px'>%s</span>", s));
-    l.set_halign (Align.START);
-    pack (l);
-  }
-
-  public Grid pack_header_in_grid (string s, out Label label) {
-    var grid = new Grid ();
-    grid.set_column_spacing (4);
-    var l = new Label (s);
-    label = l;
-    l.set_markup (
-      Markup.printf_escaped ("<span font='24px'>%s</span>", s));
-    l.set_halign (Align.START);
-    l.set_hexpand (true);
-
-    grid.set_halign (Align.FILL);
-    grid.add (l);
-
-    pack (grid);
-
-    return grid;
-  }
-
-  public Label pack_text (bool wrap = false) {
-    var l = new Label ("");
-    if (wrap) {
-      l.set_line_wrap (true);
-      l.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR);
-    } else {
-      l.set_ellipsize (Pango.EllipsizeMode.END);
-    }
-    l.set_halign (Align.START);
-    pack (l);
-    return l;
-  }
-
-  public void pack_text_detail (out Label text_label, out Label detail_label, bool wrap = false) {
-    var grid = new Grid ();
-
-    var l = new Label ("");
-    l.set_hexpand (true);
-    l.set_halign (Align.START);
-    if (wrap) {
-      l.set_line_wrap (true);
-      l.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR);
-    } else {
-      l.set_ellipsize (Pango.EllipsizeMode.END);
-    }
-    grid.add (l);
-
-    text_label = l;
-
-    l = new Label ("");
-    l.set_halign (Align.END);
-    l.get_style_context ().add_class ("dim-label");
-    detail_label = l;
-
-    grid.set_halign (Align.FILL);
-    grid.add (l);
-
-    pack (grid);
-  }
-
-  public void pack_widget_detail_combo (Widget w, AbstractFieldDetails detail, TypeSet type_set, out TypeCombo combo) {
-    var grid = new Grid ();
-    grid.set_column_spacing (16);
-
-    grid.add (w);
-
-    combo = new TypeCombo (type_set);
-    combo.set_hexpand (false);
-    combo.set_halign (Align.END);
-    combo.set_active (detail);
-
-    grid.set_halign (Align.FILL);
-    grid.add (combo);
-
-    pack (grid);
-  }
-
-
-  public void pack_entry_detail_combo (string text, AbstractFieldDetails detail, TypeSet type_set, out Entry entry, out TypeCombo combo) {
-    entry = new Entry ();
-    entry.get_style_context ().add_class ("contacts-entry");
-    entry.set_text (text);
-    entry.set_hexpand (true);
-    entry.set_halign (Align.FILL);
-
-    pack_widget_detail_combo (entry, detail, type_set, out combo);
-  }
-
-  public Entry pack_entry (string s) {
-    var e = new Entry ();
-    e.get_style_context ().add_class ("contacts-entry");
-    e.set_text (s);
-    e.set_halign (Align.FILL);
-    pack (e);
-    return e;
-  }
-
-  public void left_add (Widget widget) {
-    this.attach (widget, 0, 0);
-    widget.set_halign (Align.END);
-  }
-
-  public void right_add (Widget widget) {
-    this.attach (widget, 2, 0);
-    widget.set_halign (Align.START);
-  }
-
-  public Button pack_delete_button () {
-    var image = new Image.from_icon_name ("user-trash-symbolic", IconSize.MENU);
-    var b = new Button();
-    b.add (image);
-    right_add (b);
-    b.set_halign (Align.CENTER);
-    return b;
-  }
-
-
-  public virtual signal bool enter_edit_mode () {
-    return false;
-  }
-
-  public virtual signal void exit_edit_mode (bool save) {
-  }
-}
-
-public abstract class Contacts.FieldSet : Grid {
-  public class string label_name;
-  public class string detail_name;
-  public class string property_name;
-  public class bool is_single_value;
-
-  public PersonaSheet sheet { get; construct; }
-  public int row_nr { get; construct; }
-  public bool added;
-  public bool saving;
-  FieldRow label_row;
-  protected ArrayList<DataFieldRow> data_rows = new ArrayList<DataFieldRow>();
-
-  public abstract void populate ();
-  public abstract DataFieldRow new_field ();
-
-  construct {
-    this.set_orientation (Orientation.VERTICAL);
-
-    label_row = new FieldRow (sheet.pane.row_group, sheet.pane);
-    this.add (label_row);
-    label_row.pack_label (label_name);
-  }
-
-  public void add_to_sheet () {
-    if (!added) {
-      sheet.attach (this, 0, row_nr, 1, 1);
-      added = true;
-    }
-  }
-
-  public void remove_from_sheet () {
-    if (added) {
-      sheet.remove (this);
-      added = false;
-    }
-  }
-
-  public bool reads_param (string param) {
-    return param == property_name;
-  }
-
-  public bool is_empty () {
-    return get_children ().length () == 1;
-  }
-
-  public void clear () {
-    foreach (var row in data_rows) {
-      row.destroy ();
-    }
-    data_rows.clear ();
-  }
-
-  public void add_row (DataFieldRow row) {
-    this.add (row);
-    data_rows.add (row);
-
-    row.clicked.connect( () => {
-	sheet.pane.enter_edit_mode (row);
-      });
-
-    row.update ();
-  }
-
-  public void remove_row (DataFieldRow row) {
-    this.remove (row);
-    data_rows.remove (row);
-  }
-
-  public virtual Value? get_value () {
-    return null;
-  }
-
-  public void save () {
-    var value = get_value ();
-    if (value == null)
-      warning ("Unimplemented get_value()");
-    else {
-      saving = true;
-      Contact.set_persona_property.begin (sheet.persona, property_name, value,
-					  (obj, result) => {
-	  try {
-	    saving = false;
-	    Contact.set_persona_property.end (result);
-	  } catch (Error e2) {
-	    App.app.show_message (e2.message);
-	    refresh_from_persona ();
-	  }
-						     });
-    }
-  }
-
-  public void refresh_from_persona () {
-    this.clear ();
-    this.populate ();
-
-    if (this.is_empty ())
-      this.remove_from_sheet ();
-    else {
-      this.show_all ();
-      this.add_to_sheet ();
-    }
-  }
-}
-
-public abstract class Contacts.DataFieldRow : FieldRow {
-  public FieldSet field_set;
-  protected Button? delete_button;
-
-  public DataFieldRow (FieldSet field_set) {
-    base (field_set.sheet.pane.row_group, field_set.sheet.pane);
-    bool editable =
-      Contact.persona_has_writable_property (field_set.sheet.persona,
-					     field_set.property_name);
-    set_editable (editable);
-    this.field_set = field_set;
-  }
-
-  public void set_editable (bool editable) {
-    this.show_as_editable = editable;
-    set_can_focus (editable);
-  }
-
-  public abstract void update ();
-  public virtual void pack_edit_widgets () {
-  }
-  public virtual bool finish_edit_widgets (bool save) {
-    return false;
-  }
-
-  public override bool enter_edit_mode () {
-    if (!show_as_editable)
-      return false;
-
-    this.set_can_focus (false);
-    foreach (var w in this.get_children ()) {
-      w.hide ();
-      w.set_data ("original-widget", true);
-    }
-
-    this.reset ();
-    delete_button = this.pack_delete_button ();
-    delete_button.clicked.connect ( () => {
-	field_set.remove_row (this);
-	field_set.save ();
-      });
-
-    this.pack_edit_widgets ();
-
-    foreach (var w in this.get_children ()) {
-      if (!w.get_data<bool> ("original-widget"))
-	w.show_all ();
-    }
-
-    return true;
-  }
-
-  public override void lost_child_focus () {
-    if (field_set.sheet.pane.editing_row == this)
-      field_set.sheet.pane.exit_edit_mode (true);
-  }
-
-  public override void exit_edit_mode (bool save) {
-    if (!show_as_editable)
-      return;
-
-    var had_child_focus = this.get_focus_child () != null;
-
-    var changed = finish_edit_widgets (save);
-
-    delete_button = null;
-    foreach (var w in this.get_children ()) {
-      if (!w.get_data<bool> ("original-widget"))
-	w.destroy ();
-    }
-
-    update ();
-    this.show_all ();
-    this.set_can_focus (true);
-    if (had_child_focus)
-      this.grab_focus ();
-
-    if (save && changed)
-      field_set.save ();
-  }
-
-  public void setup_entry_for_edit (Entry entry, bool grab_focus = true) {
-    if (grab_focus) {
-      Utils.grab_widget_later (entry);
-    }
-    entry.activate.connect_after ( () => {
-	field_set.sheet.pane.exit_edit_mode (true);
-      });
-    entry.key_press_event.connect ( (key_event) => {
-	if (key_event.keyval == Gdk.Key.Escape) {
-	  field_set.sheet.pane.exit_edit_mode (false);
-	}
-	return false;
-      });
-  }
-
-  public void setup_text_view_for_edit (TextView text, bool grab_focus = true) {
-    if (grab_focus) {
-      Utils.grab_widget_later (text);
-    }
-    text.key_press_event.connect ( (key_event) => {
-	if (key_event.keyval == Gdk.Key.Escape) {
-	  field_set.sheet.pane.exit_edit_mode (false);
-	}
-	return false;
-      });
-  }
-}
-
-class Contacts.LinkFieldRow : DataFieldRow {
-  public UrlFieldDetails details;
-  Label text_label;
-  LinkButton uri_button;
-  Entry? entry;
-
-  public LinkFieldRow (FieldSet field_set, UrlFieldDetails details) {
-    base (field_set);
-    this.details = details;
-
-    text_label = this.pack_text ();
-    var image = new Image.from_icon_name ("web-browser-symbolic", IconSize.MENU);
-    image.get_style_context ().add_class ("dim-label");
-    uri_button = new LinkButton("");
-    uri_button.remove (uri_button.get_child ());
-    uri_button.set_relief (ReliefStyle.NONE);
-    uri_button.add (image);
-    this.right_add (uri_button);
-  }
-
-  public override void update () {
-    text_label.set_text (Contact.format_uri_link_text (details));
-    uri_button.set_uri (details.value);
-  }
-
-  public override void pack_edit_widgets () {
-    entry = this.pack_entry (details.value);
-    setup_entry_for_edit (entry);
-  }
-
-  public override bool finish_edit_widgets (bool save) {
-    var old_details = details;
-    var changed = entry.get_text () != details.value;
-    if (save && changed)
-      details = new UrlFieldDetails (entry.get_text (), old_details.parameters);
-    entry = null;
-    return changed;
-  }
-}
-
-class Contacts.LinkFieldSet : FieldSet {
-  class construct {
-    label_name = C_("Addresses on the Web", "Links");
-    detail_name = C_("Web address", "Link");
-    property_name = "urls";
-  }
-
-  public override void populate () {
-    var details = sheet.persona as UrlDetails;
-    if (details == null)
-      return;
-
-    var urls = details.urls;
-    foreach (var url_details in urls) {
-      var row = new LinkFieldRow (this, url_details);
-      add_row (row);
-    }
-  }
-
-  public override DataFieldRow new_field () {
-    var row = new LinkFieldRow (this, new UrlFieldDetails (""));
-    add_row (row);
-    return row;
-  }
-
-  public override Value? get_value () {
-    var details = sheet.persona as UrlDetails;
-    if (details == null)
-      return null;
-
-    var new_details = new HashSet<UrlFieldDetails>();
-    foreach (var row in data_rows) {
-      var link_row = row as LinkFieldRow;
-      new_details.add (link_row.details);
-    }
-
-    var value = Value(new_details.get_type ());
-    value.set_object (new_details);
-
-    return value;
-  }
-}
-
-class Contacts.DetailedFieldRow<T> : DataFieldRow {
-  public AbstractFieldDetails<string> _details;
-  TypeSet type_set;
-  Label text_label;
-  Label detail_label;
-  Entry? entry;
-  TypeCombo? combo;
-
-  public delegate AbstractFieldDetails<string> DataCreate(string s);
-  DataCreate data_create;
-
-  public T details { get { return (T)_details; } }
-
-  public DetailedFieldRow (FieldSet field_set, AbstractFieldDetails<string> details, TypeSet type_set, owned DataCreate data_create) {
-    base (field_set);
-    this._details = details;
-    this.type_set = type_set;
-    this.data_create = (owned) data_create;
-    this.pack_text_detail (out text_label, out detail_label);
-  }
-
-  public override void update () {
-    text_label.set_text (_details.value);
-    detail_label.set_text (type_set.format_type (_details));
-  }
-
-  public override void pack_edit_widgets () {
-    this.pack_entry_detail_combo (_details.value, _details, type_set, out entry, out combo);
-    setup_entry_for_edit (entry);
-  }
-
-  public override bool finish_edit_widgets (bool save) {
-    var old_details = _details;
-    bool changed = _details.value != entry.get_text () || combo.modified;
-    if (save && changed) {
-      _details = data_create (entry.get_text ());
-      _details.parameters = old_details.parameters;
-      combo.update_details (_details);
-    }
-    entry = null;
-    combo = null;
-    return changed;
-  }
-}
-
-class Contacts.EmailFieldSet : FieldSet {
-  class construct {
-    label_name = _("Email");
-    detail_name = _("Email");
-    property_name = "email-addresses";
-  }
-
-  public override void populate () {
-    var details = sheet.persona as EmailDetails;
-    if (details == null)
-      return;
-    var emails = Contact.sort_fields<EmailFieldDetails>(details.email_addresses);
-    foreach (var email in emails) {
-      var row = new DetailedFieldRow<EmailFieldDetails> (this, email,TypeSet.general, (s) => { return new EmailFieldDetails (s); } );
-      add_row (row);
-    }
-  }
-
-  public override DataFieldRow new_field () {
-    var row = new DetailedFieldRow<EmailFieldDetails> (this, new EmailFieldDetails("") ,TypeSet.general, (s) => { return new EmailFieldDetails (s); } );
-    add_row (row);
-    return row;
-  }
-
-  public override Value? get_value () {
-    var details = sheet.persona as EmailDetails;
-    if (details == null)
-      return null;
-
-    var new_details = new HashSet<EmailFieldDetails>();
-    foreach (var row in data_rows) {
-      var email_row = row as DetailedFieldRow<EmailFieldDetails>;
-      new_details.add (email_row.details);
-    }
-
-    var value = Value(new_details.get_type ());
-    value.set_object (new_details);
-
-    return value;
-  }
-}
-
-class Contacts.PhoneFieldSet : FieldSet {
-  class construct {
-    label_name = _("Phone");
-    detail_name = _("Phone number");
-    property_name = "phone-numbers";
-  }
-  public override void populate () {
-    var details = sheet.persona as PhoneDetails;
-    if (details == null)
-      return;
-    var phone_numbers = Contact.sort_fields<PhoneFieldDetails>(details.phone_numbers);
-    foreach (var phone in phone_numbers) {
-      var row = new DetailedFieldRow<PhoneFieldDetails> (this, phone,TypeSet.phone, (s) => { return new PhoneFieldDetails (s);} );
-      add_row (row);
-    }
-  }
-
-  public override DataFieldRow new_field () {
-    var row = new DetailedFieldRow<PhoneFieldDetails> (this, new PhoneFieldDetails("") ,TypeSet.phone, (s) => { return new EmailFieldDetails (s); } );
-    add_row (row);
-    return row;
-  }
-
-  public override Value? get_value () {
-    var details = sheet.persona as PhoneDetails;
-    if (details == null)
-      return null;
-
-    var new_details = new HashSet<PhoneFieldDetails>();
-    foreach (var row in data_rows) {
-      var phone_row = row as DetailedFieldRow<PhoneFieldDetails>;
-      new_details.add (phone_row.details);
-    }
-
-    var value = Value(new_details.get_type ());
-    value.set_object (new_details);
-
-    return value;
-  }
-}
-
-class Contacts.ChatFieldRow : DataFieldRow {
-  string protocol;
-  ImFieldDetails details;
-
-  Label text_label;
-
-  public ChatFieldRow (FieldSet field_set, string protocol, ImFieldDetails details) {
-    base (field_set);
-    this.protocol = protocol;
-    this.details = details;
-    text_label = this.pack_text ();
-    this.set_editable (false);
-  }
-
-  public override void update () {
-    var im_persona = field_set.sheet.persona as Tpf.Persona;
-    text_label.set_text (Contact.format_im_name (im_persona, protocol, details.value));
-  }
-}
-
-class Contacts.ChatFieldSet : FieldSet {
-  class construct {
-    label_name = _("Chat");
-    detail_name = _("Chat");
-    property_name = "im-addresses";
-  }
-  public override void populate () {
-    var details = sheet.persona as ImDetails;
-    if (details == null)
-      return;
-    foreach (var protocol in details.im_addresses.get_keys ()) {
-      foreach (var id in details.im_addresses[protocol]) {
-	if (sheet.persona is Tpf.Persona) {
-	  var row = new ChatFieldRow (this, protocol, id);
-	  add_row (row);
-	}
-      }
-    }
-  }
-
-  public override DataFieldRow new_field () {
-    var row = new ChatFieldRow (this, "", new ImFieldDetails (""));
-    add_row (row);
-    return row;
-  }
-}
-
-class Contacts.BirthdayFieldRow : DataFieldRow {
-  public DateTime details;
-  Label text_label;
-  SpinButton? day_spin;
-  SpinButton? year_spin;
-  ComboBoxText? combo;
-
-  public BirthdayFieldRow (FieldSet field_set, DateTime details) {
-    base (field_set);
-    this.details = details;
-
-    text_label = this.pack_text ();
-    var image = new Image.from_icon_name ("preferences-system-time-symbolic", IconSize.MENU);
-    image.get_style_context ().add_class ("dim-label");
-    var button = new Button();
-    button.set_relief (ReliefStyle.NONE);
-    button.add (image);
-    this.right_add (button);
-    button.clicked.connect ( () => {
-	Utils.show_calendar (details);
-      });
-  }
-
-  public override void update () {
-    text_label.set_text (details.to_local ().format ("%x"));
-  }
-
-  public override void pack_edit_widgets () {
-    var bday = details.to_local ();
-    var grid = new Grid ();
-    grid.set_column_spacing (16);
-
-    day_spin = new SpinButton.with_range (0, 31, 1);
-    day_spin.set_digits (0);
-    day_spin.numeric = true;
-    day_spin.set_value ((double)bday.get_day_of_month ());
-    grid.add (day_spin);
-
-    setup_entry_for_edit (day_spin);
-
-    combo = new ComboBoxText ();
-    combo.append_text (_("January"));
-    combo.append_text (_("February"));
-    combo.append_text (_("March"));
-    combo.append_text (_("April"));
-    combo.append_text (_("May"));
-    combo.append_text (_("June"));
-    combo.append_text (_("July"));
-    combo.append_text (_("August"));
-    combo.append_text (_("September"));
-    combo.append_text (_("October"));
-    combo.append_text (_("November"));
-    combo.append_text (_("December"));
-    combo.set_active (bday.get_month () - 1);
-    combo.get_style_context ().add_class ("contacts-combo");
-    grid.add (combo);
-
-    year_spin = new SpinButton.with_range (1800, 3000, 1);
-    year_spin.set_digits (0);
-    year_spin.numeric = true;
-    year_spin.set_value ((double)bday.get_year ());
-    grid.add (year_spin);
-
-    setup_entry_for_edit (year_spin, false);
-
-    pack (grid);
-  }
-
-  public override bool finish_edit_widgets (bool save) {
-    var old_details = details;
-
-    var bday = new DateTime.local ((int)year_spin.get_value (),
-				   combo.get_active () + 1,
-				   (int)day_spin.get_value (),
-				   0, 0, 0);
-    bday = bday.to_utc ();
-
-    var changed = !bday.equal (old_details);
-    if (save && changed)
-      details = bday;
-
-    combo = null;
-    day_spin = null;
-    year_spin = null;
-    return changed;
-  }
-}
-
-class Contacts.BirthdayFieldSet : FieldSet {
-  class construct {
-    label_name = _("Birthday");
-    detail_name = _("Birthday");
-    property_name = "birthday";
-    is_single_value = true;
-  }
-  public override void populate () {
-    var details = sheet.persona as BirthdayDetails;
-    if (details == null)
-      return;
-
-    DateTime? bday = details.birthday;
-    if (bday != null) {
-      var row = new BirthdayFieldRow (this, bday);
-      add_row (row);
-    }
-  }
-
-  public override DataFieldRow new_field () {
-    var row = new BirthdayFieldRow (this, new DateTime.now_utc ());
-    add_row (row);
-    return row;
-  }
-
-  public override Value? get_value () {
-    var details = sheet.persona as BirthdayDetails;
-    if (details == null)
-      return null;
-
-    DateTime? new_details = null;
-    foreach (var row in data_rows) {
-      var bday_row = row as BirthdayFieldRow;
-      new_details = bday_row.details;
-    }
-
-    var value = Value(typeof (DateTime));
-    value.set_boxed (new_details);
-
-    return value;
-  }
-}
-
-class Contacts.StringFieldRow : DataFieldRow {
-  public string value;
-  Label text_label;
-  Entry? entry;
-
-  public StringFieldRow (FieldSet field_set, string value) {
-    base (field_set);
-    this.value = value;
-
-    text_label = this.pack_text ();
-  }
-
-  public override void update () {
-    text_label.set_text (value);
-  }
-
-  public override void pack_edit_widgets () {
-    entry = this.pack_entry (value);
-    setup_entry_for_edit (entry);
-  }
-
-  public override bool finish_edit_widgets (bool save) {
-    var changed = entry.get_text () != value;
-    if (save && changed)
-      value = entry.get_text ();
-    entry = null;
-    return changed;
-  }
-}
-
-class Contacts.NicknameFieldSet : FieldSet {
-  class construct {
-    label_name = _("Nickname");
-    detail_name = _("Nickname");
-    property_name = "nickname";
-    is_single_value = true;
-  }
-  public override void populate () {
-    var details = sheet.persona as NameDetails;
-    if (details == null)
-      return;
-
-    if (is_set (details.nickname)) {
-      var row = new StringFieldRow (this, details.nickname);
-      add_row (row);
-    }
-  }
-
-  public override DataFieldRow new_field () {
-    var row = new StringFieldRow (this, "");
-    add_row (row);
-    return row;
-  }
-
-  public override Value? get_value () {
-    var details = sheet.persona as NameDetails;
-    if (details == null)
-      return null;
-
-    var value = Value(typeof (string));
-    value.set_string ("");
-    foreach (var row in data_rows) {
-      var string_row = row as StringFieldRow;
-      value.set_string (string_row.value);
-    }
-
-    return value;
-  }
-}
-
-class Contacts.NoteFieldRow : DataFieldRow {
-  public NoteFieldDetails details;
-  Label text_label;
-  TextView? text;
-
-  public NoteFieldRow (FieldSet field_set, NoteFieldDetails details) {
-    base (field_set);
-    this.details = details;
-
-    text_label = this.pack_text (true);
-  }
-
-  public override void update () {
-    text_label.set_text (details.value);
-  }
-
-  public override void pack_edit_widgets () {
-    text = new TextView ();
-    text.get_style_context ().add_class ("contacts-entry");
-    text.set_hexpand (true);
-    text.set_vexpand (true);
-    var scrolled = new ScrolledWindow (null, null);
-    scrolled.set_shadow_type (ShadowType.OUT);
-    scrolled.add_with_viewport (text);
-
-    pack (scrolled);
-
-    delete_button.set_valign (Align.START);
-
-    text.get_buffer ().set_text (details.value);
-    text.get_buffer ().set_modified (false);
-
-    setup_text_view_for_edit (text);
-  }
-
-  public override bool finish_edit_widgets (bool save) {
-    var old_details = details;
-    var changed = text.get_buffer (). get_modified ();
-    if (save && changed) {
-	TextIter start, end;
-	text.get_buffer ().get_start_iter (out start);
-	text.get_buffer ().get_end_iter (out end);
-	var value = text.get_buffer ().get_text (start, end, true);
-	details = new NoteFieldDetails (value, old_details.parameters);
-    }
-    text = null;
-
-    return changed;
-  }
-}
-
-class Contacts.NoteFieldSet : FieldSet {
-  class construct {
-    label_name = _("Note");
-    detail_name = _("Note");
-    property_name = "notes";
-    is_single_value = true;
-  }
-  public override void populate () {
-    var details = sheet.persona as NoteDetails;
-    if (details == null)
-      return;
-
-    foreach (var note in details.notes) {
-      var row = new NoteFieldRow (this, note);
-      add_row (row);
-    }
-  }
-
-  public override DataFieldRow new_field () {
-    var row = new NoteFieldRow (this, new NoteFieldDetails (""));
-    add_row (row);
-    return row;
-  }
-
-  public override Value? get_value () {
-    var details = sheet.persona as NoteDetails;
-    if (details == null)
-      return null;
-
-    var new_details = new HashSet<NoteFieldDetails>();
-    foreach (var row in data_rows) {
-      var note_row = row as NoteFieldRow;
-      new_details.add (note_row.details);
-    }
-
-    var value = Value(new_details.get_type ());
-    value.set_object (new_details);
-
-    return value;
-  }
-}
-
-class Contacts.AddressFieldRow : DataFieldRow {
-  public PostalAddressFieldDetails details;
-  Label? text_label[8];
-  Label detail_label;
-  Entry? entry[7];
-  TypeCombo? combo;
-
-  public AddressFieldRow (FieldSet field_set, PostalAddressFieldDetails details) {
-    base (field_set);
-    this.details = details;
-    this.pack_text_detail (out text_label[0], out detail_label);
-    for (int i = 1; i < text_label.length; i++) {
-      text_label[i] = this.pack_text (true);
-    }
-  }
+public class Contacts.ContactSheet : Grid {
 
-  public override void update () {
-    detail_label.set_text (TypeSet.general.format_type (details));
-
-    string[] strs = Contact.format_address (details.value);
-    for (int i = 0; i < text_label.length; i++) {
-      if (i < strs.length && strs[i] != null) {
-	text_label[i].set_text (strs[i]);
-	text_label[i].show ();
-	text_label[i].set_no_show_all (false);
-      } else {
-	text_label[i].hide ();
-	text_label[i].set_no_show_all (true);
-      }
-    }
-  }
-
-  public override void pack_edit_widgets () {
-
-    var grid = new Box (Orientation.VERTICAL, 0);
-    grid.set_hexpand (true);
-    grid.set_halign (Align.FILL);
-
-    for (int i = 0; i < entry.length; i++) {
-      string postal_part;
-      details.value.get (Contact.postal_element_props[i], out postal_part);
-      entry[i] = new Entry ();
-      entry[i].set_hexpand (true);
-      if (postal_part != null)
-	entry[i].set_text (postal_part);
-      entry[i].set ("placeholder-text", Contact.postal_element_names[i]);
-      entry[i].get_style_context ().add_class ("contacts-entry");
-      entry[i].get_style_context ().add_class ("contacts-postal-entry");
-      grid.add (entry[i]);
-
-      setup_entry_for_edit (entry[i], i == 0);
-    }
-
-    this.pack_widget_detail_combo (grid, details, TypeSet.general, out combo);
-    delete_button.set_valign (Align.START);
-    var size_group = new SizeGroup (SizeGroupMode.VERTICAL);
-    size_group.add_widget (delete_button);
-    size_group.add_widget (combo);
-
-  }
-
-  public override bool finish_edit_widgets (bool save) {
-    var old_details = details;
-
-    bool changed = combo.modified;
-    for (int i = 0; i < entry.length; i++) {
-      string postal_part;
-      details.value.get (Contact.postal_element_props[i], out postal_part);
-      if (entry[i].get_text () != postal_part) {
-	changed = true;
-	break;
-      }
-    }
-
-    if (save && changed) {
-      var new_value = new PostalAddress (details.value.po_box,
-					 details.value.extension,
-					 details.value.street,
-					 details.value.locality,
-					 details.value.region,
-					 details.value.postal_code,
-					 details.value.country,
-					 details.value.address_format,
-					 details.value.uid);
-      for (int i = 0; i < entry.length; i++)
-	new_value.set (Contact.postal_element_props[i], entry[i].get_text ());
-      details = new PostalAddressFieldDetails(new_value, old_details.parameters);
-      combo.update_details (details);
-    }
-
-    for (int i = 0; i < entry.length; i++)
-      entry[i] = null;
-    combo = null;
-
-    return changed;
-  }
-}
-
-class Contacts.AddressFieldSet : FieldSet {
-  class construct {
-    label_name = _("Addresses");
-    detail_name = _("Address");
-    property_name = "postal-addresses";
-  }
-  public override void populate () {
-    var details = sheet.persona as PostalAddressDetails;
-    if (details == null)
-      return;
-
-    foreach (var addr in details.postal_addresses) {
-      var row = new AddressFieldRow (this, addr);
-      add_row (row);
-    }
-  }
-
-  public override DataFieldRow new_field () {
-    var row = new AddressFieldRow (this,
-				   new PostalAddressFieldDetails (
-				     new PostalAddress (null,
-							null,
-							null,
-							null,
-							null,
-							null,
-							null,
-							null,
-							null)));
-    add_row (row);
-    return row;
-  }
-
-  public override Value? get_value () {
-    var details = sheet.persona as PostalAddressDetails;
-    if (details == null)
-      return null;
-
-    var new_details = new HashSet<PostalAddressFieldDetails>();
-    foreach (var row in data_rows) {
-      var addr_row = row as AddressFieldRow;
-      new_details.add (addr_row.details);
-    }
-
-    var value = Value(new_details.get_type ());
-    value.set_object (new_details);
-
-    return value;
-  }
-}
-
-public class Contacts.PersonaSheet : Grid {
-  public ContactPane pane;
-  public Persona persona;
-  FieldRow? header;
-  FieldRow footer;
-  int sheet_nr;
-
-  static Type[] field_set_types = {
-    typeof(LinkFieldSet),
-    typeof(EmailFieldSet),
-    typeof(PhoneFieldSet),
-    typeof(ChatFieldSet),
-    typeof(BirthdayFieldSet),
-    typeof(NicknameFieldSet),
-    typeof(AddressFieldSet),
-    typeof(NoteFieldSet)
-    /* More:
-       company/department/profession/title/manager/assistant
-    */
-  };
-  FieldSet? field_sets[8]; // This is really the size of field_set_types
-
-  public PersonaSheet(ContactPane pane, Persona persona, int _sheet_nr) {
-    assert (field_sets.length == field_set_types.length);
-
-    this.sheet_nr = _sheet_nr;
-    this.pane = pane;
-    this.persona = persona;
-
-    this.set_orientation (Orientation.VERTICAL);
-    this.set_row_spacing (16);
-
-    int row_nr = 0;
-
-    bool editable = Contact.persona_has_writable_property (persona, "email-addresses") &&
-      Contact.persona_has_writable_property (persona, "phone-numbers") &&
-      Contact.persona_has_writable_property (persona, "postal-addresses");
-
-    if (!Contact.persona_is_main (persona) || sheet_nr > 0) {
-      this.build_header ();
-      row_nr = 1;
-    }
-
-    for (int i = 0; i < field_set_types.length; i++) {
-      var field_set = (FieldSet) Object.new(field_set_types[i], sheet: this, row_nr: row_nr++);
-      field_sets[i] = field_set;
-
-      field_set.populate ();
-      if (!field_set.is_empty ())
-	field_set.add_to_sheet ();
-    }
-
-    if (editable) {
-      footer = new FieldRow (pane.row_group, pane);
-      this.attach (footer, 0, row_nr++, 1, 1);
-
-      var b = new Button.with_label (_("Add detail..."));
-      b.set_halign (Align.START);
-      b.clicked.connect (add_detail);
-      footer.pack (b);
-    }
-
-    persona.notify.connect(persona_notify_cb);
-  }
+  const int PROFILE_SIZE = 128;
 
-  ~PersonaSheet() {
-    persona.notify.disconnect(persona_notify_cb);
+  public ContactSheet () {
+    set_row_spacing (12);
+    set_column_spacing (16);
   }
 
-  private void build_header () {
-    bool editable = Contact.persona_has_writable_property (persona, "email-addresses") &&
-      Contact.persona_has_writable_property (persona, "phone-numbers") &&
-      Contact.persona_has_writable_property (persona, "postal-addresses");
-
-    header = new FieldRow (pane.row_group, pane);
-
-    Label label;
-    var grid = header.pack_header_in_grid (Contact.format_persona_store_name_for_contact (persona), out label);
+  public void update (Contact c) {
+    var image_frame = new ContactFrame (PROFILE_SIZE, true);
+    image_frame.set_vexpand (false);
+    image_frame.set_valign (Align.START);
+    c.keep_widget_uptodate (image_frame,  (w) => {
+	(w as ContactFrame).set_image (c.individual, c);
+      });
+    attach (image_frame,  0, 0, 1, 3);
+
+    var name_label = new Label (null);
+    name_label.set_hexpand (true);
+    name_label.set_halign (Align.START);
+    name_label.set_valign (Align.START);
+    name_label.set_margin_top (4);
+    name_label.set_ellipsize (Pango.EllipsizeMode.END);
+    name_label.xalign = 0.0f;
+
+    c.keep_widget_uptodate (name_label, (w) => {
+	(w as Label).set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", c.display_name));
+      });
+    attach (name_label,  1, 0, 1, 1);
 
-    if (!editable) {
-      var image = new Image.from_icon_name ("changes-prevent-symbolic", IconSize.MENU);
+    var merged_presence = c.create_merged_presence_widget ();
+    merged_presence.set_halign (Align.START);
+    merged_presence.set_valign (Align.START);
+    attach (merged_presence,  1, 1, 1, 1);
 
-      label.set_hexpand (false);
-      image.get_style_context ().add_class ("dim-label");
-      image.set_hexpand (true);
-      image.set_halign (Align.START);
-      image.set_valign (Align.CENTER);
-      grid.add (image);
-    }
+    int i = 3;
+    int last_store_position = 0;
+    PersonaStore last_store = null;
 
-    if (sheet_nr == 0) {
-      var b = new Button.with_label(_("Add to My Contacts"));
-      grid.add (b);
-
-      if (persona.store.is_primary_store) {
-	// Google Other contact (otherwise it wouldn't be non-main while
-	// being in the primary store)
-	b.clicked.connect ( () => {
-	    (persona as Edsf.Persona).in_google_personal_group = true;
-	  });
-      } else {
-	b.clicked.connect ( () => {
-	    link_contacts.begin (pane.contact, null, (obj, result) => {
-		link_contacts.end (result);
-		/* TODO: Support undo */
-	      });
-	  });
+    var personas = c.get_personas_for_display ();
+    /* Cause personas are sorted properly I can do this */
+    foreach (var p in personas) {
+      if (! Contact.persona_is_main (p) && p.store != last_store) {
+	var store_name = new Label("");
+	store_name.set_markup (Markup.printf_escaped ("<span font='16px bold'>%s</span>",
+						      Contact.format_persona_store_name_for_contact (p)));
+	store_name.set_halign (Align.START);
+	store_name.xalign = 0.0f;
+	store_name.margin_left = 6;
+	attach (store_name, 0, i, 1, 1);
+	last_store = p.store;
+	last_store_position = ++i;
       }
-    } else if (pane.contact.individual.personas.size > 1) {
-      var b = new Button.with_label(_("Unlink"));
-      grid.add (b);
-
-      b.clicked.connect ( () => {
-	  unlink_persona.begin (pane.contact, persona, (obj, result) => {
-	      unlink_persona.end (result);
-	      /* TODO: Support undo */
-	      /* TODO: Ensure we don't get suggestion for this linkage again */
-	    });
-	});
-    }
-
-    this.attach (header, 0, 0, 1, 1);
-
-    header.clicked.connect ( () => {
-	this.pane.enter_edit_mode (header);
-      });
-  }
 
-  private void add_detail () {
-    pane.exit_edit_mode (true);
-    var title = _("Select detail to add to %s").printf (pane.contact.display_name);
-    var dialog = new Dialog.with_buttons ("",
-					  (Window) pane.get_toplevel (),
-					  DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT,
-					  Stock.CANCEL, ResponseType.CANCEL,
-					  Stock.OK, ResponseType.OK);
-
-    dialog.set_resizable (false);
-    dialog.set_default_response (ResponseType.OK);
-
-    var tree_view = new TreeView ();
-    var store = new ListStore (2, typeof (string), typeof (FieldSet));
-    tree_view.set_model (store);
-    tree_view.set_headers_visible (false);
-    tree_view.get_selection ().set_mode (SelectionMode.BROWSE);
-
-    var column = new Gtk.TreeViewColumn ();
-    tree_view.append_column (column);
-
-    var renderer = new Gtk.CellRendererText ();
-    column.pack_start (renderer, false);
-    column.add_attribute (renderer, "text", 0);
-
-    var scrolled = new ScrolledWindow(null, null);
-    scrolled.set_size_request (340, 300);
-    scrolled.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
-    scrolled.set_vexpand (true);
-    scrolled.set_hexpand (true);
-    scrolled.set_shadow_type (ShadowType.IN);
-    scrolled.add (tree_view);
-
-    var grid = new Grid ();
-    grid.set_orientation (Orientation.VERTICAL);
-    grid.set_row_spacing (28);
-
-    var l = new Label (title);
-    l.set_halign (Align.START);
-
-    grid.add (l);
-    grid.add (scrolled);
-
-    var box = dialog.get_content_area () as Box;
-    box.pack_start (grid, true, true, 0);
-    grid.set_border_width (6);
-
-    TreeIter iter;
-
-    for (int i = 0; i < field_set_types.length; i++) {
-      var field_set = field_sets[i];
-      if (!(field_set is ChatFieldSet) &&
-	  Contact.persona_has_writable_property (persona, field_set.property_name) &&
-	  (field_set.is_empty () || !field_set.is_single_value)) {
-	store.append (out iter);
-	store.set (iter, 0, field_set.detail_name, 1, field_set);
+      /* emails first */
+      var details = p as EmailDetails;
+      if (details != null) {
+	var emails = Contact.sort_fields<EmailFieldDetails>(details.email_addresses);
+	foreach (var email in emails) {
+	  var type_label = new Label (TypeSet.general.format_type (email));
+	  type_label.xalign = 1.0f;
+	  type_label.set_halign (Align.END);
+	  type_label.get_style_context ().add_class ("dim-label");
+	  attach (type_label, 0, i, 1, 1);
+
+	  var value_label = new Button.with_label (email.value);
+	  value_label.focus_on_click = false;
+	  value_label.relief = ReliefStyle.NONE;
+	  value_label.xalign = 0.0f;
+	  value_label.set_hexpand (true);
+	  attach (value_label, 1, i, 1, 1);
+	  i++;
+
+	  value_label.clicked.connect (() => {
+	      Utils.compose_mail ("%s <%s>".printf(c.display_name, email.value));
+	    });
+	}
       }
-    }
 
-    dialog.show_all ();
-    dialog.response.connect ( (response) => {
-	if (response == ResponseType.OK) {
-	  FieldSet field_set;
-	  TreeIter iter2;
-
-	  if (tree_view.get_selection() .get_selected (null, out iter2)) {
-	    store.get (iter2, 1, out field_set);
-
-	    var row = field_set.new_field ();
-	    field_set.show_all ();
-	    field_set.add_to_sheet ();
-	    pane.enter_edit_mode (row);
+      /* phones then */
+      var phone_details = p as PhoneDetails;
+      if (phone_details != null) {
+	var phones = Contact.sort_fields<PhoneFieldDetails>(phone_details.phone_numbers);
+	foreach (var phone in phones) {
+	  var type_label = new Label (TypeSet.general.format_type (phone));
+	  type_label.xalign = 1.0f;
+	  type_label.set_halign (Align.END);
+	  type_label.get_style_context ().add_class ("dim-label");
+	  attach (type_label, 0, i, 1, 1);
+
+	  Widget value_label;
+	  if (App.app.contacts_store.can_call) {
+	    value_label = new Button.with_label (phone.value);
+	    value_label.set_hexpand (true);
+	    (value_label as Button).focus_on_click = false;
+	    (value_label as Button).relief = ReliefStyle.NONE;
+
+	    (value_label as Button).clicked.connect (() => {
+		Utils.start_call (phone.value, App.app.contacts_store.calling_accounts);
+	      });
+	  } else {
+	    value_label = new Label (phone.value);
+	    value_label.set_halign (Align.START);
+	    /* FXIME: hardcode gap to match the button-label starting */
+	    value_label.margin_left = 6;
 	  }
+
+	  attach (value_label, 1, i, 1, 1);
+	  i++;
+
 	}
-	dialog.destroy ();
-      });
-  }
+      }
 
-  private void persona_notify_cb (ParamSpec pspec) {
-    var name = pspec.get_name ();
-    foreach (var field_set in field_sets) {
-      if (field_set.reads_param (name) && !field_set.saving) {
-	field_set.refresh_from_persona ();
+      if (i == last_store_position) {
+	get_child_at (0, i - 1).destroy ();
       }
     }
 
-    if (name == "in-google-personal-group") {
-      bool is_main = Contact.persona_is_main (persona);
+    show_all ();
+  }
 
-      if ((!is_main || sheet_nr > 0) &&
-	  header == null) {
-	this.build_header ();
-      } else if (is_main && sheet_nr == 0 && header != null) {
-	header.destroy();
-	header = null;
-      }
+  public void clear () {
+    foreach (var w in get_children ()) {
+      w.destroy ();
     }
   }
 }
 
-
 public class Contacts.ContactPane : ScrolledWindow {
   private Store contacts_store;
+  public Contact? contact;
+
   private Grid top_grid;
-  private FieldRow card_row;
-  private Grid card_grid;
-  private Grid personas_grid;
-  public RowGroup row_group;
-  public RowGroup card_row_group;
-  public FieldRow? editing_row;
-
-  public Button email_button;
-  public Button chat_button;
-  public Button call_button;
-  public Gtk.Menu context_menu;
-  private Gtk.MenuItem link_menu_item;
-  private Gtk.MenuItem delete_menu_item;
+  private ContactSheet sheet; /* Eventually replace top_grid with sheet */
+
+  private Grid no_selection_grid;
 
   public Grid suggestion_grid;
-  public Contact? contact;
 
-  const int PROFILE_SIZE = 128;
+  /* Signals */
+  public signal void contacts_linked (string? main_contact, string linked_contact, LinkOperation operation);
+  public signal void will_delete (Contact contact);
 
   /* Tries to set the property on all persons that have it writeable, and
    * if none, creates a new persona and writes to it, returning the new
@@ -1523,7 +189,6 @@ public class Contacts.ContactPane : ScrolledWindow {
   }
 
   private void change_avatar (ContactFrame image_frame) {
-    this.exit_edit_mode (true);
     var dialog = new AvatarDialog (contact);
     dialog.show ();
     dialog.set_avatar.connect ( (icon) =>  {
@@ -1542,217 +207,25 @@ public class Contacts.ContactPane : ScrolledWindow {
       });
   }
 
-  public void update_card () {
-    foreach (var w in card_grid.get_children ()) {
-      w.destroy ();
-    }
-
-    if (contact == null)
-      return;
-
-    var image_frame = new ContactFrame (PROFILE_SIZE, true);
-    image_frame.clicked.connect ( () => {
-	change_avatar (image_frame);
-      });
-    contact.keep_widget_uptodate (image_frame,  (w) => {
-	(w as ContactFrame).set_image (contact.individual, contact);
-      });
-
-    card_grid.attach (image_frame,  0, 0, 1, 3);
-    card_grid.set_column_spacing (16);
-
-    var l = new Label (null);
-    l.set_hexpand (true);
-    l.set_halign (Align.START);
-    l.set_valign (Align.START);
-    l.set_margin_top (4);
-    l.set_ellipsize (Pango.EllipsizeMode.END);
-    l.xalign = 0.0f;
-
-    contact.keep_widget_uptodate (l,  (w) => {
-	(w as Label).set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", contact.display_name));
-      });
-
-    var event_box = new EventBox ();
-    event_box.set_margin_top (4);
-    event_box.set_margin_bottom (8);
-    event_box.set_visible_window (false);
-
-    var clickable = new Clickable (event_box);
-    event_box.realize.connect_after ( (event) => {
-	Gdk.Window window = null;
-	foreach (var win in event_box.get_window ().get_children ()) {
-	  Widget *w = null;
-	  win.get_user_data (out w);
-	  if (w == event_box) {
-	    window = win;
-	  }
-	}
-	clickable.realize_for (window);
-      });
-    event_box.unrealize.connect_after ( (event) => {
-	clickable.unrealize ();
-      });
-    clickable.clicked.connect ( () => {
-	this.enter_edit_mode (card_row);
-      });
-
-    var id1 = card_row.enter_edit_mode.connect_after ( () => {
-	event_box.remove (l);
-	var entry = new Entry ();
-	entry.set_text (contact.display_name);
-	entry.set_hexpand (true);
-	entry.show ();
-	entry.override_font (Pango.FontDescription.from_string ("16px"));
-	event_box.add (entry);
-	Utils.grab_widget_later (entry);
-
-	entry.activate.connect_after ( () => {
-	    exit_edit_mode (true);
-	  });
-	entry.key_press_event.connect ( (key_event) => {
-	    if (key_event.keyval == Gdk.Key.Escape) {
-	      exit_edit_mode (false);
-	    }
-	    return false;
-	  });
-
-	return true;
-      });
-
-    var id2 = card_row.exit_edit_mode.connect ( (save) => {
-	Entry entry = event_box.get_child () as Entry;
-	bool changed = entry.get_text () != contact.display_name;
-
-	if (save && changed) {
-	  // Things look better if we update immediately, rather than after the setting has
-	  // been applied
-	  l.set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", entry.get_text ()));
-
-	  Value v = Value (typeof (string));
-	  v.set_string (entry.get_text ());
-	  set_individual_property.begin (contact,
-					 "full-name", v,
-					 (obj, result) => {
-					   try {
-					     set_individual_property.end (result);
-					   } catch (Error e) {
-					     App.app.show_message (e.message);
-					     l.set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", contact.display_name));
-					   }
-					 });
-	}
-
-	event_box.remove (entry);
-	event_box.add (l);
-      });
-
-    var id3 = card_row.lost_child_focus.connect ( () => {
-	if (editing_row == card_row)
-	  exit_edit_mode (true);
-      });
-
-    event_box.destroy.connect ( () => {
-	card_row.disconnect (id1);
-	card_row.disconnect (id2);
-	card_row.disconnect (id3);
-      });
-
-    event_box.add (l);
-    card_grid.attach (event_box,  1, 0, 1, 1);
-
-    var merged_presence = contact.create_merged_presence_widget ();
-    merged_presence.set_halign (Align.START);
-    merged_presence.set_valign (Align.START);
-    merged_presence.set_vexpand (true);
-    card_grid.attach (merged_presence,  1, 1, 1, 1);
-
-    var box = new Box (Orientation.HORIZONTAL, 0);
-    box.set_margin_bottom (4 + 8);
-    box.set_halign (Align.START);
-
-    box.get_style_context ().add_class ("linked");
-    box.set_homogeneous (true);
-    box.set_halign (Align.FILL);
-    var image = new Image.from_icon_name ("mail-unread-symbolic", IconSize.MENU);
-    var b = new Button ();
-    b.add (image);
-    box.pack_start (b, true, true, 0);
-    email_button = b;
-    email_button.clicked.connect (send_email);
-
-    image = new Image.from_icon_name ("user-available-symbolic", IconSize.MENU);
-    b = new Button ();
-    b.add (image);
-    box.pack_start (b, true, true, 0);
-    chat_button = b;
-    chat_button.clicked.connect (start_chat);
-
-    image = new Image.from_icon_name ("call-start-symbolic", IconSize.MENU);
-    b = new Button ();
-    b.add (image);
-    box.pack_start (b, true, true, 0);
-    call_button = b;
-    call_button.clicked.connect (start_call);
-
-    card_grid.attach (box,  1, 2, 1, 1);
-
-    card_grid.show_all ();
-
-    update_buttons ();
-  }
+  public void update_sheet (bool show_matches = true) {
+    sheet.clear ();
 
-  public void update_buttons () {
     if (contact == null)
       return;
 
-    var emails = contact.individual.email_addresses;
-    email_button.set_sensitive (!emails.is_empty);
+    sheet.update (contact);
 
-    var ims = contact.individual.im_addresses;
-    var im_keys = ims.get_keys ();
-    bool found_im = false;
-    bool callable = false;
-    PresenceType max_presence = 0;
-    foreach (var protocol in im_keys) {
-      foreach (var id in ims[protocol]) {
-	var im_persona = contact.find_im_persona (protocol, id.value);
-	if (im_persona != null) {
-	  var type = im_persona.presence_type;
-	  if (type != PresenceType.UNSET &&
-	      type != PresenceType.ERROR &&
-	      type != PresenceType.OFFLINE &&
-	      type != PresenceType.UNKNOWN) {
-	    found_im = true;
-	    if (type > max_presence)
-	      max_presence = type;
-	  }
+    if (show_matches) {
+      var matches = contact.store.aggregator.get_potential_matches (contact.individual, MatchResult.HIGH);
+      foreach (var ind in matches.keys) {
+	var c = Contact.from_individual (ind);
+	if (c != null && contact.suggest_link_to (c)) {
+	  add_suggestion (c);
 	}
-
-	if (contact.is_callable (protocol, id.value) != null)
-	  callable = true;
       }
     }
-
-    if (contacts_store.can_call) {
-      var phones = contact.individual.phone_numbers;
-      if (!phones.is_empty)
-	callable = true;
-    }
-
-    string icon;
-    if (found_im)
-      icon = Contact.presence_to_icon_symbolic (max_presence);
-    else
-      icon = "user-available-symbolic";
-    (chat_button.get_child () as Image).set_from_icon_name (icon, IconSize.MENU);
-    chat_button.set_sensitive (found_im);
-
-    call_button.set_sensitive (callable);
   }
 
-  public signal void contacts_linked (string? main_contact, string linked_contact, LinkOperation operation);
-
   public void add_suggestion (Contact c) {
     var parent_overlay = this.get_parent () as Overlay;
 
@@ -1826,48 +299,10 @@ public class Contacts.ContactPane : ScrolledWindow {
     suggestion_grid.show_all ();
   }
 
-  private uint update_personas_timeout;
-  public void update_personas (bool show_matches = true) {
-    if (update_personas_timeout != 0) {
-      Source.remove (update_personas_timeout);
-      update_personas_timeout = 0;
-    }
-
-    foreach (var w in personas_grid.get_children ()) {
-      w.destroy ();
-    }
-
-    if (contact == null)
-      return;
-
-    var personas = contact.get_personas_for_display ();
-
-    int i = 0;
-    foreach (var p in personas) {
-      var sheet = new PersonaSheet(this, p, i++);
-      personas_grid.add (sheet);
-    }
-
-    if (show_matches) {
-      var matches = contact.store.aggregator.get_potential_matches (contact.individual, MatchResult.HIGH);
-      foreach (var ind in matches.keys) {
-	var c = Contact.from_individual (ind);
-	if (c != null && contact.suggest_link_to (c)) {
-	  add_suggestion (c);
-	}
-      }
-    }
-
-    personas_grid.show_all ();
-  }
-
   public void show_contact (Contact? new_contact, bool edit=false, bool show_matches = true) {
     if (contact == new_contact)
       return;
 
-    if (contact != null && editing_row != null)
-      exit_edit_mode (true);
-
     if (contact != null) {
       contact.personas_changed.disconnect (personas_changed_cb);
       contact.changed.disconnect (contact_changed_cb);
@@ -1875,19 +310,14 @@ public class Contacts.ContactPane : ScrolledWindow {
 
     contact = new_contact;
 
-    update_card ();
-    update_personas (show_matches);
+    if (contact != null)
+      no_selection_grid.destroy ();
+
+    update_sheet ();
 
     if (suggestion_grid != null)
       suggestion_grid.destroy ();
 
-    if (!show_matches) {
-      update_personas_timeout = Gdk.threads_add_timeout (100, () => {
-	  update_personas ();
-	  return false;
-	});
-    }
-
     bool can_remove = false;
 
     if (contact != null) {
@@ -1897,201 +327,16 @@ public class Contacts.ContactPane : ScrolledWindow {
       can_remove = contact.can_remove_personas ();
     }
 
-    delete_menu_item.set_sensitive (can_remove);
-    link_menu_item.set_sensitive (contact != null);
+    if (contact == null)
+      show_no_selection_grid ();
   }
 
   private void personas_changed_cb (Contact contact) {
-    update_personas ();
+    update_sheet ();
   }
 
   private void contact_changed_cb (Contact contact) {
-    update_buttons ();
-  }
-
-  public void enter_edit_mode (FieldRow row) {
-    if (editing_row != row) {
-      exit_edit_mode (true);
-      editing_row = null;
-      if (row.enter_edit_mode ()) {
-	editing_row = row;
-	editing_row.set_editing (true);
-      }
-    }
-  }
-
-  public void exit_edit_mode (bool save) {
-    if (editing_row != null) {
-      editing_row.exit_edit_mode (save);
-      editing_row.set_editing (false);
-    }
-
-    editing_row = null;
-  }
-
-  private Dialog pick_one_dialog (string title, TreeModel model, out TreeSelection selection) {
-    var dialog = new Dialog.with_buttons (title,
-					  (Window) this.get_toplevel (),
-					  DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT,
-					  Stock.CANCEL, ResponseType.CANCEL,
-					  Stock.OK, ResponseType.OK);
-
-    dialog.set_resizable (false);
-    dialog.set_default_response (ResponseType.OK);
-
-    var tree_view = new TreeView ();
-    tree_view.set_model (model);
-    tree_view.set_headers_visible (false);
-    tree_view.get_selection ().set_mode (SelectionMode.BROWSE);
-
-    var column = new Gtk.TreeViewColumn ();
-    tree_view.append_column (column);
-
-    var renderer = new Gtk.CellRendererText ();
-    column.pack_start (renderer, false);
-    column.add_attribute (renderer, "text", 0);
-
-    var scrolled = new ScrolledWindow(null, null);
-    scrolled.set_size_request (340, 300);
-    scrolled.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
-    scrolled.set_vexpand (true);
-    scrolled.set_hexpand (true);
-    scrolled.set_shadow_type (ShadowType.IN);
-    scrolled.add (tree_view);
-
-    var grid = new Grid ();
-    grid.set_orientation (Orientation.VERTICAL);
-    grid.set_row_spacing (6);
-
-    var l = new Label (title);
-    l.set_halign (Align.START);
-
-    grid.add (l);
-    grid.add (scrolled);
-
-    var box = dialog.get_content_area () as Box;
-    box.pack_start (grid, true, true, 0);
-    grid.set_border_width (6);
-
-    dialog.show_all ();
-
-    selection = tree_view.get_selection ();
-    return dialog;
-  }
-
-
-  public void send_email () {
-    var emails = contact.individual.email_addresses;
-    if (emails.is_empty)
-      return;
-    if (emails.size == 1) {
-      foreach (var email in emails) {
-	var email_addr = email.value;
-	Utils.compose_mail (email_addr);
-      }
-    } else {
-      TreeIter iter;
-
-      var store = new ListStore (1, typeof (string));
-      foreach (var email in emails) {
-	var email_addr = email.value;
-	store.append (out iter);
-	store.set (iter, 0, email_addr);
-      }
-
-      TreeSelection selection;
-      var dialog = pick_one_dialog (_("Select email address"), store, out selection);
-      dialog.response.connect ( (response) => {
-	  if (response == ResponseType.OK) {
-	    string email2;
-	    TreeIter iter2;
-
-	    if (selection.get_selected (null, out iter2)) {
-	      store.get (iter2, 0, out email2);
-	      Utils.compose_mail (email2);
-	    }
-	  }
-	  dialog.destroy ();
-	});
-    }
-  }
-
-  struct CallValue {
-    string phone_nr;
-    string protocol;
-    string id;
-    string name;
-  }
-
-  public void start_call () {
-    var ims = contact.individual.im_addresses;
-    var im_keys = ims.get_keys ();
-    var call_targets = new ArrayList<CallValue?>();
-    foreach (var protocol in im_keys) {
-      foreach (var id in ims[protocol]) {
-	var im_persona = contact.find_im_persona (protocol, id.value);
-	if (im_persona != null &&
-	    contact.is_callable (protocol, id.value) != null) {
-	  var type = im_persona.presence_type;
-	  if (type != PresenceType.UNSET &&
-	      type != PresenceType.ERROR &&
-	      type != PresenceType.OFFLINE &&
-	      type != PresenceType.UNKNOWN) {
-	    CallValue? value = { null, protocol, id.value, Contact.format_im_name (im_persona, protocol, id.value) };
-	    call_targets.add (value);
-	  }
-	}
-      }
-    }
-
-    if (contacts_store.can_call) {
-      var phones = contact.individual.phone_numbers;
-      foreach (var phone in phones) {
-	CallValue? value = { phone.value, null, null, phone.value };
-	call_targets.add (value);
-      }
-    }
-
-
-    if (call_targets.is_empty)
-      return;
-
-    if (call_targets.size == 1) {
-      foreach (var value in call_targets) {
-	if (value.phone_nr != null)
-	  Utils.start_call (value.phone_nr, this.contacts_store.calling_accounts);
-	else {
-	  var account = contact.is_callable (value.protocol, value.id);
-	  Utils.start_call_with_account (value.id, account);
-	}
-      }
-    } else {
-      var store = new ListStore (2, typeof (string), typeof (CallValue?));
-      foreach (var value in call_targets) {
-	TreeIter iter;
-	store.append (out iter);
-	store.set (iter, 0, value.name, 1, value);
-      }
-      TreeSelection selection;
-      var dialog = pick_one_dialog (_("Select what to call"), store, out selection);
-      dialog.response.connect ( (response) => {
-	  if (response == ResponseType.OK) {
-	    CallValue? value2;
-	    TreeIter iter2;
-
-	    if (selection.get_selected (null, out iter2)) {
-	      store.get (iter2, 1, out value2);
-	      if (value2.phone_nr != null)
-		Utils.start_call (value2.phone_nr, this.contacts_store.calling_accounts);
-	      else {
-		var account = contact.is_callable (value2.protocol, value2.id);
-		Utils.start_call_with_account (value2.id, account);
-	      }
-	    }
-	  }
-	  dialog.destroy ();
-	});
-    }
+    /* FIXME: what to do here ? */
   }
 
   struct ImValue {
@@ -2130,26 +375,27 @@ public class Contacts.ContactPane : ScrolledWindow {
 	Utils.start_chat (contact, value.protocol, value.id);
       }
     } else {
-      var store = new ListStore (2, typeof (string), typeof (ImValue?));
-      foreach (var value in online_personas) {
-	TreeIter iter;
-	store.append (out iter);
-	store.set (iter, 0, value.name, 1, value);
-      }
-      TreeSelection selection;
-      var dialog = pick_one_dialog (_("Select chat account"), store, out selection);
-      dialog.response.connect ( (response) => {
-	  if (response == ResponseType.OK) {
-	    ImValue? value2;
-	    TreeIter iter2;
-
-	    if (selection.get_selected (null, out iter2)) {
-	      store.get (iter2, 1, out value2);
-	      Utils.start_chat (contact, value2.protocol, value2.id);
-	    }
-	  }
-	  dialog.destroy ();
-	});
+      /* FIXME, uncomment */
+      // var store = new ListStore (2, typeof (string), typeof (ImValue?));
+      // foreach (var value in online_personas) {
+      // 	TreeIter iter;
+      // 	store.append (out iter);
+      // 	store.set (iter, 0, value.name, 1, value);
+      // }
+      // TreeSelection selection;
+      // var dialog = pick_one_dialog (_("Select chat account"), store, out selection);
+      // dialog.response.connect ( (response) => {
+      // 	  if (response == ResponseType.OK) {
+      // 	    ImValue? value2;
+      // 	    TreeIter iter2;
+
+      // 	    if (selection.get_selected (null, out iter2)) {
+      // 	      store.get (iter2, 1, out value2);
+      // 	      Utils.start_chat (contact, value2.protocol, value2.id);
+      // 	    }
+      // 	  }
+      // 	  dialog.destroy ();
+      // 	});
     }
   }
 
@@ -2157,81 +403,39 @@ public class Contacts.ContactPane : ScrolledWindow {
     this.get_style_context ().add_class ("contacts-content");
     this.set_shadow_type (ShadowType.IN);
 
-    this.button_press_event.connect ( (e) => {
-	exit_edit_mode (true);
-	return false;
-      });
-
-    this.contacts_store = contacts_store;
-    row_group = new RowGroup(3);
-    row_group.set_column_min_width (0, 32);
-    row_group.set_column_min_width (1, 400);
-    row_group.set_column_max_width (1, 480);
-    row_group.set_column_min_width (2, 32);
-    row_group.set_column_spacing (0, 8);
-    row_group.set_column_spacing (1, 8);
-    row_group.set_column_priority (1, 1);
-
-    card_row_group = row_group.copy ();
-    /* This is kinda lame hardcoding so that the frame inside
-       the button aligns with the other rows. It really
-       depends on the theme, but there seems no good way to
-       do this */
-    card_row_group.set_column_spacing (0, 0);
-
     this.set_hexpand (true);
     this.set_vexpand (true);
     this.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
 
+    this.contacts_store = contacts_store;
+
     top_grid = new Grid ();
     top_grid.set_orientation (Orientation.VERTICAL);
-    top_grid.set_margin_top (40);
-    top_grid.set_margin_bottom (32);
+    top_grid.margin = 36;
+    top_grid.set_margin_bottom (24);
     top_grid.set_row_spacing (20);
     this.add_with_viewport (top_grid);
     top_grid.set_focus_vadjustment (this.get_vadjustment ());
 
-    var viewport = this.get_child ();
-    viewport.button_press_event.connect ( (event) => {
-	if (event.button == 3) {
-	  context_menu.popup (null, null, null, event.button, event.time);
-	  return true;
-	}
-	return false;
-      });
-
     this.get_child().get_style_context ().add_class ("contacts-main-view");
     this.get_child().get_style_context ().add_class ("view");
 
-    card_row = new FieldRow (card_row_group, this);
-    top_grid.add (card_row);
-    card_grid = new Grid ();
-    card_grid.set_vexpand (false);
-    card_row.pack (card_grid);
-
-    personas_grid = new Grid ();
-    personas_grid.set_orientation (Orientation.VERTICAL);
-    personas_grid.set_row_spacing (40);
-    top_grid.add (personas_grid);
+    sheet = new ContactSheet ();
+    sheet.set_orientation (Orientation.VERTICAL);
+    top_grid.add (sheet);
 
     top_grid.show_all ();
 
-    context_menu = new Gtk.Menu ();
-    link_menu_item = Utils.add_menu_item (context_menu,_("Add/Remove Linked Contacts..."));
-    link_menu_item.activate.connect (link_contact);
-    link_menu_item.set_sensitive (false);
-    //Utils.add_menu_item (context_menu,_("Send..."));
-    delete_menu_item = Utils.add_menu_item (context_menu,_("Delete"));
-    delete_menu_item.activate.connect (delete_contact);
-    delete_menu_item.set_sensitive (false);
-
     contacts_store.quiescent.connect (() => {
       // Refresh the view when the store is quiescent as we may have missed
       // some potential matches while the store was still preparing.
-      update_personas ();
+      /* FIXME, uncomment */
+      // update_properties ();
     });
 
     suggestion_grid = null;
+
+    show_no_selection_grid ();
   }
 
   void link_contact () {
@@ -2242,12 +446,35 @@ public class Contacts.ContactPane : ScrolledWindow {
     dialog.show_all ();
   }
 
-  public signal void will_delete (Contact contact);
-
   void delete_contact () {
     if (contact != null) {
       contact.hide ();
       this.will_delete (contact);
     }
   }
+
+  void show_no_selection_grid () {
+    if ( icon_size_from_name ("ULTRABIG") == 0)
+      icon_size_register ("ULTRABIG", 144, 144);
+
+    no_selection_grid = new Grid ();
+
+    var box = new Grid ();
+    box.set_orientation (Orientation.VERTICAL);
+    box.set_valign (Align.CENTER);
+    box.set_halign (Align.CENTER);
+    box.set_vexpand (true);
+    box.set_hexpand (true);
+
+    var image = new Image.from_icon_name ("avatar-default-symbolic", icon_size_from_name ("ULTRABIG"));
+    image.get_style_context ().add_class ("dim-label");
+    box.add (image);
+
+    var label = new Gtk.Label ("Select a contact");
+    box.add (label);
+
+    no_selection_grid.add (box);
+    no_selection_grid.show_all ();
+    top_grid.add (no_selection_grid);
+  }
 }



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