From ec2939c8a9e3d30adf480ec9d5da202d8ee307f1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 17 Feb 2012 16:11:57 +0100 Subject: [PATCH 01/51] Initial version of ContactsSorted --- src/Makefile.am | 10 +- src/contacts-sorted.vala | 272 +++++++++++++++++++++++++++++++++++++++ src/test-sorted.vala | 108 ++++++++++++++++ 3 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 src/contacts-sorted.vala create mode 100644 src/test-sorted.vala diff --git a/src/Makefile.am b/src/Makefile.am index 3fc3dfd..f8423da 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,6 +7,7 @@ AM_CPPFLAGS = \ -DPKGDATADIR=\""$(pkgdatadir)"\" \ -DPKGLIBDIR=\""$(pkglibdir)"\" \ -DGNOME_DESKTOP_USE_UNSTABLE_API \ + -Dget_preferred_height_for_width_internal=get_preferred_height_for_width $(NULL) AM_VALAFLAGS = \ @@ -19,7 +20,7 @@ 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 \ @@ -67,6 +68,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 diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala new file mode 100644 index 0000000..284108f --- /dev/null +++ b/src/contacts-sorted.vala @@ -0,0 +1,272 @@ +/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */ +/* + * Copyright (C) 2011 Alexander Larsson + * + * 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 . + */ + +using Gtk; +using Gee; + +/* Requriements: + + sort + + filter + + first char or type custom "separators" + (create, destroy, update) + + Work with largish sets of children + + filter => child visibility setting + + Q: + How to construct separators? + What about resort a single item, can be problem if more change + at the same time, need a stable sort... + + settings: + sort function + filter function + needs_separator function + create_separator + update_separator (if the child below it changes) + + 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 bool NeedSeparatorFunc (Widget? before, Widget widget); + public delegate Widget CreateSeparatorFunc (Widget child); + public delegate void UpdateSeparatorFunc (Widget separator, Widget child); + + struct ChildInfo { + Widget widget; + bool is_separator; + bool has_separator; + int height; + SequenceIter iter; + } + + Sequence children; + HashMap child_hash; + CompareDataFunc? sort_func; + FilterFunc? filter_func; + NeedSeparatorFunc? need_separator_func; + CreateSeparatorFunc? create_separator_func; + + private int do_sort (ChildInfo? a, ChildInfo? b) { + return sort_func (a.widget, b.widget); + } + + public Sorted () { + set_has_window (false); + set_redraw_on_allocate (false); + + children = new Sequence(); + child_hash = new HashMap (); + } + + 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_filter_func (owned FilterFunc? f) { + filter_func = (owned)f; + refilter (); + } + + public void refilter () { + apply_filter_all (); + queue_resize (); + } + + public void resort () { + children.sort (do_sort); + queue_resize (); + } + + public void set_sort_func (owned CompareDataFunc? f) { + sort_func = (owned)f; + resort (); + } + + public override void map () { + base.map (); + } + + public override void unmap () { + base.unmap (); + } + + 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 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); + + info.iter = iter; + + widget.set_parent (this); + } + + public void child_changed (Widget widget) { + unowned ChildInfo? info = lookup_info (widget); + if (info == null) + return; + + if (sort_func != null) { + children.sort_changed (info.iter, do_sort); + this.queue_resize (); + } + apply_filter (info.widget); + } + + public override void remove (Widget widget) { + unowned ChildInfo? info = lookup_info (widget); + if (info == null) + return; + + bool was_visible = widget.get_visible (); + widget.unparent (); + + child_hash.unset (widget); + + 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 (); + 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 (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; + 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; + + widget.get_preferred_height_for_width (width, out child_min, null); + minimum_height += child_min; + } + /* 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) { + 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); + 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 (out minimum_width, out natural_width); + } + + public override void size_allocate (Gtk.Allocation allocation) { + Allocation child_allocation = { 0, 0, 0, 0}; + + set_allocation (allocation); + + child_allocation.x = allocation.x; + child_allocation.y = allocation.y; + child_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 ()) + continue; + + widget.get_preferred_height_for_width (allocation.width, out child_min, null); + child_allocation.height = child_info.height = child_min; + + widget.size_allocate (child_allocation); + + child_allocation.y += child_min; + } + } +} diff --git a/src/test-sorted.vala b/src/test-sorted.vala new file mode 100644 index 0000000..a940439 --- /dev/null +++ b/src/test-sorted.vala @@ -0,0 +1,108 @@ +/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */ +/* + * Copyright (C) 2011 Alexander Larsson + * + * 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 . + */ + +using Gtk; +using Contacts; + + +public static int +compare_label (Widget a, Widget b) { + var aa = (a as Label).get_text (); + var bb = (b as Label).get_text (); + return strcmp (aa, bb); +} + +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, "blah2") != 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.add (new Label ("blah4")); + var l3 = new Label ("blah3"); + sorted.add (l3); + sorted.add (new Label ("blah1")); + sorted.add (new Label ("blah2")); + + 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 ( () => { + l3.set_label ("blah5"); + 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 l = new Label ("blah2 new %d".printf (new_button_nr++)); + sorted.add (l); + l.show (); + }); + + + w.show_all (); + + Gtk.main (); + + return 0; +} From 0ca78f5ffada3582d8c7792f9f03f3f13d6110ab Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Sat, 18 Feb 2012 09:49:26 +0100 Subject: [PATCH 02/51] Add separator support --- src/contacts-sorted.vala | 138 ++++++++++++++++++++++++++++++++------- src/test-sorted.vala | 55 ++++++++++++++-- 2 files changed, 162 insertions(+), 31 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 284108f..3633c59 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -32,7 +32,7 @@ using Gee; How to construct separators? What about resort a single item, can be problem if more change at the same time, need a stable sort... - + settings: sort function filter function @@ -41,7 +41,7 @@ using Gee; update_separator (if the child below it changes) ops: - child-changed (resort, refilter, + child-changed (resort, refilter, resort-all refilter-all @@ -52,15 +52,13 @@ using Gee; public class Contacts.Sorted : Container { public delegate bool FilterFunc (Widget child); - public delegate bool NeedSeparatorFunc (Widget? before, Widget widget); - public delegate Widget CreateSeparatorFunc (Widget child); - public delegate void UpdateSeparatorFunc (Widget separator, Widget child); + public delegate bool NeedSeparatorFunc (Widget widget, Widget? before); + public delegate Widget CreateSeparatorFunc (); + public delegate void UpdateSeparatorFunc (Widget separator, Widget child, Widget? before); struct ChildInfo { Widget widget; - bool is_separator; - bool has_separator; - int height; + Widget? separator; SequenceIter iter; } @@ -70,6 +68,7 @@ public class Contacts.Sorted : Container { FilterFunc? filter_func; NeedSeparatorFunc? need_separator_func; CreateSeparatorFunc? create_separator_func; + UpdateSeparatorFunc? update_separator_func; private int do_sort (ChildInfo? a, ChildInfo? b) { return sort_func (a.widget, b.widget); @@ -101,17 +100,82 @@ public class Contacts.Sorted : Container { filter_func = (owned)f; refilter (); } - + + public void set_separator_funcs (owned NeedSeparatorFunc? need_separator, + owned CreateSeparatorFunc? create_separator, + owned UpdateSeparatorFunc? update_separator = null) { + need_separator_func = (owned)need_separator; + create_separator_func = (owned)create_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 void update_separator (SequenceIter iter, SequenceIter? before_iter, + bool update_if_exist) { + if (iter.is_end ()) + return; + + unowned ChildInfo? info = iter.get (); + unowned ChildInfo? before_info = null; + if (!iter.is_begin()) { + if (before_iter == null) + before_info = iter.prev ().get (); + else + before_info = before_iter.get (); + } + + var widget = info.widget; + Widget? before_widget = before_info != null ? before_info.widget : null; + + bool need_separator = false; + + if (need_separator_func != null && + widget.get_visible () && + widget.get_child_visible ()) + need_separator = need_separator_func (widget, before_widget); + + if (need_separator) { + if (info.separator == null) { + info.separator = create_separator_func (); + info.separator.set_parent (this); + info.separator.show (); + if (update_separator_func != null) + update_separator_func (info.separator, widget, before_widget); + this.queue_resize (); + } else if (update_if_exist) { + if (update_separator_func != null) + update_separator_func (info.separator, widget, before_widget); + } + } else { + if (info.separator != null) { + info.separator.unparent (); + info.separator = null; + this.queue_resize (); + } + } + } + + public void reseparate () { + SequenceIter? last = null; + for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) { + update_separator (iter, last, false); + last = iter; + } + queue_resize (); + } + public void set_sort_func (owned CompareDataFunc? f) { sort_func = (owned)f; resort (); @@ -128,21 +192,24 @@ public class Contacts.Sorted : Container { 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 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); - + + update_separator (iter, null, true); + update_separator (iter.next (), iter, true); + info.iter = iter; widget.set_parent (this); @@ -158,18 +225,23 @@ public class Contacts.Sorted : Container { this.queue_resize (); } apply_filter (info.widget); + update_separator (info.iter, null, true); } - + public override void remove (Widget widget) { unowned ChildInfo? info = lookup_info (widget); if (info == null) return; + var next = info.iter.next (); + bool was_visible = widget.get_visible (); widget.unparent (); child_hash.unset (widget); - + + update_separator (next, null, false); + if (was_visible && this.get_visible ()) this.queue_resize (); } @@ -178,6 +250,8 @@ public class Contacts.Sorted : Container { 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); } } @@ -187,7 +261,7 @@ public class Contacts.Sorted : Container { /* We don't expand vertically beyound the minimum size */ vexpand = false; } - + public override Type child_type () { return typeof (Widget); } @@ -211,7 +285,7 @@ public class Contacts.Sorted : Container { if (!widget.get_visible () || !widget.get_child_visible ()) continue; - + widget.get_preferred_height_for_width (width, out child_min, null); minimum_height += child_min; } @@ -233,10 +307,16 @@ public class Contacts.Sorted : Container { 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); natural_width = int.max (natural_width, child_nat); + + 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); + } } } @@ -248,7 +328,7 @@ public class Contacts.Sorted : Container { Allocation child_allocation = { 0, 0, 0, 0}; set_allocation (allocation); - + child_allocation.x = allocation.x; child_allocation.y = allocation.y; child_allocation.width = allocation.width; @@ -260,12 +340,22 @@ public class Contacts.Sorted : Container { if (!widget.get_visible () || !widget.get_child_visible ()) continue; - + + if (child_info.separator != null) { + child_info.separator.get_preferred_height_for_width (allocation.width, out child_min, null); + child_allocation.height = child_min; + + child_info.separator.size_allocate (child_allocation); + + child_allocation.y += child_min; + } + + widget.get_preferred_height_for_width (allocation.width, out child_min, null); - child_allocation.height = child_info.height = child_min; + child_allocation.height = child_min; widget.size_allocate (child_allocation); - + child_allocation.y += child_min; } } diff --git a/src/test-sorted.vala b/src/test-sorted.vala index a940439..b5c2fea 100644 --- a/src/test-sorted.vala +++ b/src/test-sorted.vala @@ -19,6 +19,30 @@ using Gtk; using Contacts; +public bool need_separator (Widget widget, Widget? before) +{ + if (before == null) { + return true; + } + var text = (widget as Label).get_text (); + return strcmp (text, "blah3") == 0; +} + +public Widget create_separator () +{ + var l = new Button.with_label ("label"); + return l; +} + +public void update_separator (Widget separator, + Widget child, + Widget? before_widget) +{ + var text = (child as Label).get_text (); + (separator as Button).set_label ("Label %s".printf (text)); +} + + public static int compare_label (Widget a, Widget b) { @@ -35,18 +59,18 @@ compare_label_reverse (Widget a, Widget b) { public static bool filter (Widget widget) { var text = (widget as Label).get_text (); - return strcmp (text, "blah2") != 0; + 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); @@ -58,7 +82,7 @@ main (string[] args) { var vbox = new Box(Orientation.VERTICAL, 0); hbox.add (vbox); - + var b = new Button.with_label ("sort"); vbox.add (b); b.clicked.connect ( () => { @@ -74,7 +98,10 @@ main (string[] args) { b = new Button.with_label ("change"); vbox.add (b); b.clicked.connect ( () => { - l3.set_label ("blah5"); + if (l3.get_text () == "blah3") + l3.set_text ("blah5"); + else + l3.set_text ("blah3"); sorted.child_changed (l3); }); @@ -98,10 +125,24 @@ main (string[] args) { sorted.add (l); l.show (); }); - + + b = new Button.with_label ("separate"); + vbox.add (b); + b.clicked.connect ( () => { + sorted.set_separator_funcs (need_separator, + create_separator, + update_separator); + }); + + b = new Button.with_label ("unseparate"); + vbox.add (b); + b.clicked.connect ( () => { + sorted.set_separator_funcs (null, null, null); + }); + w.show_all (); - + Gtk.main (); return 0; From e8d9f74a1f1ad40744b017cb50277e454745b611 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Sat, 18 Feb 2012 09:58:23 +0100 Subject: [PATCH 03/51] Fix up separator handling when things move --- src/contacts-sorted.vala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 3633c59..6f0e741 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -25,6 +25,7 @@ using Gee; + first char or type custom "separators" (create, destroy, update) + Work with largish sets of children + + selection and keynave filter => child visibility setting @@ -207,8 +208,10 @@ public class Contacts.Sorted : Container { apply_filter (widget); + var prev_next = iter.next (); update_separator (iter, null, true); update_separator (iter.next (), iter, true); + update_separator (prev_next, null, true); info.iter = iter; @@ -220,12 +223,17 @@ public class Contacts.Sorted : Container { if (info == null) return; + var prev_next = info.iter.next (); + if (sort_func != null) { children.sort_changed (info.iter, do_sort); this.queue_resize (); } apply_filter (info.widget); update_separator (info.iter, null, true); + update_separator (info.iter.next (), info.iter, true); + update_separator (prev_next, null, true); + } public override void remove (Widget widget) { From e2d418384c4ee9e74702ec7c4b9226144e08f818 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Sat, 18 Feb 2012 10:02:00 +0100 Subject: [PATCH 04/51] sorted: Add input only window so we can get events --- src/contacts-sorted.vala | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 6f0e741..7598690 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -70,6 +70,7 @@ public class Contacts.Sorted : Container { NeedSeparatorFunc? need_separator_func; CreateSeparatorFunc? create_separator_func; UpdateSeparatorFunc? update_separator_func; + protected Gdk.Window event_window; private int do_sort (ChildInfo? a, ChildInfo? b) { return sort_func (a.widget, b.widget); @@ -83,6 +84,36 @@ public class Contacts.Sorted : Container { child_hash = new HashMap (); } + 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.EXPOSURE_MASK | Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK; + + var window = get_parent_window (); + this.set_window (window); + + attributes.wclass = Gdk.WindowWindowClass.INPUT_ONLY; + event_window = new Gdk.Window (window, attributes, + Gdk.WindowAttributesType.X | + Gdk.WindowAttributesType.Y); + event_window.set_user_data (this); + } + + public override void unrealize () { + event_window.set_user_data (null); + event_window.destroy (); + event_window = null; + base.unrealize (); + } + private void apply_filter (Widget child) { bool do_show = true; if (filter_func != null) @@ -183,10 +214,12 @@ public class Contacts.Sorted : Container { } public override void map () { + event_window.show (); base.map (); } public override void unmap () { + event_window.hide (); base.unmap (); } @@ -337,6 +370,12 @@ public class Contacts.Sorted : Container { set_allocation (allocation); + if (event_window != null) + event_window.move_resize (allocation.x, + allocation.y, + allocation.width, + allocation.height); + child_allocation.x = allocation.x; child_allocation.y = allocation.y; child_allocation.width = allocation.width; From 02d79e9779505eac02197e511c4ffb6c4c47c168 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Sun, 19 Feb 2012 15:45:34 +0100 Subject: [PATCH 05/51] Track selection --- src/contacts-sorted.vala | 49 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 7598690..23483ed 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -61,6 +61,8 @@ public class Contacts.Sorted : Container { Widget widget; Widget? separator; SequenceIter iter; + int y; + int height; } Sequence children; @@ -71,6 +73,7 @@ public class Contacts.Sorted : Container { CreateSeparatorFunc? create_separator_func; UpdateSeparatorFunc? update_separator_func; protected Gdk.Window event_window; + unowned ChildInfo? selected_child; private int do_sort (ChildInfo? a, ChildInfo? b) { return sort_func (a.widget, b.widget); @@ -84,6 +87,47 @@ public class Contacts.Sorted : Container { child_hash = new HashMap (); } + public override bool button_press_event (Gdk.EventButton event) { + if (event.button == 1) { + var y = event.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; + } + } + selected_child = child_info; + queue_draw (); + } + return false; + } + + public override bool draw (Cairo.Context cr) { + Allocation allocation; + this.get_allocation (out allocation); + + var context = this.get_style_context (); + + context.save (); + Gtk.render_background (context, cr, + 0, 0, allocation.width, allocation.height); + + if (selected_child != null) { + context.set_state (StateFlags.SELECTED); + Gtk.render_background (context, cr, + 0, selected_child.y, + allocation.width, selected_child.height); + } + + context.restore (); + + base.draw (cr); + + return true; + } + public override void realize () { Allocation allocation; get_allocation (out allocation); @@ -95,7 +139,7 @@ public class Contacts.Sorted : Container { attributes.width = allocation.width; attributes.height = allocation.height; attributes.window_type = Gdk.WindowType.CHILD; - attributes.event_mask = this.get_events () | Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK; + attributes.event_mask = this.get_events () | Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK; var window = get_parent_window (); this.set_window (window); @@ -397,10 +441,11 @@ public class Contacts.Sorted : Container { child_allocation.y += child_min; } - widget.get_preferred_height_for_width (allocation.width, out child_min, null); child_allocation.height = child_min; + child_info.y = child_allocation.y; + child_info.height = child_allocation.height; widget.size_allocate (child_allocation); child_allocation.y += child_min; From bd8316edee5834ba1a0435e3e7df314a892fbbbd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 20 Feb 2012 01:11:49 +0100 Subject: [PATCH 06/51] Initial use of ContactsSorted for view --- src/Makefile.am | 1 + src/contacts-link-dialog.vala | 2 +- src/contacts-list-pane.vala | 3 +- src/contacts-sorted.vala | 99 +++++-- src/contacts-view.vala | 490 ++++++++-------------------------- 5 files changed, 180 insertions(+), 415 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index f8423da..66bc8a9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,6 +33,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 \ diff --git a/src/contacts-link-dialog.vala b/src/contacts-link-dialog.vala index 03a61d4..186de24 100644 --- a/src/contacts-link-dialog.vala +++ b/src/contacts-link-dialog.vala @@ -220,7 +220,7 @@ 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.selection_changed.connect ( (c) => { diff --git a/src/contacts-list-pane.vala b/src/contacts-list-pane.vala index 50ae1fe..636c6d0 100644 --- a/src/contacts-list-pane.vala +++ b/src/contacts-list-pane.vala @@ -143,7 +143,7 @@ public class Contacts.ListPane : Frame { 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 +160,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; } } diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 23483ed..7045f40 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -99,11 +99,16 @@ public class Contacts.Sorted : Container { } } selected_child = child_info; + + child_selected (selected_child != null ? selected_child.widget : null); queue_draw (); } return false; } - + + public virtual signal void child_selected (Widget? child) { + } + public override bool draw (Cairo.Context cr) { Allocation allocation; this.get_allocation (out allocation); @@ -120,14 +125,14 @@ public class Contacts.Sorted : Container { 0, selected_child.y, allocation.width, selected_child.height); } - + context.restore (); base.draw (cr); return true; } - + public override void realize () { Allocation allocation; get_allocation (out allocation); @@ -198,22 +203,54 @@ public class Contacts.Sorted : Container { queue_resize (); } - private void update_separator (SequenceIter iter, SequenceIter? before_iter, - bool update_if_exist) { + private SequenceIter? get_previous_visible (SequenceIter _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? get_next_visible (SequenceIter _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 iter, bool update_if_exist) { if (iter.is_end ()) return; unowned ChildInfo? info = iter.get (); - unowned ChildInfo? before_info = null; - if (!iter.is_begin()) { - if (before_iter == null) - before_info = iter.prev ().get (); - else - before_info = before_iter.get (); - } - + var before_iter = get_previous_visible (iter); var widget = info.widget; - Widget? before_widget = before_info != null ? before_info.widget : null; + Widget? before_widget = null; + if (before_iter != null) { + unowned ChildInfo? before_info = before_iter.get (); + before_widget = before_info.widget; + } bool need_separator = false; @@ -244,10 +281,8 @@ public class Contacts.Sorted : Container { } public void reseparate () { - SequenceIter? last = null; for (var iter = children.get_begin_iter (); !iter.is_end (); iter = iter.next ()) { - update_separator (iter, last, false); - last = iter; + update_separator (iter, false); } queue_resize (); } @@ -285,10 +320,10 @@ public class Contacts.Sorted : Container { apply_filter (widget); - var prev_next = iter.next (); - update_separator (iter, null, true); - update_separator (iter.next (), iter, true); - update_separator (prev_next, null, true); + var prev_next = get_next_visible (iter); + update_separator (iter, true); + update_separator (get_next_visible (iter), true); + update_separator (prev_next, true); info.iter = iter; @@ -300,16 +335,16 @@ public class Contacts.Sorted : Container { if (info == null) return; - var prev_next = info.iter.next (); + 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, null, true); - update_separator (info.iter.next (), info.iter, true); - update_separator (prev_next, null, true); + update_separator (info.iter, true); + update_separator (get_next_visible (info.iter), true); + update_separator (prev_next, true); } @@ -318,14 +353,14 @@ public class Contacts.Sorted : Container { if (info == null) return; - var next = info.iter.next (); + var next = get_next_visible (info.iter); bool was_visible = widget.get_visible (); widget.unparent (); child_hash.unset (widget); - update_separator (next, null, false); + update_separator (next, false); if (was_visible && this.get_visible ()) this.queue_resize (); @@ -371,6 +406,11 @@ public class Contacts.Sorted : Container { 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, out child_min, null); minimum_height += child_min; } @@ -429,8 +469,11 @@ public class Contacts.Sorted : Container { unowned Widget widget = child_info.widget; int child_min; - if (!widget.get_visible () || !widget.get_child_visible ()) + 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); diff --git a/src/contacts-view.vala b/src/contacts-view.vala index dc7722e..c747bba 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -20,13 +20,16 @@ using Gtk; using Folks; using Gee; -public class Contacts.View : TreeView { +public class Contacts.View : 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,48 @@ 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 contacts; HashSet 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) { contacts_store = store; hidden_contacts = new HashSet(); show_subset = Subset.ALL; - 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 (); - 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 ("data"); + var b = widget_b.get_data ("data"); + return compare_data (a, b); }); - list_store.set_sort_column_id (0, SortType.ASCENDING); + this.set_filter_func (filter); + this.set_separator_funcs (need_separator, + create_separator, + 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) { @@ -90,13 +95,13 @@ public class Contacts.View : TreeView { 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.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; @@ -121,36 +126,9 @@ public class Contacts.View : TreeView { 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 (); } @@ -158,376 +136,120 @@ public class Contacts.View : TreeView { /* 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)); - } } 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_text (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 (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.image_frame = new ContactFrame (Contact.SMALL_AVATAR_SIZE); + data.label = new Label (""); + data.grid.add (data.image_frame); + data.grid.add (data.label); - c.set_lookup (this, data); + update_data (data); - update_visible (data); + data.grid.set_data ("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 (this); + var data = contacts.get (c); + data.grid.destroy (); + data.label.destroy (); + data.image_frame.destroy (); + this.remove (data.grid); + 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 ("data"); + selection_changed (data != null ? data.contact : null); } - - - 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); - } - - 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); + private bool need_separator (Widget widget, Widget? before) { + if (before == null) { + return true; } + var w_data = widget.get_data ("data"); + var before_data = before.get_data ("data"); - selection_changed (contact); + return w_data.initial_letter != before_data.initial_letter; + + return false; } - 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); - } + private Widget create_separator () { + var s = new Label ("---------------------"); + return s; + } + + private bool filter (Widget child) { + var data = child.get_data ("data"); + + return data.filtered; + } + + private void update_separator (Widget separator, + Widget child, + Widget? before_widget) { } } From e31fb4588e5d75c40100c06238d7ed8a290c1099 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 23 Feb 2012 16:46:09 +0100 Subject: [PATCH 07/51] sorted: Fix null check in update_separator --- src/contacts-sorted.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 7045f40..40e6b1a 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -239,8 +239,8 @@ public class Contacts.Sorted : Container { return iter; } - private void update_separator (SequenceIter iter, bool update_if_exist) { - if (iter.is_end ()) + private void update_separator (SequenceIter? iter, bool update_if_exist) { + if (iter == null || iter.is_end ()) return; unowned ChildInfo? info = iter.get (); From 2a3448f204a93458379738b2ccf49f7983d935f1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 23 Feb 2012 16:48:50 +0100 Subject: [PATCH 08/51] Add presence in new ContactsView --- src/contacts-view.vala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/contacts-view.vala b/src/contacts-view.vala index c747bba..34dea76 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -200,8 +200,16 @@ public class Contacts.View : Contacts.Sorted { data.grid = new Grid (); data.image_frame = new ContactFrame (Contact.SMALL_AVATAR_SIZE); data.label = new Label (""); - data.grid.add (data.image_frame); - data.grid.add (data.label); + data.label.set_ellipsize (Pango.EllipsizeMode.END); + + var merged_presence = c.create_merged_presence_widget (); + merged_presence.set_halign (Align.START); + merged_presence.set_valign (Align.START); + merged_presence.set_vexpand (true); + + data.grid.attach (data.image_frame, 0, 0, 1, 2); + data.grid.attach (data.label, 1, 0, 1, 1); + data.grid.attach (merged_presence, 1, 1, 1, 1); update_data (data); From a5dbd116bce5dcd855795974d30217f6f2161702 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 10 May 2012 14:27:55 +0200 Subject: [PATCH 09/51] Add more complex child widgets to test --- src/test-sorted.vala | 82 +++++++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/src/test-sorted.vala b/src/test-sorted.vala index b5c2fea..4dded3b 100644 --- a/src/test-sorted.vala +++ b/src/test-sorted.vala @@ -21,34 +21,42 @@ using Contacts; public bool need_separator (Widget widget, Widget? before) { - if (before == null) { - return true; - } - var text = (widget as Label).get_text (); - return strcmp (text, "blah3") == 0; + if (before == null) { + return true; + } + if (!(widget is Label)) + return false; + var text = (widget as Label).get_text (); + return strcmp (text, "blah3") == 0; } public Widget create_separator () { - var l = new Button.with_label ("label"); - return l; + 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 (); + return hbox; } public void update_separator (Widget separator, - Widget child, - Widget? before_widget) + Widget child, + Widget? before_widget) { - var text = (child as Label).get_text (); - (separator as Button).set_label ("Label %s".printf (text)); + var id = child.get_data("sort_id"); + var hbox = separator as Box; + var l = hbox.get_children ().data as Label; + l.set_text ("Separator %d".printf (id)); } - - public static int compare_label (Widget a, Widget b) { - var aa = (a as Label).get_text (); - var bb = (b as Label).get_text (); - return strcmp (aa, bb); + var aa = a.get_data("sort_id"); + var bb = b.get_data("sort_id"); + return bb - aa; } public static int @@ -74,12 +82,34 @@ main (string[] args) { var sorted = new Sorted(); hbox.add (sorted); - sorted.add (new Label ("blah4")); + 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); - sorted.add (new Label ("blah1")); - sorted.add (new Label ("blah2")); + 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_hbox = new Box (Orientation.HORIZONTAL, 0); + row_hbox.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); + sorted.add (row_hbox); + + 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); @@ -98,10 +128,13 @@ main (string[] args) { b = new Button.with_label ("change"); vbox.add (b); b.clicked.connect ( () => { - if (l3.get_text () == "blah3") + if (l3.get_text () == "blah3") { l3.set_text ("blah5"); - else + l3.set_data ("sort_id", 5); + } else { l3.set_text ("blah3"); + l3.set_data ("sort_id", 3); + } sorted.child_changed (l3); }); @@ -121,8 +154,11 @@ main (string[] args) { b = new Button.with_label ("add"); vbox.add (b); b.clicked.connect ( () => { - var l = new Label ("blah2 new %d".printf (new_button_nr++)); - sorted.add (l); + 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 (); }); From 36aefcf17c87557fcca1ee2b83ab93721a8dff3d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 10 May 2012 16:03:47 +0200 Subject: [PATCH 10/51] Track prelighted rows This needs us to be a widget window, otherwise we can't track prelight when inside inferior windows. --- src/contacts-sorted.vala | 119 +++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 42 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 40e6b1a..ca8a196 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -72,33 +72,74 @@ public class Contacts.Sorted : Container { NeedSeparatorFunc? need_separator_func; CreateSeparatorFunc? create_separator_func; UpdateSeparatorFunc? update_separator_func; - protected Gdk.Window event_window; unowned ChildInfo? selected_child; + unowned ChildInfo? prelight_child; private int do_sort (ChildInfo? a, ChildInfo? b) { return sort_func (a.widget, b.widget); } public Sorted () { - set_has_window (false); + set_has_window (true); set_redraw_on_allocate (false); children = new Sequence(); child_hash = new HashMap (); } + 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_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; + } + public override bool button_press_event (Gdk.EventButton event) { if (event.button == 1) { - var y = event.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; - } - } - selected_child = child_info; + unowned ChildInfo? child = find_child_at_y ((int)event.y); + selected_child = child; child_selected (selected_child != null ? selected_child.widget : null); queue_draw (); @@ -126,6 +167,13 @@ public class Contacts.Sorted : Container { allocation.width, selected_child.height); } + if (prelight_child != null && prelight_child != selected_child) { + context.set_state (StateFlags.PRELIGHT); + Gtk.render_background (context, cr, + 0, prelight_child.y, + allocation.width, prelight_child.height); + } + context.restore (); base.draw (cr); @@ -144,23 +192,19 @@ public class Contacts.Sorted : Container { attributes.width = allocation.width; attributes.height = allocation.height; attributes.window_type = Gdk.WindowType.CHILD; - attributes.event_mask = this.get_events () | Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK; + 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; - var window = get_parent_window (); + 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); - - attributes.wclass = Gdk.WindowWindowClass.INPUT_ONLY; - event_window = new Gdk.Window (window, attributes, - Gdk.WindowAttributesType.X | - Gdk.WindowAttributesType.Y); - event_window.set_user_data (this); - } - - public override void unrealize () { - event_window.set_user_data (null); - event_window.destroy (); - event_window = null; - base.unrealize (); } private void apply_filter (Widget child) { @@ -292,16 +336,6 @@ public class Contacts.Sorted : Container { resort (); } - public override void map () { - event_window.show (); - base.map (); - } - - public override void unmap () { - event_window.hide (); - base.unmap (); - } - private unowned ChildInfo? lookup_info (Widget widget) { return child_hash.get (widget); } @@ -454,11 +488,12 @@ public class Contacts.Sorted : Container { set_allocation (allocation); - if (event_window != null) - event_window.move_resize (allocation.x, - allocation.y, - allocation.width, - allocation.height); + var window = get_window(); + if (window != null) + window.move_resize (allocation.x, + allocation.y, + allocation.width, + allocation.height); child_allocation.x = allocation.x; child_allocation.y = allocation.y; From 9c20c4f8bda35e75a57e6d51a2a6c26797947657 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 10 May 2012 16:35:45 +0200 Subject: [PATCH 11/51] Redraw on allocate (we paint the bg) --- src/contacts-sorted.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index ca8a196..6ef995c 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -81,7 +81,7 @@ public class Contacts.Sorted : Container { public Sorted () { set_has_window (true); - set_redraw_on_allocate (false); + set_redraw_on_allocate (true); children = new Sequence(); child_hash = new HashMap (); From d263103e389705fd4f249c3f9f8acd9a5c62a813 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 10 May 2012 16:11:41 +0200 Subject: [PATCH 12/51] Support focus handling --- src/contacts-sorted.vala | 144 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 4 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 6ef995c..b57dcbb 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -74,12 +74,15 @@ public class Contacts.Sorted : Container { UpdateSeparatorFunc? update_separator_func; unowned ChildInfo? selected_child; unowned ChildInfo? prelight_child; + /* Only set if the row is focused, not if a child is focused, valid if has_focus. */ + unowned ChildInfo? focus_child; 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); @@ -98,6 +101,19 @@ public class Contacts.Sorted : Container { } return child_info; } + + private void update_selected (ChildInfo? child) { + if (child != selected_child) { + selected_child = child; + child_selected (selected_child != null ? selected_child.widget : null); + queue_draw (); + } + if (child != null) { + focus_child = child; + this.grab_focus (); + this.queue_draw (); + } + } private void update_prelight (ChildInfo? child) { if (child != prelight_child) { @@ -139,10 +155,7 @@ public class Contacts.Sorted : Container { public override bool button_press_event (Gdk.EventButton event) { if (event.button == 1) { unowned ChildInfo? child = find_child_at_y ((int)event.y); - selected_child = child; - - child_selected (selected_child != null ? selected_child.widget : null); - queue_draw (); + update_selected (child); } return false; } @@ -150,6 +163,102 @@ public class Contacts.Sorted : Container { public virtual signal void child_selected (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 focus child */ + recurse_into = focus_child.widget; + } + current_focus_child = focus_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 + + 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) + return false; + + focus_child = next_focus_child; + this.grab_focus (); + this.queue_draw (); + + return true; + } + public override bool draw (Cairo.Context cr) { Allocation allocation; this.get_allocation (out allocation); @@ -174,6 +283,11 @@ public class Contacts.Sorted : Container { allocation.width, prelight_child.height); } + if (has_visible_focus() && focus_child != null) { + context.render_focus (cr, 0, focus_child.y, + allocation.width, focus_child.height); + } + context.restore (); base.draw (cr); @@ -247,6 +361,28 @@ public class Contacts.Sorted : Container { 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? get_previous_visible (SequenceIter _iter) { if (_iter.is_begin()) return null; From 620bacad697c6020f79ed9298bd79854bc94bbc1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 10 May 2012 21:50:53 +0200 Subject: [PATCH 13/51] Make space for focus line and padding --- src/contacts-sorted.vala | 60 +++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index b57dcbb..35b2b13 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -101,7 +101,7 @@ public class Contacts.Sorted : Container { } return child_info; } - + private void update_selected (ChildInfo? child) { if (child != selected_child) { selected_child = child; @@ -173,7 +173,7 @@ public class Contacts.Sorted : Container { 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 || @@ -191,10 +191,10 @@ public class Contacts.Sorted : Container { /* 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) + direction == DirectionType.TAB_FORWARD) focus_into = false; /* If exiting child container to the left, select row or out */ @@ -287,7 +287,7 @@ public class Contacts.Sorted : Container { context.render_focus (cr, 0, focus_child.y, allocation.width, focus_child.height); } - + context.restore (); base.draw (cr); @@ -307,7 +307,7 @@ public class Contacts.Sorted : Container { attributes.height = allocation.height; attributes.window_type = Gdk.WindowType.CHILD; attributes.event_mask = this.get_events () | - Gdk.EventMask.ENTER_NOTIFY_MASK | + Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.EXPOSURE_MASK | @@ -382,7 +382,7 @@ public class Contacts.Sorted : Container { } return null; } - + private SequenceIter? get_previous_visible (SequenceIter _iter) { if (_iter.is_begin()) return null; @@ -568,6 +568,10 @@ public class Contacts.Sorted : Container { 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; @@ -581,9 +585,11 @@ public class Contacts.Sorted : Container { minimum_height += child_min; } - widget.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 @@ -593,6 +599,10 @@ public class Contacts.Sorted : Container { } 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 ()) { @@ -604,8 +614,8 @@ public class Contacts.Sorted : Container { continue; widget.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); + 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); @@ -621,6 +631,7 @@ public class Contacts.Sorted : Container { 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); @@ -631,9 +642,17 @@ public class Contacts.Sorted : Container { allocation.width, allocation.height); - child_allocation.x = allocation.x; + 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; + 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 (); @@ -648,21 +667,24 @@ public class Contacts.Sorted : Container { if (child_info.separator != null) { child_info.separator.get_preferred_height_for_width (allocation.width, out child_min, null); - child_allocation.height = child_min; + separator_allocation.height = child_min; + separator_allocation.y = child_allocation.y; - child_info.separator.size_allocate (child_allocation); + child_info.separator.size_allocate (separator_allocation); child_allocation.y += child_min; } - widget.get_preferred_height_for_width (allocation.width, out child_min, null); + 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.y = child_allocation.y; - child_info.height = child_allocation.height; + child_info.height = child_allocation.height + 2 * (focus_width + focus_pad); widget.size_allocate (child_allocation); - child_allocation.y += child_min; + child_allocation.y += child_min + focus_width + focus_pad; } } } From 3ac7deb791f07f6683aa4f438f4dd03b9f5df5cc Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 10:12:50 +0200 Subject: [PATCH 14/51] Add update_focus helper --- src/contacts-sorted.vala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 35b2b13..cfeece5 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -102,17 +102,20 @@ public class Contacts.Sorted : Container { return child_info; } + private void update_focus (ChildInfo? child) { + focus_child = child; + this.grab_focus (); + this.queue_draw (); + } + private void update_selected (ChildInfo? child) { if (child != selected_child) { selected_child = child; child_selected (selected_child != null ? selected_child.widget : null); queue_draw (); } - if (child != null) { - focus_child = child; - this.grab_focus (); - this.queue_draw (); - } + if (child != null) + update_focus (child); } private void update_prelight (ChildInfo? child) { @@ -252,9 +255,7 @@ public class Contacts.Sorted : Container { if (next_focus_child == null) return false; - focus_child = next_focus_child; - this.grab_focus (); - this.queue_draw (); + update_focus (next_focus_child); return true; } From 67e45d169a3d2ef3da0c553c1c792d6347600dba Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 10:13:04 +0200 Subject: [PATCH 15/51] Add initial keyboard navigation --- src/contacts-sorted.vala | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index cfeece5..d1ba6da 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -90,6 +90,41 @@ public class Contacts.Sorted : Container { child_hash = new HashMap (); } + [Signal (action=true)] + public virtual signal void select_row () { + update_selected (focus_child); + } + + [Signal (action=true)] + public virtual signal void move_cursor (MovementStep step, int count) { + unowned ChildInfo? child = null; + if (step == MovementStep.BUFFER_ENDS) { + if (count < 0) + child = get_first_visible (); + else + child = get_last_visible (); + } + + update_focus (child); + } + + [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); + + BindingEntry.add_signal (binding_set, Gdk.Key.Home, 0, + "move-cursor", 2, + typeof (MovementStep), MovementStep.BUFFER_ENDS, + typeof (int), -1); + BindingEntry.add_signal (binding_set, Gdk.Key.End, 0, + "move-cursor", 2, + typeof (MovementStep), MovementStep.BUFFER_ENDS, + typeof (int), 1); + + activate_signal = GLib.Signal.lookup ("select-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 ()) { From b45ddb74b534b60e00227e7c40ec3cbd9fe9dd05 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 13:19:45 +0200 Subject: [PATCH 16/51] Add child_activate signal --- src/contacts-sorted.vala | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index d1ba6da..ce786be 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -91,8 +91,8 @@ public class Contacts.Sorted : Container { } [Signal (action=true)] - public virtual signal void select_row () { - update_selected (focus_child); + public virtual signal void activate_row () { + select_and_activate (focus_child); } [Signal (action=true)] @@ -122,7 +122,7 @@ public class Contacts.Sorted : Container { typeof (MovementStep), MovementStep.BUFFER_ENDS, typeof (int), 1); - activate_signal = GLib.Signal.lookup ("select-row", typeof (Sorted)); + activate_signal = GLib.Signal.lookup ("activate-row", typeof (Sorted)); } unowned ChildInfo? find_child_at_y (int y) { @@ -153,6 +153,15 @@ public class Contacts.Sorted : Container { update_focus (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; @@ -193,14 +202,24 @@ public class Contacts.Sorted : Container { public override bool button_press_event (Gdk.EventButton event) { if (event.button == 1) { unowned ChildInfo? child = find_child_at_y ((int)event.y); - update_selected (child); + select_and_activate (child); } 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; @@ -559,6 +578,14 @@ public class Contacts.Sorted : Container { if (info == null) return; + if (info == selected_child) { + update_selected (null); + } + if (info == prelight_child) + prelight_child = null; + if (info == focus_child) + focus_child = null; + var next = get_next_visible (info.iter); bool was_visible = widget.get_visible (); From 3076139b41152f56ad10594b29bf3220162e75a9 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 14:29:06 +0200 Subject: [PATCH 17/51] Test child selection and activation signals --- src/test-sorted.vala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/test-sorted.vala b/src/test-sorted.vala index 4dded3b..5294585 100644 --- a/src/test-sorted.vala +++ b/src/test-sorted.vala @@ -82,6 +82,14 @@ main (string[] args) { 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); @@ -109,7 +117,7 @@ main (string[] args) { button.set_hexpand (false); button.set_halign (Align.START); sorted.add (button); - + var vbox = new Box(Orientation.VERTICAL, 0); hbox.add (vbox); From 087200ef2c8c31661d57bce9eee090f4aeaa8866 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 14:29:47 +0200 Subject: [PATCH 18/51] Support selection_mode --- src/contacts-sorted.vala | 130 +++++++++++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 26 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index ce786be..da031d9 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -74,8 +74,8 @@ public class Contacts.Sorted : Container { UpdateSeparatorFunc? update_separator_func; unowned ChildInfo? selected_child; unowned ChildInfo? prelight_child; - /* Only set if the row is focused, not if a child is focused, valid if has_focus. */ - unowned ChildInfo? focus_child; + unowned ChildInfo? cursor_child; + private SelectionMode selection_mode; private int do_sort (ChildInfo? a, ChildInfo? b) { return sort_func (a.widget, b.widget); @@ -86,26 +86,76 @@ public class Contacts.Sorted : Container { set_has_window (true); set_redraw_on_allocate (true); + selection_mode = SelectionMode.SINGLE; + children = new Sequence(); child_hash = new HashMap (); } [Signal (action=true)] public virtual signal void activate_row () { - select_and_activate (focus_child); + 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; - if (step == MovementStep.BUFFER_ENDS) { + switch (step) { + case MovementStep.BUFFER_ENDS: if (count < 0) child = get_first_visible (); else child = get_last_visible (); + break; + case MovementStep.DISPLAY_LINES: + + break; + default: + return; } - update_focus (child); + 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")] @@ -113,14 +163,20 @@ public class Contacts.Sorted : Container { static construct { unowned BindingSet binding_set = BindingSet.by_class (workaround_for_local_var_klass); - BindingEntry.add_signal (binding_set, Gdk.Key.Home, 0, - "move-cursor", 2, - typeof (MovementStep), MovementStep.BUFFER_ENDS, - typeof (int), -1); - BindingEntry.add_signal (binding_set, Gdk.Key.End, 0, - "move-cursor", 2, - typeof (MovementStep), MovementStep.BUFFER_ENDS, - typeof (int), 1); + 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); + + /* TODO: Add PgUp/PgDown */ + + BindingEntry.add_signal (binding_set, Gdk.Key.space, Gdk.ModifierType.CONTROL_MASK, + "modify-selection", 0); activate_signal = GLib.Signal.lookup ("activate-row", typeof (Sorted)); } @@ -137,20 +193,21 @@ public class Contacts.Sorted : Container { return child_info; } - private void update_focus (ChildInfo? child) { - focus_child = child; + private void update_cursor (ChildInfo? child) { + cursor_child = child; this.grab_focus (); this.queue_draw (); } private void update_selected (ChildInfo? child) { - if (child != selected_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_focus (child); + update_cursor (child); } private void select_and_activate (ChildInfo? child) { @@ -235,10 +292,10 @@ public class Contacts.Sorted : Container { /* If on row, going right, enter into possible container */ if (direction == DirectionType.RIGHT || direction == DirectionType.TAB_FORWARD) { - /* TODO: Handle null focus child */ - recurse_into = focus_child.widget; + /* TODO: Handle null cursor child */ + recurse_into = cursor_child.widget; } - current_focus_child = focus_child; + current_focus_child = cursor_child; /* Unless we're going up/down we're always leaving the container */ if (direction != DirectionType.UP && @@ -309,7 +366,18 @@ public class Contacts.Sorted : Container { if (next_focus_child == null) return false; - update_focus (next_focus_child); + 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; } @@ -338,9 +406,9 @@ public class Contacts.Sorted : Container { allocation.width, prelight_child.height); } - if (has_visible_focus() && focus_child != null) { - context.render_focus (cr, 0, focus_child.y, - allocation.width, focus_child.height); + if (has_visible_focus() && cursor_child != null) { + context.render_focus (cr, 0, cursor_child.y, + allocation.width, cursor_child.height); } context.restore (); @@ -390,6 +458,16 @@ public class Contacts.Sorted : Container { } } + public void set_selection_mode (SelectionMode mode) { + if (mode == SelectionMode.MULTIPLE) { + error ("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 (); @@ -583,8 +661,8 @@ public class Contacts.Sorted : Container { } if (info == prelight_child) prelight_child = null; - if (info == focus_child) - focus_child = null; + if (info == cursor_child) + cursor_child = null; var next = get_next_visible (info.iter); From a06929cb46d6355b06176ac2bd016f12873ca2bd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 14:40:32 +0200 Subject: [PATCH 19/51] Don't select on button press but on release (like a button) --- src/contacts-sorted.vala | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index da031d9..cbff98d 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -256,10 +256,24 @@ public class Contacts.Sorted : Container { 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); - select_and_activate (child); + 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; } @@ -434,7 +448,8 @@ public class Contacts.Sorted : Container { Gdk.EventMask.LEAVE_NOTIFY_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.EXPOSURE_MASK | - Gdk.EventMask.BUTTON_PRESS_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, From c46616cdb5e23ccf9e413e40d27b192b9e3909bf Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 14:40:52 +0200 Subject: [PATCH 20/51] Update todos --- src/contacts-sorted.vala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index cbff98d..b2552d6 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -25,14 +25,9 @@ using Gee; + first char or type custom "separators" (create, destroy, update) + Work with largish sets of children - + selection and keynave - - filter => child visibility setting - - Q: - How to construct separators? - What about resort a single item, can be problem if more change - at the same time, need a stable sort... + + selection and keynav + + activation (separate from selection) + + select mode (but not multiple) settings: sort function @@ -347,6 +342,8 @@ public class Contacts.Sorted : Container { 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) { From b56d38b78d6f8b7cdcd4e0ed2347bd89d2a503fd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 15:38:56 +0200 Subject: [PATCH 21/51] Remove iter with on container.remove --- src/contacts-sorted.vala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index b2552d6..7a48118 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -665,12 +665,13 @@ public class Contacts.Sorted : Container { public override void remove (Widget widget) { unowned ChildInfo? info = lookup_info (widget); - if (info == null) + if (info == null) { + warning ("Tried to remove non-child %p\n", widget); return; - - if (info == selected_child) { - update_selected (null); } + + if (info == selected_child) + update_selected (null); if (info == prelight_child) prelight_child = null; if (info == cursor_child) @@ -682,6 +683,7 @@ public class Contacts.Sorted : Container { widget.unparent (); child_hash.unset (widget); + children.remove (info.iter); update_separator (next, false); From 3ecb5b4bc4c15c68cf5873b585220fa4d274fc14 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 15:39:39 +0200 Subject: [PATCH 22/51] Don't remove data.grid twice --- src/contacts-view.vala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/contacts-view.vala b/src/contacts-view.vala index 34dea76..fe78242 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -224,7 +224,6 @@ public class Contacts.View : Contacts.Sorted { data.grid.destroy (); data.label.destroy (); data.image_frame.destroy (); - this.remove (data.grid); contacts.unset (c); } From 7600e8f1d2ef9177ed905d30d5d04edd95bddcad Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 15:52:02 +0200 Subject: [PATCH 23/51] Support focus_child --- src/contacts-sorted.vala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 7a48118..c97ddf0 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -192,6 +192,10 @@ public class Contacts.Sorted : Container { 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) { From e6ec0e8b4ab3884386d62bcadd20e70b01e7eb5d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 15:52:39 +0200 Subject: [PATCH 24/51] Use focus_vadj for list view --- src/contacts-link-dialog.vala | 1 + src/contacts-list-pane.vala | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/contacts-link-dialog.vala b/src/contacts-link-dialog.vala index 186de24..7024168 100644 --- a/src/contacts-link-dialog.vala +++ b/src/contacts-link-dialog.vala @@ -222,6 +222,7 @@ public class Contacts.LinkDialog : Dialog { scrolled.set_shadow_type (ShadowType.NONE); scrolled.add_with_viewport (view); list_grid.add (scrolled); + view.set_focus_vadjustment (scrolled.get_vadjustment ()); view.selection_changed.connect ( (c) => { selected_contact = c; diff --git a/src/contacts-list-pane.vala b/src/contacts-list-pane.vala index 636c6d0..af4a934 100644 --- a/src/contacts-list-pane.vala +++ b/src/contacts-list-pane.vala @@ -138,6 +138,8 @@ 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); From d4958d150d444c0647fdc35e03cb871522038e0b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 16:03:16 +0200 Subject: [PATCH 25/51] warn, not error on MULTIPLE selections --- src/contacts-sorted.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index c97ddf0..9b7807c 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -476,7 +476,7 @@ public class Contacts.Sorted : Container { public void set_selection_mode (SelectionMode mode) { if (mode == SelectionMode.MULTIPLE) { - error ("Multiple selections not supported"); + warning ("Multiple selections not supported"); return; } selection_mode = mode; From 16e60db53ef4141911f14c96301be2873c4b2611 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 16:49:21 +0200 Subject: [PATCH 26/51] Update ContactPresense when persona set changes We might have added or removed a telepathy persona --- src/contacts-contact.vala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/contacts-contact.vala b/src/contacts-contact.vala index 27c2bf1..e1ac4e5 100644 --- a/src/contacts-contact.vala +++ b/src/contacts-contact.vala @@ -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); }); } } From 6bfb93f1c08cf333d4dbc481ef93db946c8933d6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 11 May 2012 16:50:03 +0200 Subject: [PATCH 27/51] Fix up ContactsView layout --- src/contacts-view.vala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/contacts-view.vala b/src/contacts-view.vala index fe78242..7179828 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -178,7 +178,7 @@ public class Contacts.View : Contacts.Sorted { data.initial_letter = c.initial_letter; data.filtered = calculate_filtered (c); - data.label.set_text (data.display_name); + data.label.set_markup ("" + data.display_name + ""); data.image_frame.set_image (c.individual, c); } @@ -198,14 +198,18 @@ public class Contacts.View : Contacts.Sorted { var data = new ContactData(); data.contact = c; data.grid = new Grid (); + data.grid.margin = 12; + data.grid.set_column_spacing (10); data.image_frame = new ContactFrame (Contact.SMALL_AVATAR_SIZE); data.label = new Label (""); data.label.set_ellipsize (Pango.EllipsizeMode.END); + data.label.set_valign (Align.START); var merged_presence = c.create_merged_presence_widget (); merged_presence.set_halign (Align.START); - merged_presence.set_valign (Align.START); - merged_presence.set_vexpand (true); + merged_presence.set_valign (Align.END); + merged_presence.set_vexpand (false); + merged_presence.set_margin_bottom (4); data.grid.attach (data.image_frame, 0, 0, 1, 2); data.grid.attach (data.label, 1, 0, 1, 1); @@ -245,7 +249,7 @@ public class Contacts.View : Contacts.Sorted { } private Widget create_separator () { - var s = new Label ("---------------------"); + var s = new Separator (Orientation.HORIZONTAL); return s; } From 6da20da5249ff3d7e25df385bf2463f8466f2f2b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 10:57:49 +0200 Subject: [PATCH 28/51] Use get_preferred_width_internal() --- src/contacts-sorted.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 9b7807c..fa2b83c 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -721,7 +721,7 @@ public class Contacts.Sorted : Container { public override void get_preferred_height (out int minimum_height, out int natural_height) { int natural_width; - get_preferred_width (null, out natural_width); + get_preferred_width_internal (null, out natural_width); get_preferred_height_for_width_internal (natural_width, out minimum_height, out natural_height); } @@ -785,7 +785,7 @@ public class Contacts.Sorted : Container { } public override void get_preferred_width_for_height (int height, out int minimum_width, out int natural_width) { - get_preferred_width (out minimum_width, out natural_width); + get_preferred_width_internal (out minimum_width, out natural_width); } public override void size_allocate (Gtk.Allocation allocation) { From 7217975b00fbfaf8ee208549da7af86a60470077 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 10:58:17 +0200 Subject: [PATCH 29/51] Remove unnecessary workaround define No need for get_preferred_height_for_width_internal define anymore as vala seems to have been fixed. --- src/Makefile.am | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Makefile.am b/src/Makefile.am index 66bc8a9..e3b150b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,7 +7,6 @@ AM_CPPFLAGS = \ -DPKGDATADIR=\""$(pkgdatadir)"\" \ -DPKGLIBDIR=\""$(pkglibdir)"\" \ -DGNOME_DESKTOP_USE_UNSTABLE_API \ - -Dget_preferred_height_for_width_internal=get_preferred_height_for_width $(NULL) AM_VALAFLAGS = \ From cc5143998291cb2a214597a715db70e9dd7ceb67 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 11:58:45 +0200 Subject: [PATCH 30/51] Properly escape strings passed to set_markup --- src/contacts-avatar-dialog.vala | 2 +- src/contacts-contact-pane.vala | 14 +++++++------- src/contacts-contact.vala | 2 +- src/contacts-link-dialog.vala | 6 +++--- src/contacts-setup-window.vala | 2 +- src/contacts-view.vala | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/contacts-avatar-dialog.vala b/src/contacts-avatar-dialog.vala index dce0f4a..1b46aab 100644 --- a/src/contacts-avatar-dialog.vala +++ b/src/contacts-avatar-dialog.vala @@ -266,7 +266,7 @@ public class Contacts.AvatarDialog : Dialog { grid.attach (main_frame, 0, 0, 1, 1); var label = new Label (""); - label.set_markup ("" + contact.display_name + ""); + label.set_markup (Markup.printf_escaped ("%s", contact.display_name)); label.set_valign (Align.START); label.set_halign (Align.START); label.set_hexpand (true); diff --git a/src/contacts-contact-pane.vala b/src/contacts-contact-pane.vala index 8a9a46f..8682cc9 100644 --- a/src/contacts-contact-pane.vala +++ b/src/contacts-contact-pane.vala @@ -204,7 +204,7 @@ public class Contacts.FieldRow : Contacts.Row { public void pack_header (string s) { var l = new Label (s); l.set_markup ( - "%s".printf (s)); + Markup.printf_escaped ("%s", 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 ( - "%s".printf (s)); + Markup.printf_escaped ("%s", 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 ("" + contact.display_name + ""); + (w as Label).set_markup (Markup.printf_escaped ("%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 ("" + entry.get_text () + ""); + l.set_markup (Markup.printf_escaped ("%s", 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 ("" + contact.display_name + ""); + l.set_markup (Markup.printf_escaped ("%s", 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); diff --git a/src/contacts-contact.vala b/src/contacts-contact.vala index e1ac4e5..8cb29cc 100644 --- a/src/contacts-contact.vala +++ b/src/contacts-contact.vala @@ -65,7 +65,7 @@ public class Contacts.ContactPresence : Grid { if (message.length == 0) message = Contact.presence_to_string (type); - label.set_markup ("" + message + ""); + label.set_markup (Markup.printf_escaped ("%s", message)); label.set_margin_bottom (3); if (is_phone) diff --git a/src/contacts-link-dialog.vala b/src/contacts-link-dialog.vala index 7024168..cefec4e 100644 --- a/src/contacts-link-dialog.vala +++ b/src/contacts-link-dialog.vala @@ -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 ("" + selected_contact.display_name + ""); + label.set_markup (Markup.printf_escaped ("%s", 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 ("" +selected_contact.format_persona_stores () + ""); + label.set_markup (Markup.printf_escaped ("%s", 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 (_("Link contacts to %s").printf (contact.display_name)); + label.set_markup (Markup.printf_escaped (_("Link contacts to %s"), contact.display_name)); else label.set_markup (_("Select contact to link to")); label.set_valign (Align.CENTER); diff --git a/src/contacts-setup-window.vala b/src/contacts-setup-window.vala index 0ccd9cb..359b987 100644 --- a/src/contacts-setup-window.vala +++ b/src/contacts-setup-window.vala @@ -147,7 +147,7 @@ public class Contacts.SetupWindow : Gtk.Window { var item = new ToolItem (); title_label = new Label (""); - title_label.set_markup ("%s".printf (_("Contacts Setup"))); + title_label.set_markup (Markup.printf_escaped ("%s",_("Contacts Setup"))); title_label.set_no_show_all (true); item.add (title_label); item.set_expand (true); diff --git a/src/contacts-view.vala b/src/contacts-view.vala index 7179828..9ece203 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -178,7 +178,7 @@ public class Contacts.View : Contacts.Sorted { data.initial_letter = c.initial_letter; data.filtered = calculate_filtered (c); - data.label.set_markup ("" + data.display_name + ""); + data.label.set_markup (Markup.printf_escaped ("%s", data.display_name)); data.image_frame.set_image (c.individual, c); } From 78d218cb2f2c86a9d65a87cf0f334e933646e109 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 12:18:22 +0200 Subject: [PATCH 31/51] Test multiline rows --- src/test-sorted.vala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test-sorted.vala b/src/test-sorted.vala index 5294585..fe2592c 100644 --- a/src/test-sorted.vala +++ b/src/test-sorted.vala @@ -103,15 +103,19 @@ main (string[] args) { 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_hbox.set_data ("sort_id", 3); + 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); - sorted.add (row_hbox); + 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); From 01df16164a138d13ec7ae8bbe20d1c84221c550a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 12:18:40 +0200 Subject: [PATCH 32/51] Fix up ctrl up/down inside scrolled window --- src/contacts-sorted.vala | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index fa2b83c..3d5e25e 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -126,12 +126,32 @@ public class Contacts.Sorted : Container { child = get_last_visible (); break; case MovementStep.DISPLAY_LINES: + if (cursor_child != null) { + SequenceIter? 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; default: return; } + if (child == null) { + error_bell (); + return; + } + update_cursor (child); if (!modify_selection_pressed) update_selected (child); @@ -168,6 +188,16 @@ public class Contacts.Sorted : Container { 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); + /* TODO: Add PgUp/PgDown */ BindingEntry.add_signal (binding_set, Gdk.Key.space, Gdk.ModifierType.CONTROL_MASK, From 1ab1cf76f948251f0bf7d3ea1605c0d7800b1d70 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 15:09:57 +0200 Subject: [PATCH 33/51] Support pgup/down --- src/contacts-sorted.vala | 52 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 3d5e25e..adfca42 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -141,7 +141,49 @@ public class Contacts.Sorted : Container { 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? 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; @@ -198,7 +240,15 @@ public class Contacts.Sorted : Container { add_move_binding (binding_set, Gdk.Key.KP_Down, Gdk.ModifierType.CONTROL_MASK, MovementStep.DISPLAY_LINES, 1); - /* TODO: Add PgUp/PgDown */ + 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); From dba1a863f39c390a39e3a398d3d338e306defb0f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 15:11:50 +0200 Subject: [PATCH 34/51] Use BROWSE selection mode --- src/contacts-view.vala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/contacts-view.vala b/src/contacts-view.vala index 9ece203..383acad 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -60,6 +60,7 @@ public class Contacts.View : Contacts.Sorted { 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(); show_subset = Subset.ALL; From d5a40ab91b08b86984736335dedbd9e56c1bedc5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 15:37:14 +0200 Subject: [PATCH 35/51] Fix up non-match in markup --- src/contacts-contact-pane.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contacts-contact-pane.vala b/src/contacts-contact-pane.vala index 8682cc9..1ab385d 100644 --- a/src/contacts-contact-pane.vala +++ b/src/contacts-contact-pane.vala @@ -1552,7 +1552,7 @@ public class Contacts.ContactPane : ScrolledWindow { l.xalign = 0.0f; contact.keep_widget_uptodate (l, (w) => { - (w as Label).set_markup (Markup.printf_escaped ("%s/span>", contact.display_name)); + (w as Label).set_markup (Markup.printf_escaped ("%s", contact.display_name)); }); var event_box = new EventBox (); From 2d5e2ad20e205d408a9dfeb3c745976af68dda23 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 16:19:57 +0200 Subject: [PATCH 36/51] Delay showing of link hints to make scrolling faster Actually finding the matches takes some time which causes keynav to become pretty slow. --- src/contacts-app.vala | 2 +- src/contacts-contact-pane.vala | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/contacts-app.vala b/src/contacts-app.vala index 5771396..ad9ad7d 100644 --- a/src/contacts-app.vala +++ b/src/contacts-app.vala @@ -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) { diff --git a/src/contacts-contact-pane.vala b/src/contacts-contact-pane.vala index 1ab385d..2b7ff6a 100644 --- a/src/contacts-contact-pane.vala +++ b/src/contacts-contact-pane.vala @@ -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; From bb54cc89a6e344aa2f9816d923f3a87b63e9fc6e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 16:30:19 +0200 Subject: [PATCH 37/51] Stop in list at ends --- src/contacts-sorted.vala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index adfca42..70e0a85 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -458,8 +458,14 @@ public class Contacts.Sorted : Container { } } - if (next_focus_child == null) + 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; From 5cf772ef710d918808b3b0194ea8e7cee50281a6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 17:05:52 +0200 Subject: [PATCH 38/51] Switch to only one separator func --- src/contacts-sorted.vala | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 70e0a85..1a58617 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -49,8 +49,7 @@ using Gee; public class Contacts.Sorted : Container { public delegate bool FilterFunc (Widget child); public delegate bool NeedSeparatorFunc (Widget widget, Widget? before); - public delegate Widget CreateSeparatorFunc (); - public delegate void UpdateSeparatorFunc (Widget separator, Widget child, Widget? before); + public delegate void UpdateSeparatorFunc (ref Widget? separator, Widget child, Widget? before); struct ChildInfo { Widget widget; @@ -65,7 +64,6 @@ public class Contacts.Sorted : Container { CompareDataFunc? sort_func; FilterFunc? filter_func; NeedSeparatorFunc? need_separator_func; - CreateSeparatorFunc? create_separator_func; UpdateSeparatorFunc? update_separator_func; unowned ChildInfo? selected_child; unowned ChildInfo? prelight_child; @@ -576,10 +574,8 @@ public class Contacts.Sorted : Container { } public void set_separator_funcs (owned NeedSeparatorFunc? need_separator, - owned CreateSeparatorFunc? create_separator, - owned UpdateSeparatorFunc? update_separator = null) { + owned UpdateSeparatorFunc? update_separator) { need_separator_func = (owned)need_separator; - create_separator_func = (owned)create_separator; update_separator_func = (owned)update_separator; reseparate (); } @@ -674,17 +670,16 @@ public class Contacts.Sorted : Container { widget.get_child_visible ()) need_separator = need_separator_func (widget, before_widget); - if (need_separator) { - if (info.separator == null) { - info.separator = create_separator_func (); + if (need_separator && + (info.separator == null || update_if_exist)) { + 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 (); info.separator.set_parent (this); info.separator.show (); - if (update_separator_func != null) - update_separator_func (info.separator, widget, before_widget); this.queue_resize (); - } else if (update_if_exist) { - if (update_separator_func != null) - update_separator_func (info.separator, widget, before_widget); } } else { if (info.separator != null) { From b7106199d499013698677a831a36cb4b20193d78 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 17:06:17 +0200 Subject: [PATCH 39/51] update test for separator func changes --- src/test-sorted.vala | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/test-sorted.vala b/src/test-sorted.vala index fe2592c..8a181ce 100644 --- a/src/test-sorted.vala +++ b/src/test-sorted.vala @@ -30,22 +30,21 @@ public bool need_separator (Widget widget, Widget? before) return strcmp (text, "blah3") == 0; } -public Widget create_separator () -{ - 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 (); - return hbox; -} - -public void update_separator (Widget separator, +public void update_separator (ref Widget? separator, Widget child, Widget? before_widget) { + 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 id = child.get_data("sort_id"); var hbox = separator as Box; var l = hbox.get_children ().data as Label; @@ -178,14 +177,13 @@ main (string[] args) { vbox.add (b); b.clicked.connect ( () => { sorted.set_separator_funcs (need_separator, - create_separator, - update_separator); + update_separator); }); b = new Button.with_label ("unseparate"); vbox.add (b); b.clicked.connect ( () => { - sorted.set_separator_funcs (null, null, null); + sorted.set_separator_funcs (null, null); }); From 3afc08f557ebe20482de971032bc2387e3871091 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 17:06:34 +0200 Subject: [PATCH 40/51] Update view for separator changes --- src/contacts-view.vala | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/contacts-view.vala b/src/contacts-view.vala index 383acad..7743c0f 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -74,7 +74,6 @@ public class Contacts.View : Contacts.Sorted { }); this.set_filter_func (filter); this.set_separator_funcs (need_separator, - create_separator, update_separator); contacts_store.added.connect (contact_added_cb); @@ -131,6 +130,8 @@ public class Contacts.View : Contacts.Sorted { show_subset = subset; update_all_filtered (); refilter (); + resort (); + reseparate (); } public void set_custom_sort_prio (Contact c, int prio) { @@ -241,17 +242,14 @@ public class Contacts.View : Contacts.Sorted { if (before == null) { return true; } + var w_data = widget.get_data ("data"); var before_data = before.get_data ("data"); + if (is_other (w_data) && !is_other (before_data)) + return true; + return w_data.initial_letter != before_data.initial_letter; - - return false; - } - - private Widget create_separator () { - var s = new Separator (Orientation.HORIZONTAL); - return s; } private bool filter (Widget child) { @@ -260,8 +258,27 @@ public class Contacts.View : Contacts.Sorted { return data.filtered; } - private void update_separator (Widget separator, + private void update_separator (ref Widget? separator, Widget child, Widget? before_widget) { + var w_data = child.get_data ("data"); + ContactData? before_data = null; + if (before_widget != null) { + before_data = before_widget.get_data ("data"); + } + + if (is_other (w_data) && + (before_data == null || !is_other (before_data))) { + if (separator == null || !(separator is Label)) { + var l = new Label (""); + l.set_markup (Markup.printf_escaped ("%s", _("Other Contacts"))); + l.set_halign (Align.START); + separator = l; + } + return; + } + + if (separator == null || !(separator is Separator)) + separator = new Separator (Orientation.HORIZONTAL); } } From 629da7939a9c4bffb5a5ee614252f3021cb1d15b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 17:16:02 +0200 Subject: [PATCH 41/51] Should not need extra reseparate as it happens in refilter already --- src/contacts-view.vala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/contacts-view.vala b/src/contacts-view.vala index 7743c0f..ef1ed45 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -131,7 +131,6 @@ public class Contacts.View : Contacts.Sorted { update_all_filtered (); refilter (); resort (); - reseparate (); } public void set_custom_sort_prio (Contact c, int prio) { From 62ce9fc103c90d20223ed9ac4c0ccffd0e4f53ae Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 17:19:09 +0200 Subject: [PATCH 42/51] Fix up separator handling --- src/contacts-sorted.vala | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 1a58617..fe993d6 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -670,16 +670,17 @@ public class Contacts.Sorted : Container { widget.get_child_visible ()) need_separator = need_separator_func (widget, before_widget); - if (need_separator && - (info.separator == null || update_if_exist)) { - 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 (); - info.separator.set_parent (this); - info.separator.show (); - this.queue_resize (); + if (need_separator) { + if (info.separator == null || update_if_exist) { + 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 (); + info.separator.set_parent (this); + info.separator.show (); + this.queue_resize (); + } } } else { if (info.separator != null) { From 49dfd7829844d8795f5f6be65b7d041100b5b0e5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 May 2012 21:38:06 +0200 Subject: [PATCH 43/51] Remove some now unused vars in Contacts.View --- src/contacts-view.vala | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/contacts-view.vala b/src/contacts-view.vala index ef1ed45..f928b6e 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -53,10 +53,6 @@ public class Contacts.View : Contacts.Sorted { HashSet 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) { @@ -92,11 +88,8 @@ public class Contacts.View : Contacts.Sorted { if (a_prio < b_prio) return 1; - var a = a_data.contact; - var b = b_data.contact; - if (is_set (a_data.display_name) && is_set (b_data.display_name)) - return a.display_name.collate (b_data.display_name); + return a_data.display_name.collate (b_data.display_name); // Sort empty names last if (is_set (a_data.display_name)) From 976191f0577ef5792ff02e1f5c4d9f08494aab05 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 15 May 2012 10:47:54 +0200 Subject: [PATCH 44/51] Merge need_separator into update_separator --- src/contacts-sorted.vala | 58 ++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index fe993d6..8d938ef 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -32,9 +32,7 @@ using Gee; settings: sort function filter function - needs_separator function - create_separator - update_separator (if the child below it changes) + update_separator ops: child-changed (resort, refilter, @@ -63,7 +61,6 @@ public class Contacts.Sorted : Container { HashMap child_hash; CompareDataFunc? sort_func; FilterFunc? filter_func; - NeedSeparatorFunc? need_separator_func; UpdateSeparatorFunc? update_separator_func; unowned ChildInfo? selected_child; unowned ChildInfo? prelight_child; @@ -573,9 +570,7 @@ public class Contacts.Sorted : Container { refilter (); } - public void set_separator_funcs (owned NeedSeparatorFunc? need_separator, - owned UpdateSeparatorFunc? update_separator) { - need_separator_func = (owned)need_separator; + public void set_separator_funcs (owned UpdateSeparatorFunc? update_separator) { update_separator_func = (owned)update_separator; reseparate (); } @@ -650,7 +645,7 @@ public class Contacts.Sorted : Container { return iter; } - private void update_separator (SequenceIter? iter, bool update_if_exist) { + private void update_separator (SequenceIter? iter) { if (iter == null || iter.is_end ()) return; @@ -663,37 +658,30 @@ public class Contacts.Sorted : Container { before_widget = before_info.widget; } - bool need_separator = false; - - if (need_separator_func != null && + if (update_separator_func != null && widget.get_visible () && - widget.get_child_visible ()) - need_separator = need_separator_func (widget, before_widget); - - if (need_separator) { - if (info.separator == null || update_if_exist) { - 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 (); + 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 (); } + } 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, false); + update_separator (iter); } queue_resize (); } @@ -722,9 +710,9 @@ public class Contacts.Sorted : Container { apply_filter (widget); var prev_next = get_next_visible (iter); - update_separator (iter, true); - update_separator (get_next_visible (iter), true); - update_separator (prev_next, true); + update_separator (iter); + update_separator (get_next_visible (iter)); + update_separator (prev_next); info.iter = iter; @@ -743,9 +731,9 @@ public class Contacts.Sorted : Container { this.queue_resize (); } apply_filter (info.widget); - update_separator (info.iter, true); - update_separator (get_next_visible (info.iter), true); - update_separator (prev_next, true); + update_separator (info.iter); + update_separator (get_next_visible (info.iter)); + update_separator (prev_next); } @@ -771,7 +759,7 @@ public class Contacts.Sorted : Container { child_hash.unset (widget); children.remove (info.iter); - update_separator (next, false); + update_separator (next); if (was_visible && this.get_visible ()) this.queue_resize (); From 5acaabc929db7beba86d844d54104d0f5d5a46d3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 15 May 2012 10:48:13 +0200 Subject: [PATCH 45/51] Update test --- src/test-sorted.vala | 54 ++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/src/test-sorted.vala b/src/test-sorted.vala index 8a181ce..2af61d5 100644 --- a/src/test-sorted.vala +++ b/src/test-sorted.vala @@ -19,36 +19,31 @@ using Gtk; using Contacts; -public bool need_separator (Widget widget, Widget? before) -{ - if (before == null) { - return true; - } - if (!(widget is Label)) - return false; - var text = (widget as Label).get_text (); - return strcmp (text, "blah3") == 0; -} - public void update_separator (ref Widget? separator, - Widget child, - Widget? before_widget) + Widget widget, + Widget? before) { - 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; - } + 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 id = child.get_data("sort_id"); - var hbox = separator as Box; - var l = hbox.get_children ().data as Label; - l.set_text ("Separator %d".printf (id)); + var hbox = separator as Box; + var id = widget.get_data("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 @@ -176,14 +171,13 @@ main (string[] args) { b = new Button.with_label ("separate"); vbox.add (b); b.clicked.connect ( () => { - sorted.set_separator_funcs (need_separator, - update_separator); + sorted.set_separator_funcs (update_separator); }); b = new Button.with_label ("unseparate"); vbox.add (b); b.clicked.connect ( () => { - sorted.set_separator_funcs (null, null); + sorted.set_separator_funcs (null); }); From 4c1289d637316f5b18a842200581e06477f758e2 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 15 May 2012 10:48:25 +0200 Subject: [PATCH 46/51] Implemente all header separators in ContactsView --- src/contacts-view.vala | 67 ++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/src/contacts-view.vala b/src/contacts-view.vala index f928b6e..d0ce09c 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -69,8 +69,7 @@ public class Contacts.View : Contacts.Sorted { return compare_data (a, b); }); this.set_filter_func (filter); - this.set_separator_funcs (need_separator, - update_separator); + this.set_separator_funcs (update_separator); contacts_store.added.connect (contact_added_cb); contacts_store.removed.connect (contact_removed_cb); @@ -109,13 +108,13 @@ public class Contacts.View : Contacts.Sorted { } /* 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; } @@ -133,6 +132,8 @@ public class Contacts.View : Contacts.Sorted { var data = contacts.get (c); if (data == null) return; + data.sort_prio = prio; + child_changed (data.grid); } public void hide_contact (Contact contact) { @@ -230,20 +231,6 @@ public class Contacts.View : Contacts.Sorted { selection_changed (data != null ? data.contact : null); } - private bool need_separator (Widget widget, Widget? before) { - if (before == null) { - return true; - } - - var w_data = widget.get_data ("data"); - var before_data = before.get_data ("data"); - - if (is_other (w_data) && !is_other (before_data)) - return true; - - return w_data.initial_letter != before_data.initial_letter; - } - private bool filter (Widget child) { var data = child.get_data ("data"); @@ -251,18 +238,43 @@ public class Contacts.View : Contacts.Sorted { } private void update_separator (ref Widget? separator, - Widget child, + Widget widget, Widget? before_widget) { - var w_data = child.get_data ("data"); + var w_data = widget.get_data ("data"); ContactData? before_data = null; - if (before_widget != null) { + if (before_widget != null) before_data = before_widget.get_data ("data"); - } + if (before_data == null && w_data.sort_prio > 0) { + if (separator == null || + !(separator.get_data ("contacts-suggestions-header"))) { + var l = new Label (""); + l.set_data ("contacts-suggestions-header", true); + l.set_markup (Markup.printf_escaped ("%s", _("Suggestions"))); + l.set_halign (Align.START); + separator = l; + } + return; + } + + if (before_data != null && before_data.sort_prio > 0 && + w_data.sort_prio == 0) { + if (separator == null || + !(separator.get_data ("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 is Label)) { + if (separator == null || + !(separator.get_data ("contacts-other-header"))) { var l = new Label (""); + l.set_data ("contacts-other-header", true); l.set_markup (Markup.printf_escaped ("%s", _("Other Contacts"))); l.set_halign (Align.START); separator = l; @@ -270,7 +282,12 @@ public class Contacts.View : Contacts.Sorted { return; } - if (separator == null || !(separator is Separator)) - separator = new Separator (Orientation.HORIZONTAL); + 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; } } From bc0eaef4f150f4a5890cd66f3756a577a03ac46f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 15 May 2012 11:00:04 +0200 Subject: [PATCH 47/51] Implement text_display --- src/contacts-view.vala | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/contacts-view.vala b/src/contacts-view.vala index d0ce09c..be3fcd6 100644 --- a/src/contacts-view.vala +++ b/src/contacts-view.vala @@ -60,6 +60,7 @@ public class Contacts.View : Contacts.Sorted { contacts_store = store; hidden_contacts = new HashSet(); show_subset = Subset.ALL; + this.text_display = text_display; contacts = new HashMap (); @@ -199,16 +200,30 @@ public class Contacts.View : Contacts.Sorted { data.label = new Label (""); data.label.set_ellipsize (Pango.EllipsizeMode.END); data.label.set_valign (Align.START); - - 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.label.set_halign (Align.START); data.grid.attach (data.image_frame, 0, 0, 1, 2); data.grid.attach (data.label, 1, 0, 1, 1); - data.grid.attach (merged_presence, 1, 1, 1, 1); + + if (text_display == TextDisplay.PRESENCE) { + var merged_presence = c.create_merged_presence_widget (); + merged_presence.set_halign (Align.START); + merged_presence.set_valign (Align.END); + merged_presence.set_vexpand (false); + merged_presence.set_margin_bottom (4); + + data.grid.attach (merged_presence, 1, 1, 1, 1); + } + + if (text_display == TextDisplay.STORES) { + var stores = new Label (""); + stores.set_markup (Markup.printf_escaped ("%s", + 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); @@ -256,9 +271,9 @@ public class Contacts.View : Contacts.Sorted { } return; } - + if (before_data != null && before_data.sort_prio > 0 && - w_data.sort_prio == 0) { + w_data.sort_prio == 0) { if (separator == null || !(separator.get_data ("contacts-rest-header"))) { var l = new Label (""); @@ -268,7 +283,7 @@ public class Contacts.View : Contacts.Sorted { } return; } - + if (is_other (w_data) && (before_data == null || !is_other (before_data))) { if (separator == null || From dba1129de7e19128f96477fc70cacbc1fd912e91 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 15 May 2012 11:01:21 +0200 Subject: [PATCH 48/51] Remove now unused Contacts.CellRendererShape --- src/Makefile.am | 1 - src/contacts-cell-renderer-shape.vala | 330 -------------------------- 2 files changed, 331 deletions(-) delete mode 100644 src/contacts-cell-renderer-shape.vala diff --git a/src/Makefile.am b/src/Makefile.am index e3b150b..0d99c5c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,6 @@ 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 \ diff --git a/src/contacts-cell-renderer-shape.vala b/src/contacts-cell-renderer-shape.vala deleted file mode 100644 index 076c997..0000000 --- a/src/contacts-cell-renderer-shape.vala +++ /dev/null @@ -1,330 +0,0 @@ -/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */ -/* - * Copyright (C) 2011 Alexander Larsson - * - * 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 . - */ - -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.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.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 sattr = (Pango.AttrShape)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(); - } - } -} From d09f179b9df2907c0d3e1fcf5ec42e53f94c9c80 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 15 May 2012 11:04:38 +0200 Subject: [PATCH 49/51] Don't use deprecated functions in Sorted --- src/contacts-sorted.vala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 8d938ef..49f097c 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -485,21 +485,21 @@ public class Contacts.Sorted : Container { var context = this.get_style_context (); context.save (); - Gtk.render_background (context, cr, - 0, 0, allocation.width, allocation.height); + context.render_background (cr, + 0, 0, allocation.width, allocation.height); if (selected_child != null) { context.set_state (StateFlags.SELECTED); - Gtk.render_background (context, cr, - 0, selected_child.y, - allocation.width, selected_child.height); + 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); - Gtk.render_background (context, cr, - 0, prelight_child.y, - allocation.width, prelight_child.height); + context.render_background (cr, + 0, prelight_child.y, + allocation.width, prelight_child.height); } if (has_visible_focus() && cursor_child != null) { From aa084b01117d1fa3e4575f61f00aed03e79fe4c9 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 15 May 2012 11:26:09 +0200 Subject: [PATCH 50/51] Don't use Gee in Sorted --- src/contacts-sorted.vala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index 49f097c..c786820 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -17,7 +17,6 @@ */ using Gtk; -using Gee; /* Requriements: + sort @@ -58,7 +57,7 @@ public class Contacts.Sorted : Container { } Sequence children; - HashMap child_hash; + HashTable child_hash; CompareDataFunc? sort_func; FilterFunc? filter_func; UpdateSeparatorFunc? update_separator_func; @@ -79,7 +78,7 @@ public class Contacts.Sorted : Container { selection_mode = SelectionMode.SINGLE; children = new Sequence(); - child_hash = new HashMap (); + child_hash = new HashTable (GLib.direct_hash, GLib.direct_equal); } [Signal (action=true)] @@ -756,7 +755,7 @@ public class Contacts.Sorted : Container { bool was_visible = widget.get_visible (); widget.unparent (); - child_hash.unset (widget); + child_hash.remove (widget); children.remove (info.iter); update_separator (next); From 6f4030530b4bb11e28ecaaf7c78488a82f02da89 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 15 May 2012 11:28:31 +0200 Subject: [PATCH 51/51] Remove unused typedef --- src/contacts-sorted.vala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/contacts-sorted.vala b/src/contacts-sorted.vala index c786820..71a11b8 100644 --- a/src/contacts-sorted.vala +++ b/src/contacts-sorted.vala @@ -45,7 +45,6 @@ using Gtk; public class Contacts.Sorted : Container { public delegate bool FilterFunc (Widget child); - public delegate bool NeedSeparatorFunc (Widget widget, Widget? before); public delegate void UpdateSeparatorFunc (ref Widget? separator, Widget child, Widget? before); struct ChildInfo {