~ubuntu-branches/ubuntu/trusty/gnome-contacts/trusty

« back to all changes in this revision

Viewing changes to src/contacts-view.vala

  • Committer: Package Import Robot
  • Author(s): Jeremy Bicha
  • Date: 2012-06-04 08:42:35 UTC
  • Revision ID: package-import@ubuntu.com-20120604084235-ikt2xlg99vjlfvcq
Tags: 3.5.1-0ubuntu1
* New upstream release.
* debian/control.in:
  - Build-depend on libgstreamer-plugins-base0.10-dev and 
    libgstreamer0.10-dev for webcam integration 
* debian/watch: Watch for unstable releases.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
using Folks;
21
21
using Gee;
22
22
 
23
 
public class Contacts.View : TreeView {
 
23
public class Contacts.View : Egg.ListBox {
24
24
  private class ContactData {
25
25
    public Contact contact;
26
 
    public TreeIter iter;
27
 
    public bool visible;
28
 
    public bool is_first;
 
26
    public Grid grid;
 
27
    public Label label;
 
28
    public ContactFrame image_frame;
29
29
    public int sort_prio;
 
30
    public string display_name;
 
31
    public unichar initial_letter;
 
32
    public bool filtered;
30
33
  }
31
34
 
32
35
  public enum Subset {
36
39
    ALL
37
40
  }
38
41
 
 
42
  public enum TextDisplay {
 
43
    NONE,
 
44
    PRESENCE,
 
45
    STORES
 
46
  }
 
47
 
 
48
  public signal void selection_changed (Contact? contact);
 
49
 
39
50
  Store contacts_store;
40
51
  Subset show_subset;
41
 
  ListStore list_store;
 
52
  HashMap<Contact,ContactData> contacts;
42
53
  HashSet<Contact> hidden_contacts;
 
54
 
43
55
  string []? filter_values;
44
 
  int custom_visible_count;
45
 
  ContactData suggestions_header_data;
46
 
  ContactData padding_data;
47
 
