[gtk/wip/otte/listview: 1/4] listview: Implement ::section-factory property
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/listview: 1/4] listview: Implement ::section-factory property
- Date: Fri, 25 Feb 2022 22:05:32 +0000 (UTC)
commit 857478b7442d712b665bf2aa0a59249283c0d58c
Author: Benjamin Otte <otte redhat com>
Date: Sat Feb 19 22:55:25 2022 +0100
listview: Implement ::section-factory property
Add a property to listview, pipe it to the listitemmanager and make sure
the manager creates the sections so that listview can display it.
gtk/gtklistitemmanager.c | 217 ++++++++++++++++++++++++++++++++++++++--
gtk/gtklistitemmanagerprivate.h | 4 +
gtk/gtklistview.c | 165 +++++++++++++++++++++++++-----
gtk/gtklistview.h | 7 ++
4 files changed, 360 insertions(+), 33 deletions(-)
---
diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c
index 146f589bcd..b41fb51505 100644
--- a/gtk/gtklistitemmanager.c
+++ b/gtk/gtklistitemmanager.c
@@ -22,6 +22,7 @@
#include "gtklistitemmanagerprivate.h"
#include "gtklistitemwidgetprivate.h"
+#include "gtksectionmodelprivate.h"
#include "gtkwidgetprivate.h"
#define GTK_LIST_VIEW_MAX_LIST_ITEMS 200
@@ -33,6 +34,7 @@ struct _GtkListItemManager
GtkWidget *widget;
GtkSelectionModel *model;
GtkListItemFactory *factory;
+ GtkListItemFactory *section_factory;
gboolean single_click_activate;
const char *item_css_name;
GtkAccessibleRole item_role;
@@ -59,6 +61,7 @@ struct _GtkListItemTracker
static GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
guint position,
+ gboolean is_header,
GtkWidget *prev_sibling);
static GtkWidget * gtk_list_item_manager_try_reacquire_list_item
(GtkListItemManager *self,
@@ -145,6 +148,7 @@ gtk_list_item_manager_clear_node (gpointer _item)
GtkListItemManagerItem *item G_GNUC_UNUSED = _item;
g_assert (item->widget == NULL);
+ g_assert (item->section_header == NULL);
}
GtkListItemManager *
@@ -427,6 +431,141 @@ restart:
}
}
+static gboolean
+gtk_list_item_manager_has_sections (GtkListItemManager *self)
+{
+ return self->section_factory != NULL;
+}
+
+static void
+gtk_list_item_manager_clear_sections (GtkListItemManager *self)
+{
+ GtkListItemManagerItem *item;
+
+ for (item = gtk_rb_tree_get_first (self->items);
+ item != NULL;
+ item = gtk_rb_tree_node_get_next (item))
+ {
+ if (item->section_header)
+ {
+ gtk_list_item_manager_release_list_item (self, NULL, item->section_header);
+ item->section_header = NULL;
+ }
+ }
+}
+
+typedef struct _SectionIter SectionIter;
+struct _SectionIter
+{
+ GtkListItemManagerItem *item;
+ guint item_pos;
+ GtkWidget *insert_after;
+};
+
+static void
+section_iter_init (SectionIter *siter,
+ GtkListItemManager *self)
+{
+ siter->item = gtk_rb_tree_get_first (self->items);
+ siter->item_pos = 0;
+ siter->insert_after = NULL;
+}
+
+static void
+section_iter_next (SectionIter *siter)
+{
+ siter->item_pos += siter->item->n_items;
+ if (siter->item->widget)
+ siter->insert_after = siter->item->widget;
+ siter->item = gtk_rb_tree_node_get_next (siter->item);
+}
+
+static void
+section_iter_put_at (SectionIter *siter,
+ GtkListItemManager *self,
+ guint pos)
+{
+ /* We already put an item there */
+ if (siter->item_pos == pos + 1)
+ return;
+
+ g_assert (pos >= siter->item_pos);
+
+ while (siter->item_pos < pos)
+ {
+ if (siter->item->section_header)
+ {
+ gtk_list_item_manager_release_list_item (self, NULL, siter->item->section_header);
+ siter->item->section_header = NULL;
+ }
+
+ if (siter->item->n_items + siter->item_pos > pos)
+ gtk_list_item_manager_split_item (self, siter->item, pos - siter->item_pos);
+
+ section_iter_next (siter);
+ }
+
+ g_assert (siter->item_pos == pos);
+
+ while (siter->item->n_items == 0)
+ section_iter_next (siter);
+
+ if (siter->item->n_items > 1)
+ gtk_list_item_manager_split_item (self, siter->item, 1);
+
+ if (siter->item->section_header == NULL)
+ {
+ siter->item->section_header = gtk_list_item_manager_acquire_list_item (self,
+ pos,
+ TRUE,
+ siter->insert_after);
+ }
+
+ section_iter_next (siter);
+}
+
+static void
+section_iter_finish (SectionIter *siter,
+ GtkListItemManager *self)
+{
+ while (siter->item)
+ section_iter_next (siter);
+}
+
+static void
+gtk_list_item_manager_ensure_sections (GtkListItemManager *self)
+{
+ SectionIter siter;
+ guint pos, n_items, query_n_items, section_start, section_end;
+ gboolean tracked;
+
+ if (!gtk_list_item_manager_has_sections (self) || self->model == NULL)
+ return;
+
+ pos = 0;
+ n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
+ section_iter_init (&siter, self);
+
+ while (pos < n_items)
+ {
+ gtk_list_item_query_tracked_range (self, n_items, pos, &query_n_items, &tracked);
+ if (tracked)
+ {
+ gtk_list_model_get_section (G_LIST_MODEL (self->model), pos, §ion_start, §ion_end);
+ section_iter_put_at (&siter, self, section_start);
+
+ while (section_end < pos + query_n_items)
+ {
+ gtk_list_model_get_section (G_LIST_MODEL (self->model), section_end, §ion_start,
§ion_end);
+ section_iter_put_at (&siter, self, section_start);
+ }
+ }
+ pos += query_n_items;
+ }
+
+ section_iter_finish (&siter, self);
+}
+
static void
gtk_list_item_manager_remove_items (GtkListItemManager *self,
GHashTable *change,
@@ -452,8 +591,15 @@ gtk_list_item_manager_remove_items (GtkListItemManager *self,
{
GtkListItemManagerItem *next = gtk_rb_tree_node_get_next (item);
if (item->widget)
- gtk_list_item_manager_release_list_item (self, change, item->widget);
- item->widget = NULL;
+ {
+ gtk_list_item_manager_release_list_item (self, change, item->widget);
+ item->widget = NULL;
+ }
+ if (item->section_header)
+ {
+ gtk_list_item_manager_release_list_item (self, NULL, item->section_header);
+ item->section_header = NULL;
+ }
n_items -= item->n_items;
item->n_items = 0;
gtk_rb_tree_node_mark_dirty (item);
@@ -509,7 +655,8 @@ gtk_list_item_manager_merge_list_items (GtkListItemManager *self,
GtkListItemManagerItem *first,
GtkListItemManagerItem *second)
{
- if (first->widget || second->widget)
+ if (first->widget || first->section_header ||
+ second->widget || second->section_header)
return FALSE;
first->n_items += second->n_items;
@@ -575,6 +722,11 @@ gtk_list_item_manager_release_items (GtkListItemManager *self,
g_queue_push_tail (released, item->widget);
item->widget = NULL;
}
+ if (item->section_header)
+ {
+ gtk_list_item_manager_release_list_item (self, NULL, item->section_header);
+ item->section_header = NULL;
+ }
i += item->n_items;
item = gtk_rb_tree_node_get_next (item);
}
@@ -668,6 +820,7 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
{
new_item->widget = gtk_list_item_manager_acquire_list_item (self,
position + i,
+ FALSE,
insert_after);
}
}
@@ -675,7 +828,11 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
else
{
if (update_start <= position + i)
- gtk_list_item_manager_update_list_item (self, new_item->widget, position + i);
+ {
+ gtk_list_item_manager_update_list_item (self, new_item->widget, position + i);
+ if (new_item->section_header)
+ gtk_list_item_manager_update_list_item (self, new_item->section_header, position + i);
+ }
}
insert_after = new_item->widget;
}
@@ -685,6 +842,8 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
while ((widget = g_queue_pop_head (&released)))
gtk_list_item_manager_release_list_item (self, NULL, widget);
+ gtk_list_item_manager_ensure_sections (self);
+
dump_items (self);
}
@@ -976,6 +1135,31 @@ gtk_list_item_manager_get_factory (GtkListItemManager *self)
return self->factory;
}
+void
+gtk_list_item_manager_set_section_factory (GtkListItemManager *self,
+ GtkListItemFactory *factory)
+{
+ g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
+ g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory));
+
+ if (self->section_factory == factory)
+ return;
+
+ gtk_list_item_manager_clear_sections (self);
+
+ g_set_object (&self->section_factory, factory);
+
+ gtk_list_item_manager_ensure_sections (self);
+}
+
+GtkListItemFactory *
+gtk_list_item_manager_get_section_factory (GtkListItemManager *self)
+{
+ g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
+
+ return self->factory;
+}
+
void
gtk_list_item_manager_set_model (GtkListItemManager *self,
GtkSelectionModel *model)
@@ -1017,6 +1201,8 @@ gtk_list_item_manager_get_model (GtkListItemManager *self)
* gtk_list_item_manager_acquire_list_item:
* @self: a `GtkListItemManager`
* @position: the row in the model to create a list item for
+ * @is_header: %TRUE if this is for acquiring a header, %FALSE for regular
+ * list items
* @prev_sibling: the widget this widget should be inserted before or %NULL
* if it should be the first widget
*
@@ -1034,6 +1220,7 @@ gtk_list_item_manager_get_model (GtkListItemManager *self)
static GtkWidget *
gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
guint position,
+ gboolean is_header,
GtkWidget *prev_sibling)
{
GtkWidget *result;
@@ -1043,14 +1230,24 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
- result = gtk_list_item_widget_new (self->factory,
- self->item_css_name,
- self->item_role);
-
- gtk_list_item_widget_set_single_click_activate (GTK_LIST_ITEM_WIDGET (result),
self->single_click_activate);
+ if (is_header)
+ {
+ result = gtk_list_item_widget_new (self->section_factory,
+ "section",
+ GTK_ACCESSIBLE_ROLE_ROW_HEADER);
+ gtk_list_item_widget_set_activatable (GTK_LIST_ITEM_WIDGET (result), FALSE);
+ selected = FALSE;
+ }
+ else
+ {
+ result = gtk_list_item_widget_new (self->factory,
+ self->item_css_name,
+ self->item_role);
+ selected = gtk_selection_model_is_selected (self->model, position);
+ gtk_list_item_widget_set_single_click_activate (GTK_LIST_ITEM_WIDGET (result),
self->single_click_activate);
+ }
item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
- selected = gtk_selection_model_is_selected (self->model, position);
gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (result), position, item, selected);
g_object_unref (item);
gtk_widget_insert_after (result, self->widget, prev_sibling);
diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h
index 57f760bd09..5ba3b7bc98 100644
--- a/gtk/gtklistitemmanagerprivate.h
+++ b/gtk/gtklistitemmanagerprivate.h
@@ -46,6 +46,7 @@ typedef struct _GtkListItemTracker GtkListItemTracker;
struct _GtkListItemManagerItem
{
GtkWidget *widget;
+ GtkWidget *section_header;
guint n_items;
};
@@ -90,6 +91,9 @@ void gtk_list_item_manager_gc (GtkListItemMana
void gtk_list_item_manager_set_factory (GtkListItemManager *self,
GtkListItemFactory *factory);
GtkListItemFactory * gtk_list_item_manager_get_factory (GtkListItemManager *self);
+void gtk_list_item_manager_set_section_factory (GtkListItemManager *self,
+ GtkListItemFactory *factory);
+GtkListItemFactory * gtk_list_item_manager_get_section_factory (GtkListItemManager *self);
void gtk_list_item_manager_set_model (GtkListItemManager *self,
GtkSelectionModel *model);
GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemManager *self);
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index 4d69d43f83..83ed650988 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -148,6 +148,7 @@ typedef struct _ListRowAugment ListRowAugment;
struct _ListRow
{
GtkListItemManagerItem parent;
+ guint section_height;
guint height; /* per row */
};
@@ -164,6 +165,7 @@ enum
PROP_FOCUS_ITEM,
PROP_FOCUS_POSITION,
PROP_MODEL,
+ PROP_SECTION_FACTORY,
PROP_SHOW_SEPARATORS,
PROP_SINGLE_CLICK_ACTIVATE,
PROP_ENABLE_RUBBERBAND,
@@ -215,7 +217,7 @@ list_row_augment (GtkRbTree *tree,
gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
- aug->height = row->height;
+ aug->height = row->height + row->section_height;
if (left)
{
@@ -255,9 +257,9 @@ gtk_list_view_get_row_at_y (GtkListView *self,
y -= aug->height;
}
- if (y < row->height)
+ if (y < row->height + row->section_height)
break;
- y -= row->height;
+ y -= row->height + row->section_height;
row = gtk_rb_tree_node_get_right (row);
}
@@ -297,7 +299,7 @@ list_row_get_y (GtkListView *self,
ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left);
y += aug->height;
}
- y += parent->height;
+ y += parent->height + parent->section_height;
}
row = parent;
@@ -329,12 +331,13 @@ gtk_list_view_get_allocation_along (GtkListBase *base,
y = list_row_get_y (self, row);
g_assert (row->parent.n_items > 0);
- y += skip * row->height / row->parent.n_items;
+ if (skip)
+ y += row->section_height + skip * row->height / row->parent.n_items;
if (offset)
*offset = y;
if (size)
- *size = row->height / row->parent.n_items;
+ *size = row->height / row->parent.n_items + (skip ? 0 : row->section_height);
return TRUE;
}
@@ -363,6 +366,7 @@ gtk_list_view_get_items_in_rect (GtkListBase *base,
guint first, last, n_items;
GtkBitset *result;
ListRow *row;
+ int remaining;
result = gtk_bitset_new_empty ();
@@ -375,9 +379,13 @@ gtk_list_view_get_items_in_rect (GtkListBase *base,
first = gtk_list_item_manager_get_item_position (self->item_manager, row);
else
first = rect->y < 0 ? 0 : n_items - 1;
- row = gtk_list_view_get_row_at_y (self, rect->y + rect->height, NULL);
+ row = gtk_list_view_get_row_at_y (self, rect->y + rect->height, &remaining);
if (row)
- last = gtk_list_item_manager_get_item_position (self->item_manager, row);
+ {
+ last = gtk_list_item_manager_get_item_position (self->item_manager, row);
+ if (remaining < row->section_height && last > 0)
+ last--;
+ }
else
last = rect->y < 0 ? 0 : n_items - 1;
@@ -428,7 +436,7 @@ gtk_list_view_get_position_from_allocation (GtkListBase *base,
else
one_item_height = row->height / row->parent.n_items;
- g_assert (remaining < row->height);
+ g_assert (remaining < row->height + row->section_height);
*pos += remaining / one_item_height;
if (area)
@@ -499,6 +507,14 @@ gtk_list_view_measure_across (GtkWidget *widget,
&child_min, &child_nat, NULL, NULL);
min = MAX (min, child_min);
nat = MAX (nat, child_nat);
+ if (row->parent.section_header)
+ {
+ gtk_widget_measure (row->parent.section_header,
+ orientation, for_size,
+ &child_min, &child_nat, NULL, NULL);
+ min = MAX (min, child_min);
+ nat = MAX (nat, child_nat);
+ }
}
*minimum = min;
@@ -535,6 +551,15 @@ gtk_list_view_measure_list (GtkWidget *widget,
&child_min, &child_nat, NULL, NULL);
g_array_append_val (min_heights, child_min);
g_array_append_val (nat_heights, child_nat);
+ if (row->parent.section_header)
+ {
+ int section_min, section_nat;
+ gtk_widget_measure (row->parent.section_header,
+ orientation, for_size,
+ §ion_min, §ion_nat, NULL, NULL);
+ child_min += section_min;
+ child_nat += section_nat;
+ }
min += child_min;
nat += child_nat;
}
@@ -617,23 +642,42 @@ gtk_list_view_size_allocate (GtkWidget *widget,
row != NULL;
row = gtk_rb_tree_node_get_next (row))
{
- if (row->parent.widget == NULL)
- continue;
-
- gtk_widget_measure (row->parent.widget, orientation,
- self->list_width,
- &min, &nat, NULL, NULL);
- if (scroll_policy == GTK_SCROLL_MINIMUM)
- row_height = min;
+ if (row->parent.widget)
+ {
+ gtk_widget_measure (row->parent.widget, orientation,
+ self->list_width,
+ &min, &nat, NULL, NULL);
+ if (scroll_policy == GTK_SCROLL_MINIMUM)
+ row_height = min;
+ else
+ row_height = nat;
+ g_array_append_val (heights, row_height);
+ if (row->height != row_height)
+ {
+ row->height = row_height;
+ gtk_rb_tree_node_mark_dirty (row);
+ }
+ total_height += row->height;
+ }
+ if (row->parent.section_header)
+ {
+ gtk_widget_measure (row->parent.section_header, orientation,
+ self->list_width,
+ &min, &nat, NULL, NULL);
+ if (scroll_policy == GTK_SCROLL_MINIMUM)
+ row_height = min;
+ else
+ row_height = nat;
+ }
else
- row_height = nat;
- if (row->height != row_height)
+ row_height = 0;
+
+ if (row->section_height != row_height)
{
- row->height = row_height;
+ row->section_height = row_height;
gtk_rb_tree_node_mark_dirty (row);
}
- total_height += row->height;
- g_array_append_val (heights, row_height);
+ total_height += row_height;
}
/* step 5: determine height of unknown items */
@@ -670,6 +714,16 @@ gtk_list_view_size_allocate (GtkWidget *widget,
row != NULL;
row = gtk_rb_tree_node_get_next (row))
{
+ if (row->parent.section_header)
+ {
+ gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
+ row->parent.section_header,
+ x,
+ y,
+ self->list_width,
+ row->section_height);
+ y += row->section_height;
+ }
if (row->parent.widget)
{
gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
@@ -679,7 +733,6 @@ gtk_list_view_size_allocate (GtkWidget *widget,
self->list_width,
row->height);
}
-
y += row->height;
}
@@ -722,6 +775,10 @@ gtk_list_view_get_property (GObject *object,
g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
break;
+ case PROP_SECTION_FACTORY:
+ g_value_set_object (value, gtk_list_item_manager_get_section_factory (self->item_manager));
+ break;
+
case PROP_SHOW_SEPARATORS:
g_value_set_boolean (value, self->show_separators);
break;
@@ -762,6 +819,10 @@ gtk_list_view_set_property (GObject *object,
gtk_list_view_set_model (self, g_value_get_object (value));
break;
+ case PROP_SECTION_FACTORY:
+ gtk_list_view_set_section_factory (self, g_value_get_object (value));
+ break;
+
case PROP_SHOW_SEPARATORS:
gtk_list_view_set_show_separators (self, g_value_get_boolean (value));
break;
@@ -877,6 +938,20 @@ gtk_list_view_class_init (GtkListViewClass *klass)
GTK_TYPE_SELECTION_MODEL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+ /**
+ * GtkListView:section-factory: (attributes org.gtk.Property.get=gtk_list_view_get_section_factory
org.gtk.Property.set=gtk_list_view_set_section_factory)
+ *
+ * Factory for populating section headers.
+ *
+ * Since: 4.8
+ */
+ properties[PROP_SECTION_FACTORY] =
+ g_param_spec_object ("section-factory",
+ P_("Section factory"),
+ P_("Factory for populating secion headers"),
+ GTK_TYPE_LIST_ITEM_FACTORY,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
/**
* GtkListView:show-separators: (attributes org.gtk.Property.get=gtk_list_view_get_show_separators
org.gtk.Property.set=gtk_list_view_set_show_separators)
*
@@ -1086,6 +1161,50 @@ gtk_list_view_set_factory (GtkListView *self,
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}
+/**
+ * gtk_list_view_get_section_factory: (attributes org.gtk.Method.get_property=section-factory)
+ * @self: a `GtkListView`
+ *
+ * Gets the factory that's currently used to populate section headers.
+ *
+ * Returns: (nullable) (transfer none): The factory in use
+ *
+ * Since: 4.8
+ */
+GtkListItemFactory *
+gtk_list_view_get_section_factory (GtkListView *self)
+{
+ g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
+
+ return gtk_list_item_manager_get_section_factory (self->item_manager);
+}
+
+/**
+ * gtk_list_view_set_section_factory: (attributes org.gtk.Method.set_property=section-factory)
+ * @self: a `GtkListView`
+ * @factory: (nullable) (transfer none): the factory to use
+ *
+ * Sets the `GtkListItemFactory` to use for populating section headers.
+ *
+ * If this factory is set to %NULL, the list will not use section.
+ *
+ * Since: 4.8
+ */
+void
+gtk_list_view_set_section_factory (GtkListView *self,
+ GtkListItemFactory *factory)
+{
+ g_return_if_fail (GTK_IS_LIST_VIEW (self));
+ g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory));
+
+ if (factory == gtk_list_item_manager_get_section_factory (self->item_manager))
+ return;
+
+ gtk_list_item_manager_set_section_factory (self->item_manager, factory);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECTION_FACTORY]);
+}
+
/**
* gtk_list_view_set_focus_position: (attributes org.gtk.Method.set_property=focus-position)
* @self: a `GtkListView`
diff --git a/gtk/gtklistview.h b/gtk/gtklistview.h
index 8b1bdf7663..0c601fa8a9 100644
--- a/gtk/gtklistview.h
+++ b/gtk/gtklistview.h
@@ -58,6 +58,13 @@ GDK_AVAILABLE_IN_ALL
GtkListItemFactory *
gtk_list_view_get_factory (GtkListView *self);
+GDK_AVAILABLE_IN_4_8
+void gtk_list_view_set_section_factory (GtkListView *self,
+ GtkListItemFactory *factory);
+GDK_AVAILABLE_IN_4_8
+GtkListItemFactory *
+ gtk_list_view_get_section_factory (GtkListView *self);
+
GDK_AVAILABLE_IN_4_8
void gtk_list_view_set_focus_position (GtkListView *self,
guint position);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]