gnome-contacts/src/contacts-view.vala
2011-12-16 18:01:08 +01:00

485 lines
12 KiB
Vala

/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
/*
* Copyright (C) 2011 Alexander Larsson <alexl@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Gtk;
using Folks;
using Gee;
public class Contacts.View : GLib.Object {
private class ContactData {
public Contact contact;
public TreeIter iter;
public bool visible;
public bool is_first;
public int sort_prio;
}
Store contacts_store;
ListStore list_store;
HashSet<Contact> hidden_contacts;
string []? filter_values;
int custom_visible_count;
ContactData header_data;
ContactData padding_data;
public View (Store store) {
contacts_store = store;
hidden_contacts = new HashSet<Contact>();
list_store = new ListStore (2, typeof (Contact), typeof (ContactData *));
header_data = new ContactData ();
header_data.sort_prio = int.MAX;
padding_data = new ContactData ();
padding_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);
if (aa->sort_prio > bb->sort_prio)
return -1;
if (aa->sort_prio < bb->sort_prio)
return 1;
var a = aa->contact;
var b = bb->contact;
if (is_set (a.display_name) && is_set (b.display_name))
return a.display_name.collate (b.display_name);
// Sort empty names last
if (is_set (a.display_name))
return -1;
if (is_set (b.display_name))
return 1;
return 0;
});
list_store.set_sort_column_id (0, SortType.ASCENDING);
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);
}
public string get_header_text (TreeIter iter) {
ContactData *data;
model.get (iter, 1, out data);
if (data == 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);
}
return "";
}
public void add_custom_sort (Contact c, int prio) {
var data = lookup_data (c);
// 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 ();
}
}
}
public TreeModel model { get { return list_store; } }
private bool apply_filter (Contact contact) {
if (contact.is_hidden)
return false;
if (contact in hidden_contacts)
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;
}
private void add_custom_headers () {
header_data.visible = true;
list_store.append (out header_data.iter);
list_store.set (header_data.iter, 1, header_data);
padding_data.visible = true;
list_store.append (out padding_data.iter);
list_store.set (padding_data.iter, 1, padding_data);
}
private void remove_custom_headers () {
header_data.visible = false;
list_store.remove (header_data.iter);
padding_data.visible = false;
list_store.remove (padding_data.iter);
}
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 ();
}
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)
add_to_model (data);
if (was_visible && !data.visible)
remove_from_model (data);
}
private void refilter () {
foreach (var c in contacts_store.get_contacts ()) {
update_visible (lookup_data (c));
}
}
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 */
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);
}
}
private ContactData lookup_data (Contact c) {
return c.lookup<ContactData> (this);
}
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);
}
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);
}
public bool lookup_iter (Contact c, out TreeIter iter) {
var data = lookup_data (c);
iter = data.iter;
return data.visible;
}
}
public class Contacts.ViewWidget : TreeView {
public View view;
private CellRendererShape shape;
public enum TextDisplay {
NONE,
PRESENCE,
STORES
}
private TextDisplay text_display;
public signal void selection_changed (Contact? contact);
public ViewWidget (View view, TextDisplay text_display = TextDisplay.PRESENCE) {
this.view = view;
this.text_display = text_display;
set_model (view.model);
set_headers_visible (false);
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);
view.model.get (iter, 0, out contact);
return contact != null;
});
selection.changed.connect (contacts_selection_changed);
var column = new TreeViewColumn ();
var text = new CellRendererText ();
text.set_alignment (0, 0);
column.pack_start (text, false);
text.set ("weight", Pango.Weight.BOLD, "scale", 1.28, "width", 24);
column.set_cell_data_func (text, (column, cell, model, iter) => {
string letter = "";
if (view.is_first (iter)) {
Contact contact;
view.model.get (iter, 0, out contact);
letter = contact.initial_letter.to_string ();
}
cell.set ("text", letter);
if (letter != "") {
cell.height = -1;
} else {
cell.height = 1;
}
});
var icon = new CellRendererPixbuf ();
icon.xalign = 0.0f;
icon.yalign = 0.0f;
icon.width = 48 + 2;
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.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 (4, 0);
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 = "";
bool first = true;
foreach (var p in contact.individual.personas) {
if (!first)
stores += ", ";
stores += Contact.format_persona_store_name (p.store);
first = false;
}
cell.set ("name", name,
"show_presence", false,
"message", stores);
break;
}
});
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 = view.get_header_text (iter);
cell.set ("text", header);
if (header == "")
cell.height = 6; // PADDING
else
cell.height = -1;
}
});
append_column (column);
}
private void contacts_selection_changed (TreeSelection selection) {
TreeIter iter;
TreeModel model;
Contact? contact = null;
if (selection.get_selected (out model, out iter)) {
model.get (iter, 0, out contact);
}
selection_changed (contact);
}
public void select_contact (Contact contact) {
TreeIter iter;
if (view.lookup_iter (contact, out iter)) {
get_selection ().select_iter (iter);
scroll_to_cell (view.model.get_path (iter),
null, true, 0.0f, 0.0f);
}
}
}