  ContactData other_header_data;
 
56
  private TextDisplay text_display;
48
57
 
49
58
  public View (Store store, TextDisplay text_display = TextDisplay.PRESENCE) {
 
59
    set_selection_mode (SelectionMode.BROWSE);
50
60
    contacts_store = store;
51
61
    hidden_contacts = new HashSet<Contact>();
52
62
    show_subset = Subset.ALL;
53
 
 
54
 
    list_store = new ListStore (2, typeof (Contact), typeof (ContactData *));
55
 
    suggestions_header_data = new ContactData ();
56
 
    suggestions_header_data.sort_prio = int.MAX;
57
 
    padding_data = new ContactData ();
58
 
    padding_data.sort_prio = 1;
59
 
 
60
 
    other_header_data = new ContactData ();
61
 
    other_header_data.sort_prio = -1;
62
 
 
63
 
    list_store.set_sort_func (0, (model, iter_a, iter_b) => {
64
 
        ContactData *aa, bb;
65
 
        model.get (iter_a, 1, out aa);
66
 
        model.get (iter_b, 1, out bb);
67
 
 
68
 
        return compare_data (aa, bb);
 
63
    this.text_display = text_display;
 
64
 
 
65
    contacts = new HashMap<Contact,ContactData> ();
 
66
 
 
67
    this.set_sort_func ((widget_a, widget_b) => {
 
68
        var a = widget_a.get_data<ContactData> ("data");
 
69
        var b = widget_b.get_data<ContactData> ("data");
 
70
        return compare_data (a, b);
69
71
      });
70
 
    list_store.set_sort_column_id (0, SortType.ASCENDING);
 
72
    this.set_filter_func (filter);
 
73
    this.set_separator_funcs (update_separator);
71
74
 
72
75
    contacts_store.added.connect (contact_added_cb);
73
76
    contacts_store.removed.connect (contact_removed_cb);
74
77
    contacts_store.changed.connect (contact_changed_cb);
75
78
    foreach (var c in store.get_contacts ())
76
79
      contact_added_cb (store, c);
77
 
 
78
 
      init_view (text_display);
79
80
  }
80
81
 
81
82
  private int compare_data (ContactData a_data, ContactData b_data) {
87
88
    if (a_prio < b_prio)
88
89
      return 1;
89
90
 
90
 
    var a = a_data.contact;
91
 
    var b = b_data.contact;
92
 
 
93
 
    if (is_set (a.display_name) && is_set (b.display_name))
94
 
      return a.display_name.collate (b.display_name);
 
91
    if (is_set (a_data.display_name) && is_set (b_data.display_name))
 
92
      return a_data.display_name.collate (b_data.display_name);
95
93
 
96
94
    // Sort empty names last
97
 
    if (is_set (a.display_name))
 
95
    if (is_set (a_data.display_name))
98
96
      return -1;
99
 
    if (is_set (b.display_name))
 
97
    if (is_set (b_data.display_name))
100
98
      return 1;
101
99
 
102
100
    return 0;
111
109
  }
112
110
 
113
111
  /* The hardcoded prio if set, otherwise 0 for the
114
 
     main/combined group, or -2 for the separated other group */
 
112
     main/combined group, or -1 for the separated other group */
115
113
  private int get_sort_prio (ContactData *data) {
116
114
    if (data->sort_prio != 0)
117
115
      return data->sort_prio;
118
116
 
119
117
    if (is_other (data))
120
 
      return -2;
 
118
      return -1;
121
119
    return 0;
122
120
  }
123
121
 
124
 
  public string get_header_text (TreeIter iter) {
125
 
    ContactData *data;
126
 
    list_store.get (iter, 1, out data);
127
 
    if (data == suggestions_header_data) {
128
 
      /* Translators: This is the header for the list of suggested contacts to
129
 
         link to the current contact */
130
 
      return ngettext ("Suggestion", "Suggestions", custom_visible_count);
131
 
    }
132
 
    if (data == other_header_data) {
133
 
      /* Translators: This is the header for the list of suggested contacts to
134
 
         link to the current contact */
135
 
      return _("Other Contacts");
136
 
    }
137
 
    return "";
138
 
  }
139
 
 
140
122
  public void set_show_subset (Subset subset) {
141
123
    show_subset = subset;
142
 
 
143
 
    bool new_visible = show_subset == Subset.ALL_SEPARATED;
144
 
    if (new_visible && !other_header_data.visible) {
145
 
      other_header_data.visible = true;
146
 
      list_store.append (out other_header_data.iter);
147
 
      list_store.set (other_header_data.iter, 1, other_header_data);
148
 
    }
149
 
    if (!new_visible && other_header_data.visible) {
150
 
      other_header_data.visible = false;
151
 
      list_store.remove (other_header_data.iter);
152
 
    }
153
 
 
 
124
    update_all_filtered ();
154
125
    refilter ();
 
126
    resort ();
155
127
  }
156
128
 
157
129
  public void set_custom_sort_prio (Contact c, int prio) {
158
130
    /* We use negative prios internally */
159
131
    assert (prio >= 0);
160
132
 
161
 
    var data = lookup_data (c);
162
 
 
 
133
    var data = contacts.get (c);
163
134
    if (data == null)
164
135
      return;
165
 
 
166
 
    // We insert a priority between 0 and 1 for the padding
167
 
    if (prio > 0)
168
 
      prio += 1;
169
136
    data.sort_prio = prio;
170
 
    contact_changed_cb (contacts_store, c);
171
 
 
172
 
    if (data.visible) {
173
 
      if (prio > 0) {
174
 
        if (custom_visible_count++ == 0)
175
 
          add_custom_headers ();
176
 
      } else {
177
 
        if (custom_visible_count-- == 1)
178
 
          remove_custom_headers ();
179
 
      }
180
 
    }
181
 
  }
182
 
 
183
 
  private bool apply_filter (Contact contact) {
184
 
    if (contact.is_hidden)
185
 
      return false;
186
 
 
187
 
    if (contact in hidden_contacts)
188
 
      return false;
189
 
 
190
 
    if ((show_subset == Subset.MAIN &&
191
 
         !contact.is_main) ||
192
 
        (show_subset == Subset.OTHER &&
193
 
         contact.is_main))
194
 
      return false;
195
 
 
196
 
    if (filter_values == null || filter_values.length == 0)
197
 
      return true;
198
 
 
199
 
    return contact.contains_strings (filter_values);
200
 
  }
201
 
 
202
 
  public bool is_first (TreeIter iter) {
203
 
    ContactData *data;
204
 
    list_store.get (iter, 1, out data);
205
 
    if (data != null)
206
 
      return data->is_first;
207
 
    return false;
208
 
  }
209
 
 
210
 
  private ContactData? get_previous (ContactData data) {
211
 
    ContactData *previous = null;
212
 
    TreeIter iter = data.iter;
213
 
    if (list_store.iter_previous (ref iter))
214
 
      list_store.get (iter, 1, out previous);
215
 
    return previous;
216
 
  }
217
 
 
218
 
  private ContactData? get_next (ContactData data) {
219
 
    ContactData *next = null;
220
 
    TreeIter iter = data.iter;
221
 
    if (list_store.iter_next (ref iter))
222
 
      list_store.get (iter, 1, out next);
223
 
    return next;
224
 
  }
225
 
 
226
 
  private void row_changed_no_resort (ContactData data) {
227
 
    var path = list_store.get_path (data.iter);
228
 
    list_store.row_changed (path, data.iter);
229
 
  }
230
 
 
231
 
  private void row_changed_resort (ContactData data) {
232
 
    list_store.set (data.iter, 0, data.contact);
233
 
  }
234
 
 
235
 
  private bool update_is_first (ContactData data, ContactData? previous) {
236
 
    bool old_is_first = data.is_first;
237
 
 
238
 
    bool is_custom = data.sort_prio != 0;
239
 
    bool previous_is_custom = previous != null && (previous.sort_prio != 0) ;
240
 
 
241
 
    if (is_custom) {
242
 
      data.is_first = false;
243
 
    } else if (previous != null && !previous_is_custom) {
244
 
      unichar previous_initial = previous.contact.initial_letter;
245
 
      unichar initial = data.contact.initial_letter;
246
 
      data.is_first = previous_initial != initial;
247
 
    } else {
248
 
      data.is_first = true;
249
 
    }
250
 
 
251
 
    if (old_is_first != data.is_first) {
252
 
      row_changed_no_resort (data);
253
 
      return true;
254
 
    }
255
 
 
256
 
    return false;
257
 
  }
258
 
 
259
 
  private void add_custom_headers () {
260
 
    suggestions_header_data.visible = true;
261
 
    list_store.append (out suggestions_header_data.iter);
262
 
    list_store.set (suggestions_header_data.iter, 1, suggestions_header_data);
263
 
    padding_data.visible = true;
264
 
    list_store.append (out padding_data.iter);
265
 
    list_store.set (padding_data.iter, 1, padding_data);
266
 
  }
267
 
 
268
 
  private void remove_custom_headers () {
269
 
    suggestions_header_data.visible = false;
270
 
    list_store.remove (suggestions_header_data.iter);
271
 
    padding_data.visible = false;
272
 
    list_store.remove (padding_data.iter);
273
 
  }
274
 
 
275
 
  private void add_to_model (ContactData data) {
276
 
    list_store.append (out data.iter);
277
 
    list_store.set (data.iter, 0, data.contact, 1, data);
278
 
 
279
 
    if (data.sort_prio > 0) {
280
 
      if (custom_visible_count++ == 0)
281
 
        add_custom_headers ();
282
 
    }
283
 
 
284
 
    if (update_is_first (data, get_previous (data)) && data.is_first) {
285
 
      /* The newly added row is first, the next one might not be anymore */
286
 
      var next = get_next (data);
287
 
      if (next != null)
288
 
        update_is_first (next, data);
289
 
    }
290
 
  }
291
 
 
292
 
  private void remove_from_model (ContactData data) {
293
 
    if (data.sort_prio > 0) {
294
 
      if (custom_visible_count-- == 1)
295
 
        remove_custom_headers ();
296
 
    }
297
 
 
298
 
    ContactData? next = null;
299
 
    if (data.is_first)
300
 
      next = get_next (data);
301
 
 
302
 
    list_store.remove (data.iter);
303
 
    data.is_first = false;
304
 
 
305
 
    if (next != null)
306
 
      update_is_first (next, get_previous (next));
307
 
  }
308
 
 
309
 
  private void update_visible (ContactData data) {
310
 
    bool was_visible = data.visible;
311
 
    data.visible = apply_filter (data.contact);
312
 
 
313
 
    if (was_visible && !data.visible)
314
 
      remove_from_model (data);
315
 
 
316
 
    if (!was_visible && data.visible)
317
 
      add_to_model (data);
318
 
  }
319
 
 
320
 
  private void refilter () {
321
 
    foreach (var c in contacts_store.get_contacts ()) {
322
 
      update_visible (lookup_data (c));
323
 
    }
 
137
    child_changed (data.grid);
324
138
  }
325
139
 
326
140
  public void hide_contact (Contact contact) {
327
141
    hidden_contacts.add (contact);
 
142
    update_all_filtered ();
328
143
    refilter ();
329
144
  }
330
145
 
331
146
  public void set_filter_values (string []? values) {
332
147
    filter_values = values;
 
148
    update_all_filtered ();
333
149
    refilter ();
334
150
  }
335
151
 
 
152
  private bool calculate_filtered (Contact c) {
 
153
    if (c.is_hidden)
 
154
      return false;
 
155
 
 
156
    if (c in hidden_contacts)
 
157
      return false;
 
158
 
 
159
    if ((show_subset == Subset.MAIN &&
 
160
         !c.is_main) ||
 
161
        (show_subset == Subset.OTHER &&
 
162
         c.is_main))
 
163
      return false;
 
164
 
 
165
    if (filter_values == null || filter_values.length == 0)
 
166
      return true;
 
167
 
 
168
    return c.contains_strings (filter_values);
 
169
  }
 
170
 
 
171
  private void update_data (ContactData data) {
 
172
    var c = data.contact;
 
173
    data.display_name = c.display_name;
 
174
    data.initial_letter = c.initial_letter;
 
175
    data.filtered = calculate_filtered (c);
 
176
 
 
177
    data.label.set_markup (Markup.printf_escaped ("<span font='16px'>%s</span>", data.display_name));
 
178
    data.image_frame.set_image (c.individual, c);
 
179
  }
 
180
 
 
181
  private void update_all_filtered () {
 
182
    foreach (var data in contacts.values) {
 
183
      data.filtered = calculate_filtered (data.contact);
 
184
    }
 
185
  }
 
186
 
336
187
  private void contact_changed_cb (Store store, Contact c) {
337
 
    ContactData data = lookup_data (c);
338
 
 
339
 
    bool was_visible = data.visible;
340
 
 
341
 
    ContactData? next = null;
342
 
    if (data.visible)
343
 
      next = get_next (data);
344
 
 
345
 
    update_visible (data);
346
 
 
347
 
    if (was_visible && data.visible) {
348
 
      /* We just moved position in the list while visible */
349
 
 
350
 
      row_changed_resort (data);
351
 
 
352
 
      /* Update the is_first on the previous next row */
353
 
      if (next != null)
354
 
        update_is_first (next, get_previous (next));
355
 
 
356
 
      /* Update the is_first on the new next row */
357
 
      next = get_next (data);
358
 
      if (next != null)
359
 
        update_is_first (next, data);
360
 
    }
361
 
  }
362
 
 
363
 
  private ContactData lookup_data (Contact c) {
364
 
    return c.lookup<ContactData> (this);
 
188
    var data = contacts.get (c);
 
189
    update_data (data);
 
190
    child_changed (data.grid);
365
191
  }
366
192
 
367
193
  private void contact_added_cb (Store store, Contact c) {
368
 
    ContactData data =  new ContactData();
 
194
    var data =  new ContactData();
369
195
    data.contact = c;
370
 
    data.visible = false;
371
 
 
372
 
    c.set_lookup (this, data);
373
 
 
374
 
    update_visible (data);
 
196
    data.grid = new Grid ();
 
197
    data.grid.margin = 12;
 
198
    data.grid.set_column_spacing (10);
 
199
    data.image_frame = new ContactFrame (Contact.SMALL_AVATAR_SIZE);
 
200
    data.label = new Label ("");
 
201
    data.label.set_ellipsize (Pango.EllipsizeMode.END);
 
202
    data.label.set_valign (Align.START);
 
203
    data.label.set_halign (Align.START);
 
204
 
 
205
    data.grid.attach (data.image_frame, 0, 0, 1, 2);
 
206
    data.grid.attach (data.label, 1, 0, 1, 1);
 
207
 
 
208
    if (text_display == TextDisplay.PRESENCE) {
 
209
      var merged_presence = c.create_merged_presence_widget ();
 
210
      merged_presence.set_halign (Align.START);
 
211
      merged_presence.set_valign (Align.END);
 
212
      merged_presence.set_vexpand (false);
 
213
      merged_presence.set_margin_bottom (4);
 
214
 
 
215
      data.grid.attach (merged_presence,  1, 1, 1, 1);
 
216
    }
 
217
 
 
218
    if (text_display == TextDisplay.STORES) {
 
219
      var stores = new Label ("");
 
220
      stores.set_markup (Markup.printf_escaped ("<span font='12px'>%s</span>",
 
221
                                                c.format_persona_stores ()));
 
222
 
 
223
      stores.set_ellipsize (Pango.EllipsizeMode.END);
 
224
      stores.set_halign (Align.START);
 
225
      data.grid.attach (stores,  1, 1, 1, 1);
 
226
    }
 
227
 
 
228
    update_data (data);
 
229
 
 
230
    data.grid.set_data<ContactData> ("data", data);
 
231
    data.grid.show_all ();
 
232
    contacts.set (c, data);
 
233
    this.add (data.grid);
375
234
  }
376
235
 
377
236
  private void contact_removed_cb (Store store, Contact c) {
378
 
    var data = lookup_data (c);
379
 
 
380
 
    if (data.visible)
381
 
      remove_from_model (data);
382
 
 
383
 
    c.remove_lookup<ContactData> (this);
384
 
  }
385
 
 
386
 
  public bool lookup_iter (Contact c, out TreeIter iter) {
387
 
    var data = lookup_data (c);
388
 
    iter = data.iter;
389
 
    return data.visible;
390
 
  }
391
 
 
392
 
 
393
 
 
394
 
  private CellRendererShape shape;
395
 
  public enum TextDisplay {
396
 
    NONE,
397
 
    PRESENCE,
398
 
    STORES
399
 
  }
400
 
  private TextDisplay text_display;
401
 
 
402
 
  public signal void selection_changed (Contact? contact);
403
 
 
404
 
  private void init_view (TextDisplay text_display) {
405
 
    this.text_display = text_display;
406
 
 
407
 
    set_model (list_store);
408
 
    set_headers_visible (false);
409
 
 
410
 
    var row_padding = 12;
411
 
 
412
 
    var selection = get_selection ();
413
 
    selection.set_mode (SelectionMode.BROWSE);
414
 
    selection.set_select_function ( (selection, model, path, path_currently_selected) => {
415
 
        Contact contact;
416
 
        TreeIter iter;
417
 
        model.get_iter (out iter, path);
418
 
        model.get (iter, 0, out contact);
419
 
        return contact != null;
420
 
      });
421
 
    selection.changed.connect (contacts_selection_changed);
422
 
 
423
 
    var column = new TreeViewColumn ();
424
 
    column.set_spacing (8);
425
 
 
426
 
    var icon = new CellRendererPixbuf ();
427
 
    icon.set_padding (0, row_padding);
428
 
    icon.xalign = 1.0f;
429
 
    icon.yalign = 0.0f;
430
 
    icon.width = Contact.SMALL_AVATAR_SIZE + 12;
431
 
    column.pack_start (icon, false);
432
 
    column.set_cell_data_func (icon, (column, cell, model, iter) => {
433
 
        Contact contact;
434
 
 
435
 
        model.get (iter, 0, out contact);
436
 
 
437
 
        if (contact == null) {
438
 
          cell.set ("pixbuf", null);
439
 
          cell.visible = false;
440
 
          return;
441
 
        }
442
 
        cell.visible = true;
443
 
 
444
 
        if (contact != null)
445
 
          cell.set ("pixbuf", contact.small_avatar);
446
 
        else
447
 
          cell.set ("pixbuf", null);
448
 
      });
449
 
 
450
 
    shape = new CellRendererShape ();
451
 
    shape.set_padding (0, row_padding);
452
 
 
453
 
    Pango.cairo_context_set_shape_renderer (get_pango_context (), shape.render_shape);
454
 
 
455
 
    column.pack_start (shape, true);
456
 
    column.set_cell_data_func (shape, (column, cell, model, iter) => {
457
 
        Contact contact;
458
 
 
459
 
        model.get (iter, 0, out contact);
460
 
 
461
 
        if (contact == null) {
462
 
          cell.visible = false;
463
 
          return;
464
 
        }
465
 
        cell.visible = true;
466
 
 
467
 
        var name = contact.display_name;
468
 
        switch (text_display) {
469
 
        default:
470
 
        case TextDisplay.NONE:
471
 
          cell.set ("name", name,
472
 
                    "show_presence", false,
473
 
                    "message", "");
474
 
          break;
475
 
        case TextDisplay.PRESENCE:
476
 
          cell.set ("name", name,
477
 
                    "show_presence", true,
478
 
                    "presence", contact.presence_type,
479
 
                    "message", contact.presence_message,
480
 
                    "is_phone", contact.is_phone);
481
 
          break;
482
 
        case TextDisplay.STORES:
483
 
          string stores = contact.format_persona_stores ();
484
 
          cell.set ("name", name,
485
 
                    "show_presence", false,
486
 
                    "message", stores);
487
 
          break;
488
 
        }
489
 
      });
490
 
 
491
 
    var text = new CellRendererText ();
492
 
    text.set_alignment (0, 0);
493
 
    column.pack_start (text, true);
494
 
    text.set ("weight", Pango.Weight.BOLD);
495
 
    column.set_cell_data_func (text, (column, cell, model, iter) => {
496
 
        Contact contact;
497
 
 
498
 
        model.get (iter, 0, out contact);
499
 
        cell.visible = contact == null;
500
 
        if (cell.visible) {
501
 
          string header = get_header_text (iter);
502
 
          cell.set ("text", header);
503
 
          if (header == "")
504
 
            cell.height = 6; // PADDING
505
 
          else
506
 
            cell.height = -1;
507
 
        }
508
 
      });
509
 
 
510
 
    append_column (column);
511
 
  }
512
 
 
513
 
  private void contacts_selection_changed (TreeSelection selection) {
514
 
    TreeIter iter;
515
 
    TreeModel model;
516
 
 
517
 
    Contact? contact = null;
518
 
    if (selection.get_selected (out model, out iter)) {
519
 
      model.get (iter, 0, out contact);
520
 
    }
521
 
 
 
237
    var data = contacts.get (c);
 
238
    data.grid.destroy ();
 
239
    data.label.destroy ();
 
240
    data.image_frame.destroy ();
 
241
    contacts.unset (c);
 
242
  }
 
243
 
 
244
  public override void child_selected (Widget? child) {
 
245
    var data = child.get_data<ContactData> ("data");
 
246
    var contact = data != null ? data.contact : null;
522
247
    selection_changed (contact);
 
248
    if (contact != null)
 
249
      contact.fetch_contact_info ();
 
250
  }
 
251
 
 
252
  private bool filter (Widget child) {
 
253
    var data = child.get_data<ContactData> ("data");
 
254
 
 
255
    return data.filtered;
 
256
  }
 
257
 
 
258
  private void update_separator (ref Widget? separator,
 
259
                                 Widget widget,
 
260
                                 Widget? before_widget) {
 
261
    var w_data = widget.get_data<ContactData> ("data");
 
262
    ContactData? before_data = null;
 
263
    if (before_widget != null)
 
264
      before_data = before_widget.get_data<ContactData> ("data");
 
265
 
 
266
    if (before_data == null && w_data.sort_prio > 0) {
 
267
      if (separator == null ||
 
268
          !(separator.get_data<bool> ("contacts-suggestions-header"))) {
 
269
        var l = new Label ("");
 
270
        l.set_data ("contacts-suggestions-header", true);
 
271
        l.set_markup (Markup.printf_escaped ("<b>%s</b>", _("Suggestions")));
 
272
        l.set_halign (Align.START);
 
273
        separator = l;
 
274
      }
 
275
      return;
 
276
    }
 
277
 
 
278
    if (before_data != null && before_data.sort_prio > 0 &&
 
279
        w_data.sort_prio == 0) {
 
280
      if (separator == null ||
 
281
          !(separator.get_data<bool> ("contacts-rest-header"))) {
 
282
        var l = new Label ("");
 
283
        l.set_data ("contacts-rest-header", true);
 
284
        l.set_halign (Align.START);
 
285
        separator = l;
 
286
      }
 
287
      return;
 
288
    }
 
289
 
 
290
    if (is_other (w_data) &&
 
291
        (before_data == null || !is_other (before_data))) {
 
292
      if (separator == null ||
 
293
          !(separator.get_data<bool> ("contacts-other-header"))) {
 
294
        var l = new Label ("");
 
295
        l.set_data ("contacts-other-header", true);
 
296
        l.set_markup (Markup.printf_escaped ("<b>%s</b>", _("Other Contacts")));
 
297
        l.set_halign (Align.START);
 
298
        separator = l;
 
299
      }
 
300
      return;
 
301
    }
 
302
 
 
303
    if (before_data != null &&
 
304
        w_data.initial_letter != before_data.initial_letter) {
 
305
      if (separator == null || !(separator is Separator))
 
306
        separator = new Separator (Orientation.HORIZONTAL);
 
307
      return;
 
308
    }
 
309
    separator = null;
523
310
  }
524
311
 
525
312
  public void select_contact (Contact contact) {
526
 
    TreeIter iter;
527
 
    if (lookup_iter (contact, out iter)) {
528
 
      get_selection ().select_iter (iter);
529
 
      scroll_to_cell (list_store.get_path (iter),
530
 
                      null, true, 0.0f, 0.0f);
531
 
    }
 
313
    var data = contacts.get (contact);
 
314
    select_child (data.grid);
532
315
  }
533
316
}