[Nautilus-list] label ellipsizing
- From: Havoc Pennington <hp redhat com>
- To: nautilus-list eazel com
- Subject: [Nautilus-list] label ellipsizing
- Date: 30 Nov 2001 15:47:47 -0500
Hi,
Initial untested attempt to get ellipsizing functions implemented
again.
Owen says we should change the ellipsize utility functions interface
to be something like:
eel_gtk_widget_create_ellipsized_pango_layout (GtkWidget *widget, const char *string);
That's more compatible with the future planned API
pango_layout_set_ellipsize_mode() or whatever. But it requires some
more code rearrangement.
So how do I run the self test code? I have no idea if my new function
works. I'm betting 95% chance it doesn't. ;-)
Havoc
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/eel/ChangeLog,v
retrieving revision 1.202
diff -u -p -u -r1.202 ChangeLog
--- ChangeLog 2001/11/21 21:21:36 1.202
+++ ChangeLog 2001/11/30 20:49:35
@@ -1,3 +1,11 @@
+2001-11-30 Havoc Pennington <hp redhat com>
+
+ * eel/eel-gdk-font-extensions.c (eel_string_ellipsize): port to
+ Pango
+
+ * eel/eel-ellipsizing-label.c (recompute_ellipsized_text):
+ re-enable ellipsizing computation
+
Tue Nov 20 20:26:25 2001 Owen Taylor <otaylor redhat com>
* configure.in: Add [quoting] around AC_CHECK_HEADER
Index: eel/eel-ellipsizing-label.c
===================================================================
RCS file: /cvs/gnome/eel/eel/eel-ellipsizing-label.c,v
retrieving revision 1.8
diff -u -p -u -r1.8 eel-ellipsizing-label.c
--- eel/eel-ellipsizing-label.c 2001/11/04 01:55:55 1.8
+++ eel/eel-ellipsizing-label.c 2001/11/30 20:49:35
@@ -95,21 +95,19 @@ eel_ellipsizing_label_new (const char *s
static void
recompute_ellipsized_text (EelEllipsizingLabel *label, int width)
{
-#if GNOME2_CONVERSION_COMPLETE
char *ellipsized_text;
if (label->details->full_text == NULL) {
ellipsized_text = NULL;
} else {
ellipsized_text = eel_string_ellipsize (label->details->full_text,
- GTK_WIDGET (label)->style->font,
+ gtk_widget_get_pango_context (GTK_WIDGET (label)),
width,
EEL_ELLIPSIZE_MIDDLE);
}
gtk_label_set_text (GTK_LABEL (label), ellipsized_text);
g_free (ellipsized_text);
-#endif
}
void
@@ -140,6 +138,13 @@ real_size_request (GtkWidget *widget, Gt
static void
real_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
+ /* This queues a resize out of size allocate, which is really
+ * pretty lame. Or even totally
+ * broken. gtkwindow.c:gtk_window_move_resize() has a
+ * hackaround which keeps this semi-working, see comment in
+ * there about zvt's behavior (ZvtTerm is busted in the same
+ * way)
+ */
recompute_ellipsized_text (EEL_ELLIPSIZING_LABEL (widget), allocation->width);
EEL_CALL_PARENT (GTK_WIDGET_CLASS, size_allocate, (widget, allocation));
Index: eel/eel-gdk-font-extensions.c
===================================================================
RCS file: /cvs/gnome/eel/eel/eel-gdk-font-extensions.c,v
retrieving revision 1.15
diff -u -p -u -r1.15 eel-gdk-font-extensions.c
--- eel/eel-gdk-font-extensions.c 2001/10/23 06:33:53 1.15
+++ eel/eel-gdk-font-extensions.c 2001/11/30 20:49:35
@@ -35,6 +35,7 @@
#include <gdk/gdkprivate.h>
#include <gdk/gdkx.h>
+#include <gtk/gtkmain.h>
/* Magic constant copied from GTK+ */
#define MAX_FONTS 32767
@@ -637,153 +638,313 @@ eel_gdk_font_get_largest_fitting (GdkFon
return largest_fitting_font;
}
+/* Caution: this is an _expensive_ function */
+static int
+measure_string_width (const char *string,
+ PangoContext *context)
+{
+ PangoLayout *layout;
+ int width;
+
+ layout = pango_layout_new (context);
+ pango_layout_set_text (layout, string, -1);
+ pango_layout_get_pixel_size (layout, &width, NULL);
+ g_object_unref (G_OBJECT (layout));
+
+ return width;
+}
+
+/* this is also plenty slow */
+static void
+compute_character_widths (const char *string,
+ PangoContext *context,
+ int *char_len_return,
+ int **widths_return,
+ int **cuts_return)
+{
+ int *widths;
+ int *offsets;
+ int *cuts;
+ int char_len;
+ int byte_len;
+ const char *p;
+ int i;
+ PangoLayout *layout;
+ PangoLayoutIter *iter;
+ PangoLogAttr *attrs;
+
+#define BEGINS_UTF8_CHAR(x) (((x) & 0xc0) != 0x80)
+
+ char_len = g_utf8_strlen (string, -1);
+ byte_len = strlen (string);
+
+ widths = g_new (int, char_len);
+ offsets = g_new (int, byte_len);
+
+ /* Create a translation table from byte index to char offset */
+ p = string;
+ i = 0;
+ while (*p) {
+ int byte_index = p - string;
+
+ if (BEGINS_UTF8_CHAR (*p)) {
+ offsets[byte_index] = i;
+ ++i;
+ } else {
+ offsets[byte_index] = G_MAXINT; /* segv if we try to use this */
+ }
+
+ ++p;
+ }
+
+ /* Now fill in the widths array */
+ layout = pango_layout_new (context);
+ pango_layout_set_text (layout, string, -1);
+
+ iter = pango_layout_get_iter (layout);
+
+ do {
+ PangoRectangle extents;
+ int byte_index;
+
+ pango_layout_iter_get_char_extents (iter, &extents);
+
+ byte_index = pango_layout_iter_get_index (iter);
+
+ widths[offsets[byte_index]] = PANGO_PIXELS (extents.width);
+
+ } while (pango_layout_iter_next_char (iter));
+
+ pango_layout_iter_free (iter);
+ g_object_unref (G_OBJECT (layout));
+
+ g_free (offsets);
+
+ *widths_return = widths;
+
+ /* Now compute character offsets that are legitimate places to
+ * chop the string
+ */
+ attrs = g_new (PangoLogAttr, char_len + 1);
+
+ pango_get_log_attrs (string, byte_len, -1,
+ pango_context_get_language (context),
+ attrs,
+ char_len + 1);
+
+ cuts = g_new (int, char_len);
+ i = 0;
+ while (i < char_len) {
+ cuts[i] = attrs[i].is_cursor_position;
+
+ ++i;
+ }
+
+ g_free (attrs);
+
+ *cuts_return = cuts;
+
+ *char_len_return = char_len;
+}
+
static char *
-eel_string_ellipsize_start (const char *string, GdkFont *font, int width)
+eel_string_ellipsize_start (const char *string, PangoContext *context, int width)
{
- int truncate_offset;
int resulting_width;
+ int *cuts;
+ int *widths;
+ int char_len;
+ const char *p;
+ int truncate_offset;
+
+ /* Zero-length string can't get shorter - catch this here to
+ * avoid expensive calculations
+ */
+ if (*string == '\0')
+ return g_strdup ("");
- resulting_width = gdk_string_width (font, string);
+ /* I'm not sure if this short-circuit is a net win; it might be better
+ * to just dump this, and always do the compute_character_widths() etc.
+ * down below.
+ */
+ resulting_width = measure_string_width (string, context);
+
if (resulting_width <= width) {
/* String is already short enough. */
return g_strdup (string);
}
-
- /* Account for the width of the ellipsis. */
- width -= gdk_string_width (font, ELLIPSIS);
-
+
+ /* Remove width of an ellipsis */
+ width -= measure_string_width (ELLIPSIS, context);
if (width < 0) {
- /* No room even for a an ellipsis. */
+ /* No room even for an ellipsis. */
return g_strdup ("");
}
-
- g_assert (strlen (string) > 0);
- /* We rely on the fact that GdkFont does not use kerning and that
- * gdk_string_width ("foo") + gdk_string_width ("bar") ==
- * gdk_string_width ("foobar")
+ /* Our algorithm involves removing enough chars from the string to bring
+ * the width to the required small size. However, due to ligatures,
+ * combining characters, etc., it's not guaranteed that the algorithm
+ * always works 100%. It's sort of a heuristic thing. It should work
+ * nearly all the time... but I wouldn't put in
+ * g_assert (width of resulting string < width).
+ *
+ * Hmm, another thing that this breaks with is explicit line breaks
+ * in "string"
*/
- for (truncate_offset = 1; ; truncate_offset++) {
- if (string[truncate_offset] == '\0') {
- break;
- }
- resulting_width -= gdk_char_width (font, string[truncate_offset - 1]);
+ compute_character_widths (string, context, &char_len, &widths, &cuts);
- if (resulting_width <= width) {
+ for (truncate_offset = 1; truncate_offset < char_len; truncate_offset++) {
+
+ resulting_width -= widths[truncate_offset];
+
+ if (resulting_width <= width &&
+ cuts[truncate_offset]) {
break;
}
}
+
+ g_free (cuts);
+ g_free (widths);
- return g_strconcat (ELLIPSIS, string + truncate_offset, NULL);
+ p = g_utf8_offset_to_pointer (string, truncate_offset);
+
+ return g_strconcat (ELLIPSIS, p, NULL);
}
static char *
-eel_string_ellipsize_end (const char *string, GdkFont *font, int width)
+eel_string_ellipsize_end (const char *string, PangoContext *context, int width)
{
- int truncated_length;
- char *result;
int resulting_width;
+ int *cuts;
+ int *widths;
+ int char_len;
+ const char *p;
+ int truncate_offset;
+ char *result;
+
+ /* See explanatory comments in ellipsize_start */
+
+ if (*string == '\0')
+ return g_strdup ("");
- resulting_width = gdk_string_width (font, string);
+ resulting_width = measure_string_width (string, context);
+
if (resulting_width <= width) {
- /* String is already short enough. */
return g_strdup (string);
}
-
- /* Account for the width of the ellipsis. */
- width -= gdk_string_width (font, ELLIPSIS);
-
+ width -= measure_string_width (ELLIPSIS, context);
+
if (width < 0) {
- /* No room even for a an ellipsis. */
return g_strdup ("");
}
+
+ compute_character_widths (string, context, &char_len, &widths, &cuts);
- for (truncated_length = strlen (string) - 1; truncated_length > 0; truncated_length--) {
- resulting_width -= gdk_char_width (font, string[truncated_length]);
- if (resulting_width <= width) {
+ for (truncate_offset = char_len - 1; truncate_offset > 0; truncate_offset--) {
+ resulting_width -= widths[truncate_offset];
+ if (resulting_width <= width &&
+ cuts[truncate_offset]) {
break;
}
}
+
+ g_free (cuts);
+ g_free (widths);
+
+ p = g_utf8_offset_to_pointer (string, truncate_offset);
- result = g_malloc (truncated_length + strlen (ELLIPSIS) + 1);
- memcpy (result, string, truncated_length);
- strcpy (result + truncated_length, ELLIPSIS);
+ result = g_malloc ((p - string) + strlen (ELLIPSIS) + 1);
+ memcpy (result, string, (p - string));
+ strcpy (result + (p - string), ELLIPSIS);
return result;
}
static char *
-eel_string_ellipsize_middle (const char *string, GdkFont *font, int width)
+eel_string_ellipsize_middle (const char *string, PangoContext *context, int width)
{
- int original_length;
+ int resulting_width;
+ int *cuts;
+ int *widths;
+ int char_len;
+ int starting_fragment_byte_len;
+ int ending_fragment_byte_index;
int starting_fragment_length;
int ending_fragment_offset;
- int resulting_width;
char *result;
+
+ /* See explanatory comments in ellipsize_start */
+
+ if (*string == '\0')
+ return g_strdup ("");
- resulting_width = gdk_string_width (font, string);
+ resulting_width = measure_string_width (string, context);
+
if (resulting_width <= width) {
- /* String is already short enough. */
return g_strdup (string);
}
-
- /* Account for the width of the ellipsis. */
- width -= gdk_string_width (font, ELLIPSIS);
-
+
+ width -= measure_string_width (ELLIPSIS, context);
if (width < 0) {
- /* No room even for a an ellipsis. */
return g_strdup ("");
}
-
- /* Split the original string into two halves */
- original_length = strlen (string);
- g_assert (original_length > 0);
+ compute_character_widths (string, context, &char_len, &widths, &cuts);
- starting_fragment_length = original_length / 2;
+ starting_fragment_length = char_len / 2;
ending_fragment_offset = starting_fragment_length + 1;
/* Shave off a character at a time from the first and the second half
* until we can fit
*/
- resulting_width -= gdk_char_width (font, string[ending_fragment_offset - 1]);
+ resulting_width -= widths[ending_fragment_offset - 1];
/* depending on whether the original string length is odd or even, start by
* shaving off the characters from the starting or ending fragment
*/
- switch (original_length % 2) {
- while (TRUE) {
+ switch (char_len % 2) {
+ while (starting_fragment_length > 0 || ending_fragment_offset < char_len) {
case 0:
- if (resulting_width <= width) {
+ if (resulting_width <= width &&
+ cuts[ending_fragment_offset] &&
+ cuts[starting_fragment_length]) {
break;
}
- g_assert (starting_fragment_length > 0 || ending_fragment_offset < original_length);
+
if (starting_fragment_length > 0) {
+ resulting_width -= widths[starting_fragment_length];
starting_fragment_length--;
}
- resulting_width -= gdk_char_width (font, string[starting_fragment_length]);
case 1:
- if (resulting_width <= width) {
+ if (resulting_width <= width &&
+ cuts[ending_fragment_offset] &&
+ cuts[starting_fragment_length]) {
break;
}
- g_assert (starting_fragment_length > 0 || ending_fragment_offset < original_length);
- if (ending_fragment_offset < original_length) {
+
+ if (ending_fragment_offset < char_len) {
+ resulting_width -= widths[ending_fragment_offset];
ending_fragment_offset++;
}
- resulting_width -= gdk_char_width (font, string[ending_fragment_offset - 1]);
}
}
+
+ g_free (cuts);
+ g_free (widths);
/* patch the two fragments together with an ellipsis */
- result = g_malloc (starting_fragment_length + (original_length - ending_fragment_offset)
- + strlen (ELLIPSIS) + 1);
- memcpy (result, string, starting_fragment_length);
- strcpy (result + starting_fragment_length, ELLIPSIS);
- strcpy (result + starting_fragment_length + strlen (ELLIPSIS), string + ending_fragment_offset);
+ result = g_malloc (strlen (string) + strlen (ELLIPSIS) + 1); /* a bit wasteful, no biggie */
+
+ starting_fragment_byte_len = g_utf8_offset_to_pointer (string, starting_fragment_length) - string;
+ ending_fragment_byte_index = g_utf8_offset_to_pointer (string, ending_fragment_offset) - string;
+
+ memcpy (result, string, starting_fragment_byte_len);
+ strcpy (result + starting_fragment_byte_len, ELLIPSIS);
+ strcpy (result + starting_fragment_byte_len + strlen (ELLIPSIS), string + ending_fragment_byte_index);
return result;
}
@@ -793,25 +954,26 @@ eel_string_ellipsize_middle (const char
* eel_string_ellipsize:
*
* @string: A a string to be ellipsized.
- * @font: A a font used to measure the resulting string width.
+ * @context: A PangoContext used to measure the resulting string width.
* @width: Desired maximum width in points.
* @mode: The desired ellipsizing mode.
- * Returns: A truncated string at most @width points long.
+ * Returns: A truncated string hopefully at most @width points long.
*
* Truncates a string, removing characters from the start, middle or
- * end respectively and replacing them with "..."
+ * end respectively and replacing them with "...". Algorithm is a bit
+ * fuzzy, won't work 100%.
*
*/
char *
-eel_string_ellipsize (const char *string, GdkFont *font, int width, EelEllipsizeMode mode)
+eel_string_ellipsize (const char *string, PangoContext *context, int width, EelEllipsizeMode mode)
{
switch (mode) {
case EEL_ELLIPSIZE_START:
- return eel_string_ellipsize_start (string, font, width);
+ return eel_string_ellipsize_start (string, context, width);
case EEL_ELLIPSIZE_MIDDLE:
- return eel_string_ellipsize_middle (string, font, width);
+ return eel_string_ellipsize_middle (string, context, width);
case EEL_ELLIPSIZE_END:
- return eel_string_ellipsize_end (string, font, width);
+ return eel_string_ellipsize_end (string, context, width);
default:
g_assert_not_reached ();
return NULL;
@@ -1407,6 +1569,22 @@ compare_xlfd_by_size_in_pixels (gconstpo
#if ! defined (EEL_OMIT_SELF_CHECK)
+static PangoContext*
+eel_create_bogus_test_pango_context (void)
+{
+ PangoContext *context;
+
+ context = gdk_pango_context_get ();
+
+ /* FIXME do we have to set a font on the PangoContext or does Pango
+ * have some sane default?
+ */
+
+ pango_context_set_language (context, gtk_get_default_language ());
+
+ return context;
+}
+
/* Testing string truncation is tough because we do not know what font/
* font metrics to expect on a given system. To work around this we use
* a substring of the original, measure it's length using the given font,
@@ -1416,22 +1594,19 @@ compare_xlfd_by_size_in_pixels (gconstpo
static char *
eel_self_check_ellipsize (const char *string, const char *truncate_to_length_string, EelEllipsizeMode mode)
{
- GdkFont *font;
+ PangoContext *context;
int truncation_length;
char *result;
-
- /* any old font will do */
- font = eel_gdk_font_get_fixed ();
- g_assert (font);
+ context = eel_create_bogus_test_pango_context ();
/* measure the length we want to truncate to */
- truncation_length = gdk_string_width (font, truncate_to_length_string);
- truncation_length += gdk_string_width (font, ELLIPSIS);
+ truncation_length = measure_string_width (string, context);
+ truncation_length += measure_string_width (ELLIPSIS, context);
- result = eel_string_ellipsize (string, font, truncation_length, mode);
+ result = eel_string_ellipsize (string, context, truncation_length, mode);
- gdk_font_unref (font);
+ g_object_unref (G_OBJECT (context));
return result;
}
@@ -1457,11 +1632,10 @@ eel_self_check_ellipsize_end (const char
void
eel_self_check_gdk_font_extensions (void)
{
- GdkFont *font;
+ PangoContext *context;
/* used to test ellipsize routines */
- font = eel_gdk_font_get_fixed ();
- g_assert (font);
+ context = eel_create_bogus_test_pango_context ();
/* eel_string_ellipsize_start */
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "0012345678"), "012345678");
@@ -1471,10 +1645,10 @@ eel_self_check_gdk_font_extensions (void
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "678"), "...678");
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "78"), "...78");
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_start ("012345678", "8"), "...8");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_start ("", font, 100), "");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_start ("test", font, 0), "");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_start ("test", font, gdk_string_width (font, "...") - 1), "");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_start ("test", font, gdk_string_width (font, "...")), "...");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_start ("", context, 100), "");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_start ("test", context, 0), "");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_start ("test", context, measure_string_width ("...", context) - 1), "");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_start ("test", context, measure_string_width ("...", context)), "...");
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("012345678", "0123456789"), "012345678");
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("012345678", "012345678"), "012345678");
@@ -1490,10 +1664,10 @@ eel_self_check_gdk_font_extensions (void
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("0123456789", "019"), "01...9");
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("0123456789", "09"), "0...9");
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_middle ("0123456789", "0"), "0...");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_middle ("", font, 100), "");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_middle ("test", font, 0), "");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_middle ("test", font, gdk_string_width (font, "...") - 1), "");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_middle ("test", font, gdk_string_width (font, "...")), "...");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_middle ("", context, 100), "");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_middle ("test", context, 0), "");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_middle ("test", context, measure_string_width ("...", context) - 1), "");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_middle ("test", context, measure_string_width ("...", context)), "...");
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "0123456789"), "012345678");
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "012345678"), "012345678");
@@ -1502,12 +1676,12 @@ eel_self_check_gdk_font_extensions (void
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "012"), "012...");
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "01"), "01...");
EEL_CHECK_STRING_RESULT (eel_self_check_ellipsize_end ("012345678", "0"), "0...");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_end ("", font, 100), "");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_end ("test", font, 0), "");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_end ("test", font, gdk_string_width (font, "...") - 1), "");
- EEL_CHECK_STRING_RESULT (eel_string_ellipsize_end ("test", font, gdk_string_width (font, "...")), "...");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_end ("", context, 100), "");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_end ("test", context, 0), "");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_end ("test", context, measure_string_width ("...", context) - 1), "");
+ EEL_CHECK_STRING_RESULT (eel_string_ellipsize_end ("test", context, measure_string_width ("...", context)), "...");
- gdk_font_unref (font);
+ g_object_unref (G_OBJECT (context));
/* xlfd_string_get_nth */
EEL_CHECK_STRING_RESULT (xlfd_string_get_nth ("", 1), NULL);
Index: eel/eel-gdk-font-extensions.h
===================================================================
RCS file: /cvs/gnome/eel/eel/eel-gdk-font-extensions.h,v
retrieving revision 1.7
diff -u -p -u -r1.7 eel-gdk-font-extensions.h
--- eel/eel-gdk-font-extensions.h 2001/09/08 02:25:27 1.7
+++ eel/eel-gdk-font-extensions.h 2001/11/30 20:49:35
@@ -52,9 +52,10 @@ GdkFont *eel_gdk_font_get_larger
GdkFont *eel_gdk_font_get_smaller (GdkFont *font,
int num_sizes);
GdkFont *eel_gdk_font_get_fixed (void);
-char * eel_string_ellipsize (const char *string,
- GdkFont *font,
- int width,
+/* caution: this function is expensive. */
+char * eel_string_ellipsize (const char *string,
+ PangoContext *context,
+ int width,
EelEllipsizeMode mode);
char * eel_gdk_font_xlfd_string_new (const char *foundry,
const char *family,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]