Compare commits
51 commits
master
...
wip/sorted
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6f4030530b | ||
![]() |
aa084b0111 | ||
![]() |
d09f179b9d | ||
![]() |
dba1129de7 | ||
![]() |
bc0eaef4f1 | ||
![]() |
4c1289d637 | ||
![]() |
5acaabc929 | ||
![]() |
976191f057 | ||
![]() |
49dfd78298 | ||
![]() |
62ce9fc103 | ||
![]() |
629da7939a | ||
![]() |
3afc08f557 | ||
![]() |
b7106199d4 | ||
![]() |
5cf772ef71 | ||
![]() |
bb54cc89a6 | ||
![]() |
2d5e2ad20e | ||
![]() |
d5a40ab91b | ||
![]() |
dba1a863f3 | ||
![]() |
1ab1cf76f9 | ||
![]() |
01df16164a | ||
![]() |
78d218cb2f | ||
![]() |
cc51439982 | ||
![]() |
7217975b00 | ||
![]() |
6da20da524 | ||
![]() |
6bfb93f1c0 | ||
![]() |
16e60db53e | ||
![]() |
d4958d150d | ||
![]() |
e6ec0e8b4a | ||
![]() |
7600e8f1d2 | ||
![]() |
3ecb5b4bc4 | ||
![]() |
b56d38b78d | ||
![]() |
c46616cdb5 | ||
![]() |
a06929cb46 | ||
![]() |
087200ef2c | ||
![]() |
3076139b41 | ||
![]() |
b45ddb74b5 | ||
![]() |
67e45d169a | ||
![]() |
3ac7deb791 | ||
![]() |
620bacad69 | ||
![]() |
d263103e38 | ||
![]() |
9c20c4f8bd | ||
![]() |
36aefcf17c | ||
![]() |
a5dbd116bc | ||
![]() |
2a3448f204 | ||
![]() |
e31fb4588e | ||
![]() |
bd8316edee | ||
![]() |
02d79e9779 | ||
![]() |
e2d418384c | ||
![]() |
e8d9f74a1f | ||
![]() |
0ca78f5ffa | ||
![]() |
ec2939c8a9 |
12 changed files with 1326 additions and 746 deletions
|
@ -19,11 +19,10 @@ AM_VALAFLAGS += -D HAVE_GSTREAMER @CONTACTS_GSTREAMER_PACKAGES@
|
|||
AM_CPPFLAGS += $(CONTACTS_GSTREAMER_CFLAGS)
|
||||
endif
|
||||
|
||||
bin_PROGRAMS = gnome-contacts
|
||||
bin_PROGRAMS = gnome-contacts test-sorted
|
||||
|
||||
vala_sources = \
|
||||
contacts-app.vala \
|
||||
contacts-cell-renderer-shape.vala \
|
||||
contacts-contact.vala \
|
||||
contacts-contact-pane.vala \
|
||||
contacts-types.vala \
|
||||
|
@ -32,6 +31,7 @@ vala_sources = \
|
|||
contacts-linking.vala \
|
||||
contacts-menu-button.vala \
|
||||
contacts-row.vala \
|
||||
contacts-sorted.vala \
|
||||
contacts-store.vala \
|
||||
contacts-view.vala \
|
||||
contacts-utils.vala \
|
||||
|
@ -67,6 +67,13 @@ gnome_contacts_SOURCES = \
|
|||
$(vala_sources) \
|
||||
$(NULL)
|
||||
|
||||
test_sorted_SOURCES = \
|
||||
contacts-sorted.vala \
|
||||
test-sorted.vala \
|
||||
$(NULL)
|
||||
test_sorted_LDADD = $(CONTACTS_LIBS)
|
||||
|
||||
|
||||
gnome_contacts_LDADD = $(CONTACTS_LIBS) -lm
|
||||
|
||||
if USE_GSTREAMER
|
||||
|
|
|
@ -59,7 +59,7 @@ public class Contacts.App : Gtk.Application {
|
|||
}
|
||||
|
||||
private void selection_changed (Contact? new_selection) {
|
||||
contacts_pane.show_contact (new_selection);
|
||||
contacts_pane.show_contact (new_selection, false, false);
|
||||
}
|
||||
|
||||
public void show_contact (Contact? contact) {
|
||||
|
|
|
@ -266,7 +266,7 @@ public class Contacts.AvatarDialog : Dialog {
|
|||
grid.attach (main_frame, 0, 0, 1, 1);
|
||||
|
||||
var label = new Label ("");
|
||||
label.set_markup ("<span font='16'>" + contact.display_name + "</span>");
|
||||
label.set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", contact.display_name));
|
||||
label.set_valign (Align.START);
|
||||
label.set_halign (Align.START);
|
||||
label.set_hexpand (true);
|
||||
|
|
|
@ -1,330 +0,0 @@
|
|||
/* -*- 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;
|
||||
|
||||
public class Contacts.CellRendererShape : Gtk.CellRenderer {
|
||||
public const int IMAGE_SIZE = 14;
|
||||
|
||||
private Widget current_widget;
|
||||
|
||||
public string name { get; set; }
|
||||
public PresenceType presence { get; set; }
|
||||
public string message { get; set; }
|
||||
public bool is_phone { get; set; }
|
||||
public bool show_presence { get; set; }
|
||||
const int default_width = 60;
|
||||
int renderer_height = Contact.SMALL_AVATAR_SIZE;
|
||||
|
||||
private struct IconShape {
|
||||
string icon;
|
||||
}
|
||||
|
||||
Gdk.Pixbuf? create_symbolic_pixbuf (Widget widget, string icon_name, int size) {
|
||||
var screen = widget. get_screen ();
|
||||
var icon_theme = Gtk.IconTheme.get_for_screen (screen);
|
||||
|
||||
var info = icon_theme.lookup_icon (icon_name, size, Gtk.IconLookupFlags.USE_BUILTIN);
|
||||
if (info == null)
|
||||
return null;
|
||||
|
||||
var context = widget.get_style_context ();
|
||||
|
||||
context.save ();
|
||||
bool is_symbolic;
|
||||
Gdk.Pixbuf? pixbuf = null;
|
||||
try {
|
||||
pixbuf = info.load_symbolic_for_context (context,
|
||||
out is_symbolic);
|
||||
} catch (Error e) {
|
||||
}
|
||||
context.restore ();
|
||||
|
||||
return pixbuf;
|
||||
}
|
||||
|
||||
private Pango.Layout get_name_layout (Widget widget,
|
||||
Gdk.Rectangle? cell_area,
|
||||
CellRendererState flags) {
|
||||
Pango.Layout layout;
|
||||
int xpad;
|
||||
|
||||
var attr_list = new Pango.AttrList ();
|
||||
|
||||
layout = widget.create_pango_layout (name);
|
||||
|
||||
var attr = new Pango.AttrSize (13 * Pango.SCALE);
|
||||
attr.absolute = 1;
|
||||
attr.start_index = 0;
|
||||
attr.end_index = attr.start_index + name.length;
|
||||
attr_list.insert ((owned) attr);
|
||||
|
||||
/* Now apply the attributes as they will effect the outcome
|
||||
* of pango_layout_get_extents() */
|
||||
layout.set_attributes (attr_list);
|
||||
|
||||
// We only look at xpad, and only use it for the left side...
|
||||
get_padding (out xpad, null);
|
||||
|
||||
layout.set_ellipsize (Pango.EllipsizeMode.END);
|
||||
|
||||
Pango.Rectangle rect;
|
||||
int width, text_width;
|
||||
|
||||
layout.get_extents (null, out rect);
|
||||
text_width = rect.width;
|
||||
|
||||
if (cell_area != null)
|
||||
width = (cell_area.width - xpad) * Pango.SCALE;
|
||||
else
|
||||
width = default_width * Pango.SCALE;
|
||||
|
||||
width = int.min (width, text_width);
|
||||
|
||||
layout.set_width (width);
|
||||
|
||||
layout.set_wrap (Pango.WrapMode.WORD_CHAR);
|
||||
layout.set_height (-2);
|
||||
|
||||
Pango.Alignment align;
|
||||
if (widget.get_direction () == TextDirection.RTL)
|
||||
align = Pango.Alignment.RIGHT;
|
||||
else
|
||||
align = Pango.Alignment.LEFT;
|
||||
layout.set_alignment (align);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
private Pango.Layout get_presence_layout (Widget widget,
|
||||
Gdk.Rectangle? cell_area,
|
||||
CellRendererState flags) {
|
||||
Pango.Layout layout;
|
||||
int xpad;
|
||||
Pango.Rectangle r = { 0, -CellRendererShape.IMAGE_SIZE*1024*7/10,
|
||||
CellRendererShape.IMAGE_SIZE*1024, CellRendererShape.IMAGE_SIZE*1024 };
|
||||
|
||||
var attr_list = new Pango.AttrList ();
|
||||
|
||||
string? str = "";
|
||||
string? iconname = Contact.presence_to_icon (presence);
|
||||
if (iconname != null && show_presence) {
|
||||
str += "*";
|
||||
IconShape icon_shape = IconShape();
|
||||
icon_shape.icon = iconname;
|
||||
var a = new Pango.AttrShape<IconShape?>.with_data (r, r, icon_shape, (s) => { return s;} );
|
||||
a.start_index = 0;
|
||||
a.end_index = 1;
|
||||
attr_list.insert ((owned) a);
|
||||
str += " ";
|
||||
}
|
||||
if (message != null && (!show_presence || iconname != null)) {
|
||||
string m = message;
|
||||
if (m.length == 0)
|
||||
m = Contact.presence_to_string (presence);
|
||||
|
||||
var attr = new Pango.AttrSize (9 * Pango.SCALE);
|
||||
attr.absolute = 1;
|
||||
attr.start_index = str.length;
|
||||
attr.end_index = attr.start_index + m.length;
|
||||
attr_list.insert ((owned) attr);
|
||||
str += m;
|
||||
|
||||
if (is_phone && show_presence) {
|
||||
var icon_shape = IconShape();
|
||||
icon_shape.icon = "phone-symbolic";
|
||||
var a = new Pango.AttrShape<IconShape?>.with_data (r, r, icon_shape, (s) => { return s;});
|
||||
a.start_index = str.length;
|
||||
str += "*";
|
||||
a.end_index = str.length;
|
||||
attr_list.insert ((owned) a);
|
||||
}
|
||||
}
|
||||
|
||||
layout = widget.create_pango_layout (str);
|
||||
|
||||
get_padding (out xpad, null);
|
||||
|
||||
/* Now apply the attributes as they will effect the outcome
|
||||
* of pango_layout_get_extents() */
|
||||
layout.set_attributes (attr_list);
|
||||
|
||||
layout.set_ellipsize (Pango.EllipsizeMode.END);
|
||||
|
||||
Pango.Rectangle rect;
|
||||
int width, text_width;
|
||||
|
||||
layout.get_extents (null, out rect);
|
||||
text_width = rect.width;
|
||||
|
||||
if (cell_area != null)
|
||||
width = (cell_area.width - xpad) * Pango.SCALE;
|
||||
else
|
||||
width = default_width * Pango.SCALE;
|
||||
|
||||
width = int.min (width, text_width);
|
||||
|
||||
layout.set_width (width);
|
||||
|
||||
layout.set_wrap (Pango.WrapMode.WORD_CHAR);
|
||||
|
||||
layout.set_height (-1);
|
||||
|
||||
Pango.Alignment align;
|
||||
if (widget.get_direction () == TextDirection.RTL)
|
||||
align = Pango.Alignment.RIGHT;
|
||||
else
|
||||
align = Pango.Alignment.LEFT;
|
||||
layout.set_alignment (align);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
public override void get_size (Widget widget,
|
||||
Gdk.Rectangle? cell_area,
|
||||
out int x_offset,
|
||||
out int y_offset,
|
||||
out int width,
|
||||
out int height) {
|
||||
x_offset = y_offset = width = height = 0;
|
||||
// Not used
|
||||
}
|
||||
|
||||
private void do_get_size (Widget widget,
|
||||
Gdk.Rectangle? cell_area,
|
||||
Pango.Layout? layout,
|
||||
out int x_offset,
|
||||
out int y_offset) {
|
||||
Pango.Rectangle rect;
|
||||
int xpad, ypad;
|
||||
|
||||
get_padding (out xpad, out ypad);
|
||||
|
||||
layout.get_pixel_extents (null, out rect);
|
||||
|
||||
if (cell_area != null) {
|
||||
rect.width = int.min (rect.width, cell_area.width - 2 * xpad);
|
||||
|
||||
if (widget.get_direction () == TextDirection.RTL)
|
||||
x_offset = cell_area.width - (rect.width + xpad);
|
||||
else
|
||||
x_offset = xpad;
|
||||
|
||||
x_offset = int.max (x_offset, 0);
|
||||
} else {
|
||||
x_offset = 0;
|
||||
}
|
||||
|
||||
y_offset = ypad;
|
||||
}
|
||||
|
||||
public override void render (Cairo.Context cr,
|
||||
Widget widget,
|
||||
Gdk.Rectangle background_area,
|
||||
Gdk.Rectangle cell_area,
|
||||
CellRendererState flags) {
|
||||
StyleContext context;
|
||||
Pango.Layout name_layout, presence_layout;
|
||||
int name_x_offset = 0;
|
||||
int presence_x_offset = 0;
|
||||
int name_y_offset = 0;
|
||||
int presence_y_offset = 0;
|
||||
int xpad;
|
||||
Pango.Rectangle name_rect;
|
||||
Pango.Rectangle presence_rect;
|
||||
|
||||
current_widget = widget;
|
||||
|
||||
context = widget.get_style_context ();
|
||||
get_padding (out xpad, null);
|
||||
|
||||
name_layout = get_name_layout (widget, cell_area, flags);
|
||||
do_get_size (widget, cell_area, name_layout, out name_x_offset, out name_y_offset);
|
||||
name_layout.get_pixel_extents (null, out name_rect);
|
||||
name_x_offset = name_x_offset - name_rect.x;
|
||||
|
||||
presence_layout = null;
|
||||
if (name_layout.get_lines_readonly ().length () == 1) {
|
||||
presence_layout = get_presence_layout (widget, cell_area, flags);
|
||||
do_get_size (widget, cell_area, presence_layout, out presence_x_offset, out presence_y_offset);
|
||||
presence_layout.get_pixel_extents (null, out presence_rect);
|
||||
presence_x_offset = presence_x_offset - presence_rect.x;
|
||||
}
|
||||
|
||||
cr.save ();
|
||||
|
||||
Gdk.cairo_rectangle (cr, cell_area);
|
||||
cr.clip ();
|
||||
|
||||
context.render_layout (cr,
|
||||
cell_area.x + name_x_offset,
|
||||
cell_area.y + name_y_offset,
|
||||
name_layout);
|
||||
|
||||
if (presence_layout != null)
|
||||
context.render_layout (cr,
|
||||
cell_area.x + presence_x_offset,
|
||||
cell_area.y + presence_y_offset + renderer_height - 11 - presence_layout.get_baseline () / Pango.SCALE,
|
||||
presence_layout);
|
||||
|
||||
cr.restore ();
|
||||
}
|
||||
|
||||
public override void get_preferred_width (Widget widget,
|
||||
out int min_width,
|
||||
out int nat_width) {
|
||||
int xpad;
|
||||
|
||||
get_padding (out xpad, null);
|
||||
|
||||
nat_width = min_width = xpad + default_width;
|
||||
}
|
||||
|
||||
public override void get_preferred_height_for_width (Widget widget,
|
||||
int width,
|
||||
out int minimum_height,
|
||||
out int natural_height) {
|
||||
int ypad;
|
||||
|
||||
get_padding (null, out ypad);
|
||||
minimum_height = renderer_height + ypad;
|
||||
natural_height = renderer_height + ypad;
|
||||
}
|
||||
|
||||
public override void get_preferred_height (Widget widget,
|
||||
out int minimum_size,
|
||||
out int natural_size) {
|
||||
int min_width;
|
||||
|
||||
get_preferred_width (widget, out min_width, null);
|
||||
get_preferred_height_for_width (widget, min_width,
|
||||
out minimum_size, out natural_size);
|
||||
}
|
||||
|
||||
public void render_shape (Cairo.Context cr, Pango.AttrShape attr, bool do_path) {
|
||||
unowned Pango.AttrShape<IconShape?> sattr = (Pango.AttrShape<IconShape?>)attr;
|
||||
var pixbuf = create_symbolic_pixbuf (current_widget, sattr.data.icon, IMAGE_SIZE);
|
||||
if (pixbuf != null) {
|
||||
double x, y;
|
||||
cr.get_current_point (out x, out y);
|
||||
Gdk.cairo_set_source_pixbuf (cr, pixbuf, x, y-IMAGE_SIZE*0.7);
|
||||
cr.paint();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -204,7 +204,7 @@ public class Contacts.FieldRow : Contacts.Row {
|
|||
public void pack_header (string s) {
|
||||
var l = new Label (s);
|
||||
l.set_markup (
|
||||
"<span font='24px'>%s</span>".printf (s));
|
||||
Markup.printf_escaped ("<span font='24px'>%s</span>", s));
|
||||
l.set_halign (Align.START);
|
||||
pack (l);
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ public class Contacts.FieldRow : Contacts.Row {
|
|||
var l = new Label (s);
|
||||
label = l;
|
||||
l.set_markup (
|
||||
"<span font='24px'>%s</span>".printf (s));
|
||||
Markup.printf_escaped ("<span font='24px'>%s</span>", s));
|
||||
l.set_halign (Align.START);
|
||||
l.set_hexpand (true);
|
||||
|
||||
|
@ -1552,7 +1552,7 @@ public class Contacts.ContactPane : ScrolledWindow {
|
|||
l.xalign = 0.0f;
|
||||
|
||||
contact.keep_widget_uptodate (l, (w) => {
|
||||
(w as Label).set_markup ("<span font='16'>" + contact.display_name + "</span>");
|
||||
(w as Label).set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", contact.display_name));
|
||||
});
|
||||
|
||||
var event_box = new EventBox ();
|
||||
|
@ -1609,7 +1609,7 @@ public class Contacts.ContactPane : ScrolledWindow {
|
|||
if (save && changed) {
|
||||
// Things look better if we update immediately, rather than after the setting has
|
||||
// been applied
|
||||
l.set_markup ("<span font='16'>" + entry.get_text () + "</span>");
|
||||
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 ());
|
||||
|
@ -1620,7 +1620,7 @@ public class Contacts.ContactPane : ScrolledWindow {
|
|||
set_individual_property.end (result);
|
||||
} catch (Error e) {
|
||||
App.app.show_message (e.message);
|
||||
l.set_markup ("<span font='16'>" + contact.display_name + "</span>");
|
||||
l.set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", contact.display_name));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1763,9 +1763,9 @@ public class Contacts.ContactPane : ScrolledWindow {
|
|||
|
||||
var label = new Label ("");
|
||||
if (contact.is_main)
|
||||
label.set_markup (_("Does %s from %s belong here?").printf (c.display_name, c.format_persona_stores ()));
|
||||
label.set_markup (Markup.printf_escaped (_("Does %s from %s belong here?"), c.display_name, c.format_persona_stores ()));
|
||||
else
|
||||
label.set_markup (_("Do these details belong to %s?").printf (c.display_name));
|
||||
label.set_markup (Markup.printf_escaped (_("Do these details belong to %s?"), c.display_name));
|
||||
label.set_valign (Align.START);
|
||||
label.set_halign (Align.START);
|
||||
label.set_line_wrap (true);
|
||||
|
@ -1802,7 +1802,13 @@ public class Contacts.ContactPane : ScrolledWindow {
|
|||
grid.attach (bbox, 2, 0, 1, 2);
|
||||
}
|
||||
|
||||
public void update_personas () {
|
||||
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 ();
|
||||
}
|
||||
|
@ -1818,18 +1824,20 @@ public class Contacts.ContactPane : ScrolledWindow {
|
|||
personas_grid.add (sheet);
|
||||
}
|
||||
|
||||
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 (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) {
|
||||
public void show_contact (Contact? new_contact, bool edit=false, bool show_matches = true) {
|
||||
if (contact == new_contact)
|
||||
return;
|
||||
|
||||
|
@ -1844,7 +1852,14 @@ public class Contacts.ContactPane : ScrolledWindow {
|
|||
contact = new_contact;
|
||||
|
||||
update_card ();
|
||||
update_personas ();
|
||||
update_personas (show_matches);
|
||||
|
||||
if (!show_matches) {
|
||||
update_personas_timeout = Gdk.threads_add_timeout (100, () => {
|
||||
update_personas ();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
bool can_remove = false;
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ public class Contacts.ContactPresence : Grid {
|
|||
if (message.length == 0)
|
||||
message = Contact.presence_to_string (type);
|
||||
|
||||
label.set_markup ("<span font='11px'>" + message + "</span>");
|
||||
label.set_markup (Markup.printf_escaped ("<span font='11px'>%s</span>", message));
|
||||
label.set_margin_bottom (3);
|
||||
|
||||
if (is_phone)
|
||||
|
@ -99,8 +99,13 @@ public class Contacts.ContactPresence : Grid {
|
|||
update_presence_widgets ();
|
||||
});
|
||||
|
||||
var id2 = contact.personas_changed.connect ( () => {
|
||||
update_presence_widgets ();
|
||||
});
|
||||
|
||||
this.destroy.connect (() => {
|
||||
contact.disconnect (id);
|
||||
contact.disconnect (id2);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ public class Contacts.LinkDialog : Dialog {
|
|||
persona_grid.attach (image_frame, 0, 0, 1, 2);
|
||||
|
||||
var label = new Label ("");
|
||||
label.set_markup ("<span font='13'>" + selected_contact.display_name + "</span>");
|
||||
label.set_markup (Markup.printf_escaped ("<span font='13'>%s</span>", selected_contact.display_name));
|
||||
label.set_valign (Align.START);
|
||||
label.set_halign (Align.START);
|
||||
label.set_hexpand (false);
|
||||
|
@ -61,7 +61,7 @@ public class Contacts.LinkDialog : Dialog {
|
|||
persona_grid.attach (label, 1, 0, 1, 1);
|
||||
|
||||
label = new Label ("");
|
||||
label.set_markup ("<span font='9'>" +selected_contact.format_persona_stores () + "</span>");
|
||||
label.set_markup (Markup.printf_escaped ("<span font='9'>%s</span>", selected_contact.format_persona_stores ()));
|
||||
label.set_valign (Align.START);
|
||||
label.set_halign (Align.START);
|
||||
label.set_hexpand (true);
|
||||
|
@ -179,7 +179,7 @@ public class Contacts.LinkDialog : Dialog {
|
|||
|
||||
var label = new Label ("");
|
||||
if (contact.is_main)
|
||||
label.set_markup (_("<span weight='bold'>Link contacts to %s</span>").printf (contact.display_name));
|
||||
label.set_markup (Markup.printf_escaped (_("<span weight='bold'>Link contacts to %s</span>"), contact.display_name));
|
||||
else
|
||||
label.set_markup (_("<span weight='bold'>Select contact to link to</span>"));
|
||||
label.set_valign (Align.CENTER);
|
||||
|
@ -220,8 +220,9 @@ public class Contacts.LinkDialog : Dialog {
|
|||
scrolled.set_vexpand (true);
|
||||
scrolled.set_hexpand (true);
|
||||
scrolled.set_shadow_type (ShadowType.NONE);
|
||||
scrolled.add (view);
|
||||
scrolled.add_with_viewport (view);
|
||||
list_grid.add (scrolled);
|
||||
view.set_focus_vadjustment (scrolled.get_vadjustment ());
|
||||
|
||||
view.selection_changed.connect ( (c) => {
|
||||
selected_contact = c;
|
||||
|
|
|
@ -138,12 +138,14 @@ public class Contacts.ListPane : Frame {
|
|||
grid.set_orientation (Orientation.VERTICAL);
|
||||
this.add (grid);
|
||||
|
||||
contacts_view.set_focus_vadjustment (scrolled.get_vadjustment ());
|
||||
|
||||
contacts_view.selection_changed.connect( (l, contact) => {
|
||||
if (!ignore_selection_change)
|
||||
selection_changed (contact);
|
||||
});
|
||||
|
||||
scrolled.add (contacts_view);
|
||||
scrolled.add_with_viewport (contacts_view);
|
||||
contacts_view.show_all ();
|
||||
scrolled.set_no_show_all (true);
|
||||
|
||||
|
@ -160,7 +162,6 @@ public class Contacts.ListPane : Frame {
|
|||
public void select_contact (Contact contact, bool ignore_change = false) {
|
||||
if (ignore_change)
|
||||
ignore_selection_change = true;
|
||||
contacts_view.select_contact (contact);
|
||||
ignore_selection_change = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,7 +147,7 @@ public class Contacts.SetupWindow : Gtk.Window {
|
|||
|
||||
var item = new ToolItem ();
|
||||
title_label = new Label ("");
|
||||
title_label.set_markup ("<b>%s</b>".printf (_("Contacts Setup")));
|
||||
title_label.set_markup (Markup.printf_escaped ("<b>%s</b>",_("Contacts Setup")));
|
||||
title_label.set_no_show_all (true);
|
||||
item.add (title_label);
|
||||
item.set_expand (true);
|
||||
|
|
917
src/contacts-sorted.vala
Normal file
917
src/contacts-sorted.vala
Normal file
|
@ -0,0 +1,917 @@
|
|||
/* -*- 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;
|
||||
|
||||
/* Requriements:
|
||||
+ sort
|
||||
+ filter
|
||||
+ first char or type custom "separators"
|
||||
(create, destroy, update)
|
||||
+ Work with largish sets of children
|
||||
+ selection and keynav
|
||||
+ activation (separate from selection)
|
||||
+ select mode (but not multiple)
|
||||
|
||||
settings:
|
||||
sort function
|
||||
filter function
|
||||
update_separator
|
||||
|
||||
ops:
|
||||
child-changed (resort, refilter,
|
||||
resort-all
|
||||
refilter-all
|
||||
|
||||
Impl:
|
||||
GSequence for children
|
||||
GHashTable for child to iter mapping
|
||||
*/
|
||||
|
||||
public class Contacts.Sorted : Container {
|
||||
public delegate bool FilterFunc (Widget child);
|
||||
public delegate void UpdateSeparatorFunc (ref Widget? separator, Widget child, Widget? before);
|
||||
|
||||
struct ChildInfo {
|
||||
Widget widget;
|
||||
Widget? separator;
|
||||
SequenceIter<ChildInfo?> iter;
|
||||
int y;
|
||||
int height;
|
||||
}
|
||||
|
||||
Sequence<ChildInfo?> children;
|
||||
HashTable<unowned Widget, unowned ChildInfo?> child_hash;
|
||||
CompareDataFunc<Widget>? sort_func;
|
||||
FilterFunc? filter_func;
|
||||
UpdateSeparatorFunc? update_separator_func;
|
||||
unowned ChildInfo? selected_child;
|
||||
unowned ChildInfo? prelight_child;
|
||||
unowned ChildInfo? cursor_child;
|
||||
private SelectionMode selection_mode;
|
||||
|
||||
private int do_sort (ChildInfo? a, ChildInfo? b) {
|
||||
return sort_func (a.widget, b.widget);
|
||||
}
|
||||
|
||||
public Sorted () {
|
||||
set_can_focus (true);
|
||||
set_has_window (true);
|
||||
set_redraw_on_allocate (true);
|
||||
|
||||
selection_mode = SelectionMode.SINGLE;
|
||||
|
||||
children = new Sequence<ChildInfo?>();
|
||||
child_hash = new HashTable<unowned Widget, unowned ChildInfo?> (GLib.direct_hash, GLib.direct_equal);
|
||||
}
|
||||
|
||||
[Signal (action=true)]
|
||||
public virtual signal void activate_row () {
|
||||
select_and_activate (cursor_child);
|
||||
}
|
||||
|
||||
[Signal (action=true)]
|
||||
public virtual signal void modify_selection () {
|
||||
if (cursor_child == null)
|
||||
return;
|
||||
|
||||
if (selection_mode == SelectionMode.SINGLE &&
|
||||
selected_child == cursor_child)
|
||||
update_selected (null);
|
||||
else
|
||||
select_and_activate (cursor_child);
|
||||
}
|
||||
|
||||
|
||||
[Signal (action=true)]
|
||||
public virtual signal void move_cursor (MovementStep step, int count) {
|
||||
Gdk.ModifierType state;
|
||||
|
||||
bool modify_selection_pressed = false;
|
||||
|
||||
if (Gtk.get_current_event_state (out state)) {
|
||||
var modify_mod_mask = this.get_modifier_mask (Gdk.ModifierIntent.MODIFY_SELECTION);
|
||||
if ((state & modify_mod_mask) == modify_mod_mask)
|
||||
modify_selection_pressed = true;
|
||||
}
|
||||
|
||||
unowned ChildInfo? child = null;
|
||||
switch (step) {
|
||||
case MovementStep.BUFFER_ENDS:
|
||||
if (count < 0)
|
||||
child = get_first_visible ();
|
||||
else
|
||||
child = get_last_visible ();
|
||||
break;
|
||||
case MovementStep.DISPLAY_LINES:
|
||||
if (cursor_child != null) {
|
||||
SequenceIter<ChildInfo?>? iter = cursor_child.iter;
|
||||
|
||||
while (count < 0 && iter != null) {
|
||||
iter = get_previous_visible (iter);
|
||||
count++;
|
||||
}
|
||||
while (count > 0 && iter != null) {
|
||||
iter = get_next_visible (iter);
|
||||
count--;
|
||||
}
|
||||
if (iter != null && !iter.is_end ()) {
|
||||
child = iter.get ();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MovementStep.PAGES:
|
||||
int page_size = 100;
|
||||
var vadj = get_focus_vadjustment ();
|
||||
if (vadj != null)
|
||||
page_size = (int) vadj.get_page_increment ();
|
||||
|
||||
if (cursor_child != null) {
|
||||
int start_y = cursor_child.y;
|
||||
int end_y = start_y;
|
||||
SequenceIter<ChildInfo?>? iter = cursor_child.iter;
|
||||
|
||||
child = cursor_child;
|
||||
if (count < 0) {
|
||||
/* Up */
|
||||
|
||||
while (iter != null && !iter.is_begin ()) {
|
||||
iter = get_previous_visible (iter);
|
||||
if (iter == null)
|
||||
break;
|
||||
unowned ChildInfo? prev = iter.get ();
|
||||
if (prev.y < start_y - page_size)
|
||||
break;
|
||||
child = prev;
|
||||
}
|
||||
} else {
|
||||
/* Down */
|
||||
|
||||
while (iter != null && !iter.is_end ()) {
|
||||
iter = get_next_visible (iter);
|
||||
if (iter.is_end ())
|
||||
break;
|
||||
unowned ChildInfo? next = iter.get ();
|
||||
if (next.y > start_y + page_size)
|
||||
break;
|
||||
child = next;
|
||||
}
|
||||
}
|
||||
end_y = child.y;
|
||||
if (end_y != start_y && vadj != null)
|
||||
vadj.value += end_y - start_y;
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (child == null) {
|
||||
error_bell ();
|
||||
return;
|
||||
}
|
||||
|
||||
update_cursor (child);
|
||||
if (!modify_selection_pressed)
|
||||
update_selected (child);
|
||||
}
|
||||
|
||||
private static void add_move_binding (BindingSet binding_set, uint keyval, Gdk.ModifierType modmask,
|
||||
MovementStep step, int count) {
|
||||
BindingEntry.add_signal (binding_set, keyval, modmask,
|
||||
"move-cursor", 2,
|
||||
typeof (MovementStep), step,
|
||||
typeof (int), count);
|
||||
|
||||
if ((modmask & Gdk.ModifierType.CONTROL_MASK) == Gdk.ModifierType.CONTROL_MASK)
|
||||
return;
|
||||
|
||||
BindingEntry.add_signal (binding_set, keyval, Gdk.ModifierType.CONTROL_MASK,
|
||||
"move-cursor", 2,
|
||||
typeof (MovementStep), step,
|
||||
typeof (int), count);
|
||||
}
|
||||
|
||||
[CCode (cname = "klass")]
|
||||
private static extern void *workaround_for_local_var_klass;
|
||||
static construct {
|
||||
unowned BindingSet binding_set = BindingSet.by_class (workaround_for_local_var_klass);
|
||||
|
||||
add_move_binding (binding_set, Gdk.Key.Home, 0,
|
||||
MovementStep.BUFFER_ENDS, -1);
|
||||
add_move_binding (binding_set, Gdk.Key.KP_Home, 0,
|
||||
MovementStep.BUFFER_ENDS, -1);
|
||||
|
||||
add_move_binding (binding_set, Gdk.Key.End, 0,
|
||||
MovementStep.BUFFER_ENDS, 1);
|
||||
add_move_binding (binding_set, Gdk.Key.KP_End, 0,
|
||||
MovementStep.BUFFER_ENDS, 1);
|
||||
|
||||
add_move_binding (binding_set, Gdk.Key.Up, Gdk.ModifierType.CONTROL_MASK,
|
||||
MovementStep.DISPLAY_LINES, -1);
|
||||
add_move_binding (binding_set, Gdk.Key.KP_Up, Gdk.ModifierType.CONTROL_MASK,
|
||||
MovementStep.DISPLAY_LINES, -1);
|
||||
|
||||
add_move_binding (binding_set, Gdk.Key.Down, Gdk.ModifierType.CONTROL_MASK,
|
||||
MovementStep.DISPLAY_LINES, 1);
|
||||
add_move_binding (binding_set, Gdk.Key.KP_Down, Gdk.ModifierType.CONTROL_MASK,
|
||||
MovementStep.DISPLAY_LINES, 1);
|
||||
|
||||
add_move_binding (binding_set, Gdk.Key.Page_Up, 0,
|
||||
MovementStep.PAGES, -1);
|
||||
add_move_binding (binding_set, Gdk.Key.KP_Page_Up, 0,
|
||||
MovementStep.PAGES, -1);
|
||||
|
||||
add_move_binding (binding_set, Gdk.Key.Page_Down, 0,
|
||||
MovementStep.PAGES, 1);
|
||||
add_move_binding (binding_set, Gdk.Key.KP_Page_Down, 0,
|
||||
MovementStep.PAGES, 1);
|
||||
|
||||
BindingEntry.add_signal (binding_set, Gdk.Key.space, Gdk.ModifierType.CONTROL_MASK,
|
||||
"modify-selection", 0);
|
||||
|
||||
activate_signal = GLib.Signal.lookup ("activate-row", typeof (Sorted));
|
||||
}
|
||||
|
||||
unowned ChildInfo? find_child_at_y (int y) {
|
||||
unowned ChildInfo? child_info = null;
|
||||
for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
|
||||
unowned ChildInfo? info = iter.get ();
|
||||
if (y >= info.y && y < info.y + info.height) {
|
||||
child_info = info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return child_info;
|
||||
}
|
||||
|
||||
private void update_cursor (ChildInfo? child) {
|
||||
cursor_child = child;
|
||||
this.grab_focus ();
|
||||
this.queue_draw ();
|
||||
var vadj = get_focus_vadjustment ();
|
||||
if (child != null && vadj != null)
|
||||
vadj.clamp_page (cursor_child.y,
|
||||
cursor_child.y + cursor_child.height);
|
||||
}
|
||||
|
||||
private void update_selected (ChildInfo? child) {
|
||||
if (child != selected_child &&
|
||||
(child == null || selection_mode != SelectionMode.NONE)) {
|
||||
selected_child = child;
|
||||
child_selected (selected_child != null ? selected_child.widget : null);
|
||||
queue_draw ();
|
||||
}
|
||||
if (child != null)
|
||||
update_cursor (child);
|
||||
}
|
||||
|
||||
private void select_and_activate (ChildInfo? child) {
|
||||
Widget? w = null;
|
||||
if (child != null)
|
||||
w = child.widget;
|
||||
update_selected (child);
|
||||
if (w != null)
|
||||
child_activated (w);
|
||||
}
|
||||
|
||||
private void update_prelight (ChildInfo? child) {
|
||||
if (child != prelight_child) {
|
||||
prelight_child = child;
|
||||
queue_draw ();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool enter_notify_event (Gdk.EventCrossing event) {
|
||||
if (event.window != get_window ())
|
||||
return false;
|
||||
|
||||
unowned ChildInfo? child = find_child_at_y ((int)event.y);
|
||||
update_prelight (child);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool leave_notify_event (Gdk.EventCrossing event) {
|
||||
if (event.window != get_window ())
|
||||
return false;
|
||||
|
||||
if (event.detail != Gdk.NotifyType.INFERIOR) {
|
||||
update_prelight (null);
|
||||
} else {
|
||||
unowned ChildInfo? child = find_child_at_y ((int)event.y);
|
||||
update_prelight (child);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool motion_notify_event (Gdk.EventMotion event) {
|
||||
unowned ChildInfo? child = find_child_at_y ((int)event.y);
|
||||
update_prelight (child);
|
||||
return false;
|
||||
}
|
||||
|
||||
private Widget? button_down_child;
|
||||
public override bool button_press_event (Gdk.EventButton event) {
|
||||
if (event.button == 1) {
|
||||
unowned ChildInfo? child = find_child_at_y ((int)event.y);
|
||||
if (child != null)
|
||||
button_down_child = child.widget;
|
||||
|
||||
/* TODO: Should mark as active while down, and handle grab breaks */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool button_release_event (Gdk.EventButton event) {
|
||||
if (event.button == 1) {
|
||||
unowned ChildInfo? child = find_child_at_y ((int)event.y);
|
||||
if (child != null && child.widget == button_down_child)
|
||||
select_and_activate (child);
|
||||
button_down_child = null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Widget? get_selected_child (){
|
||||
if (selected_child != null)
|
||||
return selected_child.widget;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual signal void child_selected (Widget? child) {
|
||||
}
|
||||
|
||||
public virtual signal void child_activated (Widget? child) {
|
||||
}
|
||||
|
||||
public override bool focus (DirectionType direction) {
|
||||
bool had_focus;
|
||||
bool focus_into;
|
||||
Widget recurse_into = null;
|
||||
|
||||
focus_into = true;
|
||||
had_focus = has_focus;
|
||||
|
||||
unowned ChildInfo? current_focus_child = null;
|
||||
unowned ChildInfo? next_focus_child = null;
|
||||
|
||||
if (had_focus) {
|
||||
/* If on row, going right, enter into possible container */
|
||||
if (direction == DirectionType.RIGHT ||
|
||||
direction == DirectionType.TAB_FORWARD) {
|
||||
/* TODO: Handle null cursor child */
|
||||
recurse_into = cursor_child.widget;
|
||||
}
|
||||
current_focus_child = cursor_child;
|
||||
/* Unless we're going up/down we're always leaving
|
||||
the container */
|
||||
if (direction != DirectionType.UP &&
|
||||
direction != DirectionType.DOWN)
|
||||
focus_into = false;
|
||||
} else if (this.get_focus_child () != null) {
|
||||
/* There is a focus child, always navigat inside it first */
|
||||
recurse_into = this.get_focus_child ();
|
||||
current_focus_child = lookup_info (recurse_into);
|
||||
|
||||
/* If exiting child container to the right, exit row */
|
||||
if (direction == DirectionType.RIGHT ||
|
||||
direction == DirectionType.TAB_FORWARD)
|
||||
focus_into = false;
|
||||
|
||||
/* If exiting child container to the left, select row or out */
|
||||
if (direction == DirectionType.LEFT ||
|
||||
direction == DirectionType.TAB_BACKWARD) {
|
||||
next_focus_child = current_focus_child;
|
||||
}
|
||||
} else {
|
||||
/* If coming from the left, enter into possible container */
|
||||
if (direction == DirectionType.LEFT ||
|
||||
direction == DirectionType.TAB_BACKWARD) {
|
||||
if (selected_child != null)
|
||||
recurse_into = selected_child.widget;
|
||||
}
|
||||
}
|
||||
|
||||
if (recurse_into != null) {
|
||||
if (recurse_into.child_focus (direction))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!focus_into)
|
||||
return false; // Focus is leaving us
|
||||
|
||||
/* TODO: This doesn't handle up/down going into a focusable separator */
|
||||
|
||||
if (next_focus_child == null) {
|
||||
if (current_focus_child != null) {
|
||||
if (direction == DirectionType.UP) {
|
||||
var i = get_previous_visible (current_focus_child.iter);
|
||||
if (i != null)
|
||||
next_focus_child = i.get ();
|
||||
} else {
|
||||
var i = get_next_visible (current_focus_child.iter);
|
||||
if (!i.is_end ())
|
||||
next_focus_child = i.get ();
|
||||
}
|
||||
} else {
|
||||
switch (direction) {
|
||||
case DirectionType.DOWN:
|
||||
case DirectionType.TAB_FORWARD:
|
||||
next_focus_child = get_first_visible ();
|
||||
break;
|
||||
case DirectionType.UP:
|
||||
case DirectionType.TAB_BACKWARD:
|
||||
next_focus_child = get_last_visible ();
|
||||
break;
|
||||
default:
|
||||
next_focus_child = selected_child;
|
||||
if (next_focus_child == null)
|
||||
next_focus_child = get_first_visible ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (next_focus_child == null) {
|
||||
if (direction == DirectionType.UP || direction == DirectionType.DOWN) {
|
||||
error_bell ();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool modify_selection_pressed = false;
|
||||
Gdk.ModifierType state;
|
||||
|
||||
if (Gtk.get_current_event_state (out state)) {
|
||||
var modify_mod_mask = this.get_modifier_mask (Gdk.ModifierIntent.MODIFY_SELECTION);
|
||||
if ((state & modify_mod_mask) == modify_mod_mask)
|
||||
modify_selection_pressed = true;
|
||||
}
|
||||
|
||||
update_cursor (next_focus_child);
|
||||
if (!modify_selection_pressed)
|
||||
update_selected (next_focus_child);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool draw (Cairo.Context cr) {
|
||||
Allocation allocation;
|
||||
this.get_allocation (out allocation);
|
||||
|
||||
var context = this.get_style_context ();
|
||||
|
||||
context.save ();
|
||||
context.render_background (cr,
|
||||
0, 0, allocation.width, allocation.height);
|
||||
|
||||
if (selected_child != null) {
|
||||
context.set_state (StateFlags.SELECTED);
|
||||
context.render_background (cr,
|
||||
0, selected_child.y,
|
||||
allocation.width, selected_child.height);
|
||||
}
|
||||
|
||||
if (prelight_child != null && prelight_child != selected_child) {
|
||||
context.set_state (StateFlags.PRELIGHT);
|
||||
context.render_background (cr,
|
||||
0, prelight_child.y,
|
||||
allocation.width, prelight_child.height);
|
||||
}
|
||||
|
||||
if (has_visible_focus() && cursor_child != null) {
|
||||
context.render_focus (cr, 0, cursor_child.y,
|
||||
allocation.width, cursor_child.height);
|
||||
}
|
||||
|
||||
context.restore ();
|
||||
|
||||
base.draw (cr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void realize () {
|
||||
Allocation allocation;
|
||||
get_allocation (out allocation);
|
||||
set_realized (true);
|
||||
|
||||
Gdk.WindowAttr attributes = { };
|
||||
attributes.x = allocation.x;
|
||||
attributes.y = allocation.y;
|
||||
attributes.width = allocation.width;
|
||||
attributes.height = allocation.height;
|
||||
attributes.window_type = Gdk.WindowType.CHILD;
|
||||
attributes.event_mask = this.get_events () |
|
||||
Gdk.EventMask.ENTER_NOTIFY_MASK |
|
||||
Gdk.EventMask.LEAVE_NOTIFY_MASK |
|
||||
Gdk.EventMask.POINTER_MOTION_MASK |
|
||||
Gdk.EventMask.EXPOSURE_MASK |
|
||||
Gdk.EventMask.BUTTON_PRESS_MASK |
|
||||
Gdk.EventMask.BUTTON_RELEASE_MASK;
|
||||
|
||||
attributes.wclass = Gdk.WindowWindowClass.INPUT_OUTPUT;
|
||||
var window = new Gdk.Window (get_parent_window (), attributes,
|
||||
Gdk.WindowAttributesType.X |
|
||||
Gdk.WindowAttributesType.Y);
|
||||
window.set_user_data (this);
|
||||
this.set_window (window);
|
||||
}
|
||||
|
||||
private void apply_filter (Widget child) {
|
||||
bool do_show = true;
|
||||
if (filter_func != null)
|
||||
do_show = filter_func (child);
|
||||
child.set_child_visible (do_show);
|
||||
}
|
||||
|
||||
private void apply_filter_all () {
|
||||
for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
|
||||
unowned ChildInfo? child_info = iter.get ();
|
||||
apply_filter (child_info.widget);
|
||||
}
|
||||
}
|
||||
|
||||
public void set_selection_mode (SelectionMode mode) {
|
||||
if (mode == SelectionMode.MULTIPLE) {
|
||||
warning ("Multiple selections not supported");
|
||||
return;
|
||||
}
|
||||
selection_mode = mode;
|
||||
if (mode == SelectionMode.NONE)
|
||||
update_selected (null);
|
||||
}
|
||||
|
||||
public void set_filter_func (owned FilterFunc? f) {
|
||||
filter_func = (owned)f;
|
||||
refilter ();
|
||||
}
|
||||
|
||||
public void set_separator_funcs (owned UpdateSeparatorFunc? update_separator) {
|
||||
update_separator_func = (owned)update_separator;
|
||||
reseparate ();
|
||||
}
|
||||
|
||||
public void refilter () {
|
||||
apply_filter_all ();
|
||||
reseparate ();
|
||||
queue_resize ();
|
||||
}
|
||||
|
||||
public void resort () {
|
||||
children.sort (do_sort);
|
||||
reseparate ();
|
||||
queue_resize ();
|
||||
}
|
||||
|
||||
private unowned ChildInfo? get_first_visible () {
|
||||
for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
|
||||
unowned ChildInfo? child_info = iter.get ();
|
||||
unowned Widget widget = child_info.widget;
|
||||
if (widget.get_visible () && widget.get_child_visible ())
|
||||
return child_info;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private unowned ChildInfo? get_last_visible () {
|
||||
var iter = children.get_end_iter ();
|
||||
while (!iter.is_begin ()) {
|
||||
iter = iter.prev ();
|
||||
unowned ChildInfo? child_info = iter.get ();
|
||||
unowned Widget widget = child_info.widget;
|
||||
if (widget.get_visible () && widget.get_child_visible ())
|
||||
return child_info;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private SequenceIter<ChildInfo?>? get_previous_visible (SequenceIter<ChildInfo?> _iter) {
|
||||
if (_iter.is_begin())
|
||||
return null;
|
||||
var iter = _iter;
|
||||
|
||||
do {
|
||||
iter = iter.prev ();
|
||||
|
||||
unowned ChildInfo? child_info = iter.get ();
|
||||
unowned Widget widget = child_info.widget;
|
||||
if (widget.get_visible () && widget.get_child_visible ())
|
||||
return iter;
|
||||
} while (!iter.is_begin ());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private SequenceIter<ChildInfo?>? get_next_visible (SequenceIter<ChildInfo?> _iter) {
|
||||
if (_iter.is_end())
|
||||
return _iter;
|
||||
|
||||
var iter = _iter;
|
||||
do {
|
||||
iter = iter.next ();
|
||||
|
||||
if (!iter.is_end ()) {
|
||||
unowned ChildInfo? child_info = iter.get ();
|
||||
unowned Widget widget = child_info.widget;
|
||||
if (widget.get_visible () && widget.get_child_visible ())
|
||||
return iter;
|
||||
}
|
||||
} while (!iter.is_end ());
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
private void update_separator (SequenceIter<ChildInfo?>? iter) {
|
||||
if (iter == null || iter.is_end ())
|
||||
return;
|
||||
|
||||
unowned ChildInfo? info = iter.get ();
|
||||
var before_iter = get_previous_visible (iter);
|
||||
var widget = info.widget;
|
||||
Widget? before_widget = null;
|
||||
if (before_iter != null) {
|
||||
unowned ChildInfo? before_info = before_iter.get ();
|
||||
before_widget = before_info.widget;
|
||||
}
|
||||
|
||||
if (update_separator_func != null &&
|
||||
widget.get_visible () &&
|
||||
widget.get_child_visible ()) {
|
||||
var old_separator = info.separator;
|
||||
update_separator_func (ref info.separator, widget, before_widget);
|
||||
if (old_separator != info.separator) {
|
||||
if (old_separator != null)
|
||||
old_separator.unparent ();
|
||||
if (info.separator != null) {
|
||||
info.separator.set_parent (this);
|
||||
info.separator.show ();
|
||||
}
|
||||
this.queue_resize ();
|
||||
}
|
||||
} else if (info.separator != null) {
|
||||
info.separator.unparent ();
|
||||
info.separator = null;
|
||||
this.queue_resize ();
|
||||
}
|
||||
}
|
||||
|
||||
public void reseparate () {
|
||||
for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
|
||||
update_separator (iter);
|
||||
}
|
||||
queue_resize ();
|
||||
}
|
||||
|
||||
public void set_sort_func (owned CompareDataFunc<Widget>? f) {
|
||||
sort_func = (owned)f;
|
||||
resort ();
|
||||
}
|
||||
|
||||
private unowned ChildInfo? lookup_info (Widget widget) {
|
||||
return child_hash.get (widget);
|
||||
}
|
||||
|
||||
public override void add (Widget widget) {
|
||||
ChildInfo? the_info = { widget };
|
||||
unowned ChildInfo? info = the_info;
|
||||
SequenceIter<ChildInfo?> iter;
|
||||
|
||||
child_hash.set (widget, info);
|
||||
|
||||
if (sort_func != null)
|
||||
iter = children.insert_sorted ((owned) the_info, do_sort);
|
||||
else
|
||||
iter = children.append ((owned) the_info);
|
||||
|
||||
apply_filter (widget);
|
||||
|
||||
var prev_next = get_next_visible (iter);
|
||||
update_separator (iter);
|
||||
update_separator (get_next_visible (iter));
|
||||
update_separator (prev_next);
|
||||
|
||||
info.iter = iter;
|
||||
|
||||
widget.set_parent (this);
|
||||
}
|
||||
|
||||
public void child_changed (Widget widget) {
|
||||
unowned ChildInfo? info = lookup_info (widget);
|
||||
if (info == null)
|
||||
return;
|
||||
|
||||
var prev_next = get_previous_visible (info.iter);
|
||||
|
||||
if (sort_func != null) {
|
||||
children.sort_changed (info.iter, do_sort);
|
||||
this.queue_resize ();
|
||||
}
|
||||
apply_filter (info.widget);
|
||||
update_separator (info.iter);
|
||||
update_separator (get_next_visible (info.iter));
|
||||
update_separator (prev_next);
|
||||
|
||||
}
|
||||
|
||||
public override void remove (Widget widget) {
|
||||
unowned ChildInfo? info = lookup_info (widget);
|
||||
if (info == null) {
|
||||
warning ("Tried to remove non-child %p\n", widget);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info == selected_child)
|
||||
update_selected (null);
|
||||
if (info == prelight_child)
|
||||
prelight_child = null;
|
||||
if (info == cursor_child)
|
||||
cursor_child = null;
|
||||
|
||||
var next = get_next_visible (info.iter);
|
||||
|
||||
bool was_visible = widget.get_visible ();
|
||||
widget.unparent ();
|
||||
|
||||
child_hash.remove (widget);
|
||||
children.remove (info.iter);
|
||||
|
||||
update_separator (next);
|
||||
|
||||
if (was_visible && this.get_visible ())
|
||||
this.queue_resize ();
|
||||
}
|
||||
|
||||
public override void forall_internal (bool include_internals,
|
||||
Gtk.Callback callback) {
|
||||
for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
|
||||
unowned ChildInfo? child_info = iter.get ();
|
||||
if (child_info.separator != null && include_internals)
|
||||
callback (child_info.separator);
|
||||
callback (child_info.widget);
|
||||
}
|
||||
}
|
||||
|
||||
public override void compute_expand_internal (out bool hexpand, out bool vexpand) {
|
||||
base.compute_expand_internal (out hexpand, out vexpand);
|
||||
/* We don't expand vertically beyound the minimum size */
|
||||
vexpand = false;
|
||||
}
|
||||
|
||||
public override Type child_type () {
|
||||
return typeof (Widget);
|
||||
}
|
||||
|
||||
public override Gtk.SizeRequestMode get_request_mode () {
|
||||
return SizeRequestMode.HEIGHT_FOR_WIDTH;
|
||||
}
|
||||
|
||||
public override void get_preferred_height (out int minimum_height, out int natural_height) {
|
||||
int natural_width;
|
||||
get_preferred_width_internal (null, out natural_width);
|
||||
get_preferred_height_for_width_internal (natural_width, out minimum_height, out natural_height);
|
||||
}
|
||||
|
||||
public override void get_preferred_height_for_width (int width, out int minimum_height, out int natural_height) {
|
||||
minimum_height = 0;
|
||||
var context = this.get_style_context ();
|
||||
int focus_width, focus_pad;
|
||||
context.get_style ("focus-line-width", out focus_width,
|
||||
"focus-padding", out focus_pad);
|
||||
for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
|
||||
unowned ChildInfo? child_info = iter.get ();
|
||||
unowned Widget widget = child_info.widget;
|
||||
int child_min;
|
||||
|
||||
if (!widget.get_visible () || !widget.get_child_visible ())
|
||||
continue;
|
||||
|
||||
if (child_info.separator != null) {
|
||||
child_info.separator.get_preferred_height_for_width (width, out child_min, null);
|
||||
minimum_height += child_min;
|
||||
}
|
||||
|
||||
widget.get_preferred_height_for_width (width - 2 * (focus_width + focus_pad),
|
||||
out child_min, null);
|
||||
minimum_height += child_min + 2 * (focus_width + focus_pad);
|
||||
}
|
||||
|
||||
/* We always allocate the minimum height, since handling
|
||||
expanding rows is way too costly, and unlikely to
|
||||
be used, as lists are generally put inside a scrolling window
|
||||
anyway.
|
||||
*/
|
||||
natural_height = minimum_height;
|
||||
}
|
||||
|
||||
public override void get_preferred_width (out int minimum_width, out int natural_width) {
|
||||
var context = this.get_style_context ();
|
||||
int focus_width, focus_pad;
|
||||
context.get_style ("focus-line-width", out focus_width,
|
||||
"focus-padding", out focus_pad);
|
||||
minimum_width = 0;
|
||||
natural_width = 0;
|
||||
for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
|
||||
unowned ChildInfo? child_info = iter.get ();
|
||||
unowned Widget widget = child_info.widget;
|
||||
int child_min, child_nat;
|
||||
|
||||
if (!widget.get_visible () || !widget.get_child_visible ())
|
||||
continue;
|
||||
|
||||
widget.get_preferred_width (out child_min, out child_nat);
|
||||
minimum_width = int.max (minimum_width, child_min + 2 * (focus_width + focus_pad));
|
||||
natural_width = int.max (natural_width, child_nat + 2 * (focus_width + focus_pad));
|
||||
|
||||
if (child_info.separator != null) {
|
||||
child_info.separator.get_preferred_width (out child_min, out child_nat);
|
||||
minimum_width = int.max (minimum_width, child_min);
|
||||
natural_width = int.max (natural_width, child_nat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void get_preferred_width_for_height (int height, out int minimum_width, out int natural_width) {
|
||||
get_preferred_width_internal (out minimum_width, out natural_width);
|
||||
}
|
||||
|
||||
public override void size_allocate (Gtk.Allocation allocation) {
|
||||
Allocation child_allocation = { 0, 0, 0, 0};
|
||||
Allocation separator_allocation = { 0, 0, 0, 0};
|
||||
|
||||
set_allocation (allocation);
|
||||
|
||||
var window = get_window();
|
||||
if (window != null)
|
||||
window.move_resize (allocation.x,
|
||||
allocation.y,
|
||||
allocation.width,
|
||||
allocation.height);
|
||||
|
||||
var context = this.get_style_context ();
|
||||
int focus_width, focus_pad;
|
||||
context.get_style ("focus-line-width", out focus_width,
|
||||
"focus-padding", out focus_pad);
|
||||
|
||||
child_allocation.x = allocation.x + focus_width + focus_pad;
|
||||
child_allocation.y = allocation.y;
|
||||
child_allocation.width = allocation.width - 2 * (focus_width + focus_pad);
|
||||
|
||||
separator_allocation.x = allocation.x;
|
||||
separator_allocation.width = allocation.width;
|
||||
|
||||
for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) {
|
||||
unowned ChildInfo? child_info = iter.get ();
|
||||
unowned Widget widget = child_info.widget;
|
||||
int child_min;
|
||||
|
||||
if (!widget.get_visible () || !widget.get_child_visible ()) {
|
||||
child_info.y = child_allocation.y;
|
||||
child_info.height = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child_info.separator != null) {
|
||||
child_info.separator.get_preferred_height_for_width (allocation.width, out child_min, null);
|
||||
separator_allocation.height = child_min;
|
||||
separator_allocation.y = child_allocation.y;
|
||||
|
||||
child_info.separator.size_allocate (separator_allocation);
|
||||
|
||||
child_allocation.y += child_min;
|
||||
}
|
||||
|
||||
child_info.y = child_allocation.y;
|
||||
child_allocation.y += focus_width + focus_pad;
|
||||
|
||||
widget.get_preferred_height_for_width (child_allocation.width, out child_min, null);
|
||||
child_allocation.height = child_min;
|
||||
|
||||
child_info.height = child_allocation.height + 2 * (focus_width + focus_pad);
|
||||
widget.size_allocate (child_allocation);
|
||||
|
||||
child_allocation.y += child_min + focus_width + focus_pad;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,13 +20,16 @@ using Gtk;
|
|||
using Folks;
|
||||
using Gee;
|
||||
|
||||
public class Contacts.View : TreeView {
|
||||
public class Contacts.View : Contacts.Sorted {
|
||||
private class ContactData {
|
||||
public Contact contact;
|
||||
public TreeIter iter;
|
||||
public bool visible;
|
||||
public bool is_first;
|
||||
public Grid grid;
|
||||
public Label label;
|
||||
public ContactFrame image_frame;
|
||||
public int sort_prio;
|
||||
public string display_name;
|
||||
public unichar initial_letter;
|
||||
public bool filtered;
|
||||
}
|
||||
|
||||
public enum Subset {
|
||||
|
@ -36,46 +39,44 @@ public class Contacts.View : TreeView {
|
|||
ALL
|
||||
}
|
||||
|
||||
public enum TextDisplay {
|
||||
NONE,
|
||||
PRESENCE,
|
||||
STORES
|
||||
}
|
||||
|
||||
public signal void selection_changed (Contact? contact);
|
||||
|
||||
Store contacts_store;
|
||||
Subset show_subset;
|
||||
ListStore list_store;
|
||||
HashMap<Contact,ContactData> contacts;
|
||||
HashSet<Contact> hidden_contacts;
|
||||
|
||||
string []? filter_values;
|
||||
int custom_visible_count;
|
||||
ContactData suggestions_header_data;
|
||||
ContactData padding_data;
|
||||
ContactData other_header_data;
|
||||
private TextDisplay text_display;
|
||||
|
||||
public View (Store store, TextDisplay text_display = TextDisplay.PRESENCE) {
|
||||
set_selection_mode (SelectionMode.BROWSE);
|
||||
contacts_store = store;
|
||||
hidden_contacts = new HashSet<Contact>();
|
||||
show_subset = Subset.ALL;
|
||||
this.text_display = text_display;
|
||||
|
||||
list_store = new ListStore (2, typeof (Contact), typeof (ContactData *));
|
||||
suggestions_header_data = new ContactData ();
|
||||
suggestions_header_data.sort_prio = int.MAX;
|
||||
padding_data = new ContactData ();
|
||||
padding_data.sort_prio = 1;
|
||||
contacts = new HashMap<Contact,ContactData> ();
|
||||
|
||||
other_header_data = new ContactData ();
|
||||
other_header_data.sort_prio = -1;
|
||||
|
||||
list_store.set_sort_func (0, (model, iter_a, iter_b) => {
|
||||
ContactData *aa, bb;
|
||||
model.get (iter_a, 1, out aa);
|
||||
model.get (iter_b, 1, out bb);
|
||||
|
||||
return compare_data (aa, bb);
|
||||
this.set_sort_func ((widget_a, widget_b) => {
|
||||
var a = widget_a.get_data<ContactData> ("data");
|
||||
var b = widget_b.get_data<ContactData> ("data");
|
||||
return compare_data (a, b);
|
||||
});
|
||||
list_store.set_sort_column_id (0, SortType.ASCENDING);
|
||||
this.set_filter_func (filter);
|
||||
this.set_separator_funcs (update_separator);
|
||||
|
||||
contacts_store.added.connect (contact_added_cb);
|
||||
contacts_store.removed.connect (contact_removed_cb);
|
||||
contacts_store.changed.connect (contact_changed_cb);
|
||||
foreach (var c in store.get_contacts ())
|
||||
contact_added_cb (store, c);
|
||||
|
||||
init_view (text_display);
|
||||
}
|
||||
|
||||
private int compare_data (ContactData a_data, ContactData b_data) {
|
||||
|
@ -87,16 +88,13 @@ public class Contacts.View : TreeView {
|
|||
if (a_prio < b_prio)
|
||||
return 1;
|
||||
|
||||
var a = a_data.contact;
|
||||
var b = b_data.contact;
|
||||
|
||||
if (is_set (a.display_name) && is_set (b.display_name))
|
||||
return a.display_name.collate (b.display_name);
|
||||
if (is_set (a_data.display_name) && is_set (b_data.display_name))
|
||||
return a_data.display_name.collate (b_data.display_name);
|
||||
|
||||
// Sort empty names last
|
||||
if (is_set (a.display_name))
|
||||
if (is_set (a_data.display_name))
|
||||
return -1;
|
||||
if (is_set (b.display_name))
|
||||
if (is_set (b_data.display_name))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
|
@ -111,423 +109,200 @@ public class Contacts.View : TreeView {
|
|||
}
|
||||
|
||||
/* The hardcoded prio if set, otherwise 0 for the
|
||||
main/combined group, or -2 for the separated other group */
|
||||
main/combined group, or -1 for the separated other group */
|
||||
private int get_sort_prio (ContactData *data) {
|
||||
if (data->sort_prio != 0)
|
||||
return data->sort_prio;
|
||||
|
||||
if (is_other (data))
|
||||
return -2;
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public string get_header_text (TreeIter iter) {
|
||||
ContactData *data;
|
||||
list_store.get (iter, 1, out data);
|
||||
if (data == suggestions_header_data) {
|
||||
/* Translators: This is the header for the list of suggested contacts to
|
||||
link to the current contact */
|
||||
return ngettext ("Suggestion", "Suggestions", custom_visible_count);
|
||||
}
|
||||
if (data == other_header_data) {
|
||||
/* Translators: This is the header for the list of suggested contacts to
|
||||
link to the current contact */
|
||||
return _("Other Contacts");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public void set_show_subset (Subset subset) {
|
||||
show_subset = subset;
|
||||
|
||||
bool new_visible = show_subset == Subset.ALL_SEPARATED;
|
||||
if (new_visible && !other_header_data.visible) {
|
||||
other_header_data.visible = true;
|
||||
list_store.append (out other_header_data.iter);
|
||||
list_store.set (other_header_data.iter, 1, other_header_data);
|
||||
}
|
||||
if (!new_visible && other_header_data.visible) {
|
||||
other_header_data.visible = false;
|
||||
list_store.remove (other_header_data.iter);
|
||||
}
|
||||
|
||||
update_all_filtered ();
|
||||
refilter ();
|
||||
resort ();
|
||||
}
|
||||
|
||||
public void set_custom_sort_prio (Contact c, int prio) {
|
||||
/* We use negative prios internally */
|
||||
assert (prio >= 0);
|
||||
|
||||
var data = lookup_data (c);
|
||||
|
||||
var data = contacts.get (c);
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
// We insert a priority between 0 and 1 for the padding
|
||||
if (prio > 0)
|
||||
prio += 1;
|
||||
data.sort_prio = prio;
|
||||
contact_changed_cb (contacts_store, c);
|
||||
|
||||
if (data.visible) {
|
||||
if (prio > 0) {
|
||||
if (custom_visible_count++ == 0)
|
||||
add_custom_headers ();
|
||||
} else {
|
||||
if (custom_visible_count-- == 1)
|
||||
remove_custom_headers ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool apply_filter (Contact contact) {
|
||||
if (contact.is_hidden)
|
||||
return false;
|
||||
|
||||
if (contact in hidden_contacts)
|
||||
return false;
|
||||
|
||||
if ((show_subset == Subset.MAIN &&
|
||||
!contact.is_main) ||
|
||||
(show_subset == Subset.OTHER &&
|
||||
contact.is_main))
|
||||
return false;
|
||||
|
||||
if (filter_values == null || filter_values.length == 0)
|
||||
return true;
|
||||
|
||||
return contact.contains_strings (filter_values);
|
||||
}
|
||||
|
||||
public bool is_first (TreeIter iter) {
|
||||
ContactData *data;
|
||||
list_store.get (iter, 1, out data);
|
||||
if (data != null)
|
||||
return data->is_first;
|
||||
return false;
|
||||
}
|
||||
|
||||
private ContactData? get_previous (ContactData data) {
|
||||
ContactData *previous = null;
|
||||
TreeIter iter = data.iter;
|
||||
if (list_store.iter_previous (ref iter))
|
||||
list_store.get (iter, 1, out previous);
|
||||
return previous;
|
||||
}
|
||||
|
||||
private ContactData? get_next (ContactData data) {
|
||||
ContactData *next = null;
|
||||
TreeIter iter = data.iter;
|
||||
if (list_store.iter_next (ref iter))
|
||||
list_store.get (iter, 1, out next);
|
||||
return next;
|
||||
}
|
||||
|
||||
private void row_changed_no_resort (ContactData data) {
|
||||
var path = list_store.get_path (data.iter);
|
||||
list_store.row_changed (path, data.iter);
|
||||
}
|
||||
|
||||
private void row_changed_resort (ContactData data) {
|
||||
list_store.set (data.iter, 0, data.contact);
|
||||
}
|
||||
|
||||
private bool update_is_first (ContactData data, ContactData? previous) {
|
||||
bool old_is_first = data.is_first;
|
||||
|
||||
bool is_custom = data.sort_prio != 0;
|
||||
bool previous_is_custom = previous != null && (previous.sort_prio != 0) ;
|
||||
|
||||
if (is_custom) {
|
||||
data.is_first = false;
|
||||
} else if (previous != null && !previous_is_custom) {
|
||||
unichar previous_initial = previous.contact.initial_letter;
|
||||
unichar initial = data.contact.initial_letter;
|
||||
data.is_first = previous_initial != initial;
|
||||
} else {
|
||||
data.is_first = true;
|
||||
}
|
||||
|
||||
if (old_is_first != data.is_first) {
|
||||
row_changed_no_resort (data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void add_custom_headers () {
|
||||
suggestions_header_data.visible = true;
|
||||
list_store.append (out suggestions_header_data.iter);
|
||||
list_store.set (suggestions_header_data.iter, 1, suggestions_header_data);
|
||||
padding_data.visible = true;
|
||||
list_store.append (out padding_data.iter);
|
||||
list_store.set (padding_data.iter, 1, padding_data);
|
||||
}
|
||||
|
||||
private void remove_custom_headers () {
|
||||
suggestions_header_data.visible = false;
|
||||
list_store.remove (suggestions_header_data.iter);
|
||||
padding_data.visible = false;
|
||||
list_store.remove (padding_data.iter);
|
||||
}
|
||||
|
||||
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)
|
||||
remove_from_model (data);
|
||||
|
||||
if (!was_visible && data.visible)
|
||||
add_to_model (data);
|
||||
}
|
||||
|
||||
private void refilter () {
|
||||
foreach (var c in contacts_store.get_contacts ()) {
|
||||
update_visible (lookup_data (c));
|
||||
}
|
||||
child_changed (data.grid);
|
||||
}
|
||||
|
||||
public void hide_contact (Contact contact) {
|
||||
hidden_contacts.add (contact);
|
||||
update_all_filtered ();
|
||||
refilter ();
|
||||
}
|
||||
|
||||
public void set_filter_values (string []? values) {
|
||||
filter_values = values;
|
||||
update_all_filtered ();
|
||||
refilter ();
|
||||
}
|
||||
|
||||
private void contact_changed_cb (Store store, Contact c) {
|
||||
ContactData data = lookup_data (c);
|
||||
private bool calculate_filtered (Contact c) {
|
||||
if (c.is_hidden)
|
||||
return false;
|
||||
|
||||
bool was_visible = data.visible;
|
||||
if (c in hidden_contacts)
|
||||
return false;
|
||||
|
||||
ContactData? next = null;
|
||||
if (data.visible)
|
||||
next = get_next (data);
|
||||
if ((show_subset == Subset.MAIN &&
|
||||
!c.is_main) ||
|
||||
(show_subset == Subset.OTHER &&
|
||||
c.is_main))
|
||||
return false;
|
||||
|
||||
update_visible (data);
|
||||
if (filter_values == null || filter_values.length == 0)
|
||||
return true;
|
||||
|
||||
if (was_visible && data.visible) {
|
||||
/* We just moved position in the list while visible */
|
||||
return c.contains_strings (filter_values);
|
||||
}
|
||||
|
||||
row_changed_resort (data);
|
||||
private void update_data (ContactData data) {
|
||||
var c = data.contact;
|
||||
data.display_name = c.display_name;
|
||||
data.initial_letter = c.initial_letter;
|
||||
data.filtered = calculate_filtered (c);
|
||||
|
||||
/* Update the is_first on the previous next row */
|
||||
if (next != null)
|
||||
update_is_first (next, get_previous (next));
|
||||
data.label.set_markup (Markup.printf_escaped ("<span font='16px'>%s</span>", data.display_name));
|
||||
data.image_frame.set_image (c.individual, c);
|
||||
}
|
||||
|
||||
/* Update the is_first on the new next row */
|
||||
next = get_next (data);
|
||||
if (next != null)
|
||||
update_is_first (next, data);
|
||||
private void update_all_filtered () {
|
||||
foreach (var data in contacts.values) {
|
||||
data.filtered = calculate_filtered (data.contact);
|
||||
}
|
||||
}
|
||||
|
||||
private ContactData lookup_data (Contact c) {
|
||||
return c.lookup<ContactData> (this);
|
||||
private void contact_changed_cb (Store store, Contact c) {
|
||||
var data = contacts.get (c);
|
||||
update_data (data);
|
||||
child_changed (data.grid);
|
||||
}
|
||||
|
||||
private void contact_added_cb (Store store, Contact c) {
|
||||
ContactData data = new ContactData();
|
||||
var data = new ContactData();
|
||||
data.contact = c;
|
||||
data.visible = false;
|
||||
data.grid = new Grid ();
|
||||
data.grid.margin = 12;
|
||||
data.grid.set_column_spacing (10);
|
||||
data.image_frame = new ContactFrame (Contact.SMALL_AVATAR_SIZE);
|
||||
data.label = new Label ("");
|
||||
data.label.set_ellipsize (Pango.EllipsizeMode.END);
|
||||
data.label.set_valign (Align.START);
|
||||
data.label.set_halign (Align.START);
|
||||
|
||||
c.set_lookup (this, data);
|
||||
data.grid.attach (data.image_frame, 0, 0, 1, 2);
|
||||
data.grid.attach (data.label, 1, 0, 1, 1);
|
||||
|
||||
update_visible (data);
|
||||
if (text_display == TextDisplay.PRESENCE) {
|
||||
var merged_presence = c.create_merged_presence_widget ();
|
||||
merged_presence.set_halign (Align.START);
|
||||
merged_presence.set_valign (Align.END);
|
||||
merged_presence.set_vexpand (false);
|
||||
merged_presence.set_margin_bottom (4);
|
||||
|
||||
data.grid.attach (merged_presence, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
if (text_display == TextDisplay.STORES) {
|
||||
var stores = new Label ("");
|
||||
stores.set_markup (Markup.printf_escaped ("<span font='12px'>%s</span>",
|
||||
c.format_persona_stores ()));
|
||||
|
||||
stores.set_ellipsize (Pango.EllipsizeMode.END);
|
||||
stores.set_halign (Align.START);
|
||||
data.grid.attach (stores, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
update_data (data);
|
||||
|
||||
data.grid.set_data<ContactData> ("data", data);
|
||||
data.grid.show_all ();
|
||||
contacts.set (c, data);
|
||||
this.add (data.grid);
|
||||
}
|
||||
|
||||
private void contact_removed_cb (Store store, Contact c) {
|
||||
var data = lookup_data (c);
|
||||
|
||||
if (data.visible)
|
||||
remove_from_model (data);
|
||||
|
||||
c.remove_lookup<ContactData> (this);
|
||||
var data = contacts.get (c);
|
||||
data.grid.destroy ();
|
||||
data.label.destroy ();
|
||||
data.image_frame.destroy ();
|
||||
contacts.unset (c);
|
||||
}
|
||||
|
||||
public bool lookup_iter (Contact c, out TreeIter iter) {
|
||||
var data = lookup_data (c);
|
||||
iter = data.iter;
|
||||
return data.visible;
|
||||
public override void child_selected (Widget? child) {
|
||||
var data = child.get_data<ContactData> ("data");
|
||||
selection_changed (data != null ? data.contact : null);
|
||||
}
|
||||
|
||||
private bool filter (Widget child) {
|
||||
var data = child.get_data<ContactData> ("data");
|
||||
|
||||
|
||||
private CellRendererShape shape;
|
||||
public enum TextDisplay {
|
||||
NONE,
|
||||
PRESENCE,
|
||||
STORES
|
||||
}
|
||||
private TextDisplay text_display;
|
||||
|
||||
public signal void selection_changed (Contact? contact);
|
||||
|
||||
private void init_view (TextDisplay text_display) {
|
||||
this.text_display = text_display;
|
||||
|
||||
set_model (list_store);
|
||||
set_headers_visible (false);
|
||||
|
||||
var row_padding = 12;
|
||||
|
||||
var selection = get_selection ();
|
||||
selection.set_mode (SelectionMode.BROWSE);
|
||||
selection.set_select_function ( (selection, model, path, path_currently_selected) => {
|
||||
Contact contact;
|
||||
TreeIter iter;
|
||||
model.get_iter (out iter, path);
|
||||
model.get (iter, 0, out contact);
|
||||
return contact != null;
|
||||
});
|
||||
selection.changed.connect (contacts_selection_changed);
|
||||
|
||||
var column = new TreeViewColumn ();
|
||||
column.set_spacing (8);
|
||||
|
||||
var icon = new CellRendererPixbuf ();
|
||||
icon.set_padding (0, row_padding);
|
||||
icon.xalign = 1.0f;
|
||||
icon.yalign = 0.0f;
|
||||
icon.width = Contact.SMALL_AVATAR_SIZE + 12;
|
||||
column.pack_start (icon, false);
|
||||
column.set_cell_data_func (icon, (column, cell, model, iter) => {
|
||||
Contact contact;
|
||||
|
||||
model.get (iter, 0, out contact);
|
||||
|
||||
if (contact == null) {
|
||||
cell.set ("pixbuf", null);
|
||||
cell.visible = false;
|
||||
return;
|
||||
}
|
||||
cell.visible = true;
|
||||
|
||||
if (contact != null)
|
||||
cell.set ("pixbuf", contact.small_avatar);
|
||||
else
|
||||
cell.set ("pixbuf", null);
|
||||
});
|
||||
|
||||
shape = new CellRendererShape ();
|
||||
shape.set_padding (0, row_padding);
|
||||
|
||||
Pango.cairo_context_set_shape_renderer (get_pango_context (), shape.render_shape);
|
||||
|
||||
column.pack_start (shape, true);
|
||||
column.set_cell_data_func (shape, (column, cell, model, iter) => {
|
||||
Contact contact;
|
||||
|
||||
model.get (iter, 0, out contact);
|
||||
|
||||
if (contact == null) {
|
||||
cell.visible = false;
|
||||
return;
|
||||
}
|
||||
cell.visible = true;
|
||||
|
||||
var name = contact.display_name;
|
||||
switch (text_display) {
|
||||
default:
|
||||
case TextDisplay.NONE:
|
||||
cell.set ("name", name,
|
||||
"show_presence", false,
|
||||
"message", "");
|
||||
break;
|
||||
case TextDisplay.PRESENCE:
|
||||
cell.set ("name", name,
|
||||
"show_presence", true,
|
||||
"presence", contact.presence_type,
|
||||
"message", contact.presence_message,
|
||||
"is_phone", contact.is_phone);
|
||||
break;
|
||||
case TextDisplay.STORES:
|
||||
string stores = contact.format_persona_stores ();
|
||||
cell.set ("name", name,
|
||||
"show_presence", false,
|
||||
"message", stores);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
var text = new CellRendererText ();
|
||||
text.set_alignment (0, 0);
|
||||
column.pack_start (text, true);
|
||||
text.set ("weight", Pango.Weight.BOLD);
|
||||
column.set_cell_data_func (text, (column, cell, model, iter) => {
|
||||
Contact contact;
|
||||
|
||||
model.get (iter, 0, out contact);
|
||||
cell.visible = contact == null;
|
||||
if (cell.visible) {
|
||||
string header = get_header_text (iter);
|
||||
cell.set ("text", header);
|
||||
if (header == "")
|
||||
cell.height = 6; // PADDING
|
||||
else
|
||||
cell.height = -1;
|
||||
}
|
||||
});
|
||||
|
||||
append_column (column);
|
||||
return data.filtered;
|
||||
}
|
||||
|
||||
private void contacts_selection_changed (TreeSelection selection) {
|
||||
TreeIter iter;
|
||||
TreeModel model;
|
||||
private void update_separator (ref Widget? separator,
|
||||
Widget widget,
|
||||
Widget? before_widget) {
|
||||
var w_data = widget.get_data<ContactData> ("data");
|
||||
ContactData? before_data = null;
|
||||
if (before_widget != null)
|
||||
before_data = before_widget.get_data<ContactData> ("data");
|
||||
|
||||
Contact? contact = null;
|
||||
if (selection.get_selected (out model, out iter)) {
|
||||
model.get (iter, 0, out contact);
|
||||
if (before_data == null && w_data.sort_prio > 0) {
|
||||
if (separator == null ||
|
||||
!(separator.get_data<bool> ("contacts-suggestions-header"))) {
|
||||
var l = new Label ("");
|
||||
l.set_data ("contacts-suggestions-header", true);
|
||||
l.set_markup (Markup.printf_escaped ("<b>%s</b>", _("Suggestions")));
|
||||
l.set_halign (Align.START);
|
||||
separator = l;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
selection_changed (contact);
|
||||
}
|
||||
|
||||
public void select_contact (Contact contact) {
|
||||
TreeIter iter;
|
||||
if (lookup_iter (contact, out iter)) {
|
||||
get_selection ().select_iter (iter);
|
||||
scroll_to_cell (list_store.get_path (iter),
|
||||
null, true, 0.0f, 0.0f);
|
||||
if (before_data != null && before_data.sort_prio > 0 &&
|
||||
w_data.sort_prio == 0) {
|
||||
if (separator == null ||
|
||||
!(separator.get_data<bool> ("contacts-rest-header"))) {
|
||||
var l = new Label ("");
|
||||
l.set_data ("contacts-rest-header", true);
|
||||
l.set_halign (Align.START);
|
||||
separator = l;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_other (w_data) &&
|
||||
(before_data == null || !is_other (before_data))) {
|
||||
if (separator == null ||
|
||||
!(separator.get_data<bool> ("contacts-other-header"))) {
|
||||
var l = new Label ("");
|
||||
l.set_data ("contacts-other-header", true);
|
||||
l.set_markup (Markup.printf_escaped ("<b>%s</b>", _("Other Contacts")));
|
||||
l.set_halign (Align.START);
|
||||
separator = l;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (before_data != null &&
|
||||
w_data.initial_letter != before_data.initial_letter) {
|
||||
if (separator == null || !(separator is Separator))
|
||||
separator = new Separator (Orientation.HORIZONTAL);
|
||||
return;
|
||||
}
|
||||
separator = null;
|
||||
}
|
||||
}
|
||||
|
|
189
src/test-sorted.vala
Normal file
189
src/test-sorted.vala
Normal file
|
@ -0,0 +1,189 @@
|
|||
/* -*- 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 Contacts;
|
||||
|
||||
public void update_separator (ref Widget? separator,
|
||||
Widget widget,
|
||||
Widget? before)
|
||||
{
|
||||
if (before == null ||
|
||||
(widget is Label && (widget as Label).get_text () == "blah3")) {
|
||||
if (separator == null) {
|
||||
var hbox = new Box(Orientation.HORIZONTAL, 0);
|
||||
var l = new Label ("Separator");
|
||||
hbox.add (l);
|
||||
var b = new Button.with_label ("button");
|
||||
hbox.add (b);
|
||||
l.show ();
|
||||
b.show ();
|
||||
separator = hbox;
|
||||
}
|
||||
|
||||
var hbox = separator as Box;
|
||||
var id = widget.get_data<int>("sort_id");
|
||||
var l = hbox.get_children ().data as Label;
|
||||
l.set_text ("Separator %d".printf (id));
|
||||
} else {
|
||||
separator = null;
|
||||
}
|
||||
print ("update separator => %p\n", separator);
|
||||
}
|
||||
|
||||
public static int
|
||||
compare_label (Widget a, Widget b) {
|
||||
var aa = a.get_data<int>("sort_id");
|
||||
var bb = b.get_data<int>("sort_id");
|
||||
return bb - aa;
|
||||
}
|
||||
|
||||
public static int
|
||||
compare_label_reverse (Widget a, Widget b) {
|
||||
return - compare_label (a, b);
|
||||
}
|
||||
|
||||
public static bool
|
||||
filter (Widget widget) {
|
||||
var text = (widget as Label).get_text ();
|
||||
return strcmp (text, "blah3") != 0;
|
||||
}
|
||||
|
||||
public static int
|
||||
main (string[] args) {
|
||||
|
||||
Gtk.init (ref args);
|
||||
|
||||
var w = new Window ();
|
||||
var hbox = new Box(Orientation.HORIZONTAL, 0);
|
||||
w.add (hbox);
|
||||
|
||||
var sorted = new Sorted();
|
||||
hbox.add (sorted);
|
||||
|
||||
sorted.child_activated.connect ( (child) => {
|
||||
print ("activated %p\n", child);
|
||||
});
|
||||
|
||||
sorted.child_selected.connect ( (child) => {
|
||||
print ("selected %p\n", child);
|
||||
});
|
||||
|
||||
var l = new Label ("blah4");
|
||||
l.set_data ("sort_id", 4);
|
||||
sorted.add (l);
|
||||
var l3 = new Label ("blah3");
|
||||
l3.set_data ("sort_id", 3);
|
||||
sorted.add (l3);
|
||||
l = new Label ("blah1");
|
||||
l.set_data ("sort_id", 1);
|
||||
sorted.add (l);
|
||||
l = new Label ("blah2");
|
||||
l.set_data ("sort_id", 2);
|
||||
sorted.add (l);
|
||||
|
||||
var row_vbox = new Box (Orientation.VERTICAL, 0);
|
||||
var row_hbox = new Box (Orientation.HORIZONTAL, 0);
|
||||
row_vbox.set_data ("sort_id", 3);
|
||||
l = new Label ("da box for da man");
|
||||
row_hbox.add (l);
|
||||
var check = new CheckButton ();
|
||||
row_hbox.add (check);
|
||||
var button = new Button.with_label ("ya!");
|
||||
row_hbox.add (button);
|
||||
row_vbox.add (row_hbox);
|
||||
check = new CheckButton ();
|
||||
row_vbox.add (check);
|
||||
sorted.add (row_vbox);
|
||||
|
||||
button = new Button.with_label ("focusable row");
|
||||
button.set_hexpand (false);
|
||||
button.set_halign (Align.START);
|
||||
sorted.add (button);
|
||||
|
||||
var vbox = new Box(Orientation.VERTICAL, 0);
|
||||
hbox.add (vbox);
|
||||
|
||||
var b = new Button.with_label ("sort");
|
||||
vbox.add (b);
|
||||
b.clicked.connect ( () => {
|
||||
sorted.set_sort_func (compare_label);
|
||||
});
|
||||
|
||||
b = new Button.with_label ("reverse");
|
||||
vbox.add (b);
|
||||
b.clicked.connect ( () => {
|
||||
sorted.set_sort_func (compare_label_reverse);
|
||||
});
|
||||
|
||||
b = new Button.with_label ("change");
|
||||
vbox.add (b);
|
||||
b.clicked.connect ( () => {
|
||||
if (l3.get_text () == "blah3") {
|
||||
l3.set_text ("blah5");
|
||||
l3.set_data ("sort_id", 5);
|
||||
} else {
|
||||
l3.set_text ("blah3");
|
||||
l3.set_data ("sort_id", 3);
|
||||
}
|
||||
sorted.child_changed (l3);
|
||||
});
|
||||
|
||||
b = new Button.with_label ("filter");
|
||||
vbox.add (b);
|
||||
b.clicked.connect ( () => {
|
||||
sorted.set_filter_func (filter);
|
||||
});
|
||||
|
||||
b = new Button.with_label ("unfilter");
|
||||
vbox.add (b);
|
||||
b.clicked.connect ( () => {
|
||||
sorted.set_filter_func (null);
|
||||
});
|
||||
|
||||
int new_button_nr = 1;
|
||||
b = new Button.with_label ("add");
|
||||
vbox.add (b);
|
||||
b.clicked.connect ( () => {
|
||||
var ll = new Label ("blah2 new %d".printf (new_button_nr));
|
||||
l.set_data ("sort_id", new_button_nr);
|
||||
new_button_nr++;
|
||||
|
||||
sorted.add (ll);
|
||||
l.show ();
|
||||
});
|
||||
|
||||
b = new Button.with_label ("separate");
|
||||
vbox.add (b);
|
||||
b.clicked.connect ( () => {
|
||||
sorted.set_separator_funcs (update_separator);
|
||||
});
|
||||
|
||||
b = new Button.with_label ("unseparate");
|
||||
vbox.add (b);
|
||||
b.clicked.connect ( () => {
|
||||
sorted.set_separator_funcs (null);
|
||||
});
|
||||
|
||||
|
||||
w.show_all ();
|
||||
|
||||
Gtk.main ();
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue