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; +}