Initial version of ContactsSorted

This commit is contained in:
Alexander Larsson 2012-02-17 16:11:57 +01:00
parent 5a21167bd5
commit ec2939c8a9
3 changed files with 389 additions and 1 deletions

View file

@ -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

272
src/contacts-sorted.vala Normal file
View file

@ -0,0 +1,272 @@
/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
/*
* Copyright (C) 2011 Alexander Larsson <alexl@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Gtk;
using 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<ChildInfo?> iter;
}
Sequence<ChildInfo?> children;
HashMap<unowned Widget, unowned ChildInfo?> child_hash;
CompareDataFunc<Widget>? 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<ChildInfo?>();
child_hash = new HashMap<unowned Widget, unowned ChildInfo?> ();
}
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<Widget>? 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<ChildInfo?> iter;
child_hash.set (widget, info);
if (sort_func != null)
iter = children.insert_sorted ((owned) the_info, do_sort);
else
iter = children.append ((owned) the_info);
apply_filter (widget);
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;
}
}
}

108
src/test-sorted.vala Normal file
View file

@ -0,0 +1,108 @@
/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
/*
* Copyright (C) 2011 Alexander Larsson <alexl@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Gtk;
using Contacts;
public 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;
}