[evolution/wip/webkit-composer: 584/966] Fix ESpellEntry crash
- From: Tomas Popela <tpopela src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution/wip/webkit-composer: 584/966] Fix ESpellEntry crash
- Date: Wed, 23 Apr 2014 10:42:31 +0000 (UTC)
commit 30d72e46fba224e121a39e05e85a5c5a103ad818
Author: Dan Vrátil <dvratil redhat com>
Date: Wed Aug 29 15:43:02 2012 +0200
Fix ESpellEntry crash
composer/e-composer-private.c | 54 ++++---
e-util/e-editor-widget.c | 23 ++--
e-util/e-editor-window.c | 4 +-
e-util/e-spell-dictionary.c | 3 +
e-util/e-spell-entry.c | 58 ++++----
modules/mail/e-mail-shell-backend.c | 2 +-
widgets/editor/selection-style.c | 288 +++++++++++++++++++++++++++++++++++
7 files changed, 367 insertions(+), 65 deletions(-)
---
diff --git a/composer/e-composer-private.c b/composer/e-composer-private.c
index 44ee0e7..6bc782f 100644
--- a/composer/e-composer-private.c
+++ b/composer/e-composer-private.c
@@ -130,6 +130,26 @@ composer_update_gallery_visibility (EMsgComposer *composer)
}
}
+static void
+composer_spell_languages_changed (EMsgComposer *composer,
+ GParamSpec *pspec,
+ EEditorWidget *editor_widget)
+{
+ GList *languages;
+ EComposerHeader *header;
+ EComposerHeaderTable *table;
+
+ table = e_msg_composer_get_header_table (composer);
+ header = e_composer_header_table_get_header (
+ table, E_COMPOSER_HEADER_SUBJECT);
+
+ languages = e_editor_widget_get_spell_languages (editor_widget);
+ header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_SUBJECT);
+ e_composer_spell_header_set_languages (E_COMPOSER_SPELL_HEADER (header), languages);
+
+ g_list_free (languages);
+}
+
void
e_composer_private_constructed (EMsgComposer *composer)
{
@@ -239,25 +259,9 @@ e_composer_private_constructed (EMsgComposer *composer)
priv->header_table = g_object_ref (widget);
gtk_widget_show (widget);
- g_object_bind_property (
- editor_widget, "editable",
- widget, "sensitive",
- G_BINDING_SYNC_CREATE);
-
- header = e_composer_header_table_get_header (
- E_COMPOSER_HEADER_TABLE (widget),
- E_COMPOSER_HEADER_SUBJECT);
- g_object_bind_property (
- editor_widget, "spell-checker",
- header->input_widget, "spell-checker",
- G_BINDING_SYNC_CREATE);
-
- /* Construct the editing toolbars. We'll have to reparent
- * the embedded EEditorWidget a little further down. */
-
- widget = GTK_WIDGET (editor);
- gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
- gtk_widget_show (widget);
+ g_signal_connect_swapped (
+ editor_widget, "notify::spell-languages",
+ G_CALLBACK (composer_spell_languages_changed), composer);
/* Construct the attachment paned. */
@@ -1079,8 +1083,16 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box,
g_string_append_printf (
html_buffer,
- "<SPAN class=\"-x-evolution-signature\" id=\"1\" name=\"%s\">",
- (active_id != NULL) ? active_id : "");
+ "<!--+GtkHTML:<DATA class=\"ClueFlow\" "
+ " key=\"signature\" value=\"1\">-->"
+ "<!--+GtkHTML:<DATA class=\"ClueFlow\" "
+ " key=\"signature_name\" value=\"uid:%s\"-->",
+ (encoded_uid != NULL) ? encoded_uid : "");
+
+ g_string_append (
+ html_buffer,
+ "<TABLE WIDTH=\"100%%\" CELLSPACING=\"0\""
+ " CELLPADDING=\"0\"><TR><TD>");
if (!is_html)
g_string_append (html_buffer, "<PRE>\n");
diff --git a/e-util/e-editor-widget.c b/e-util/e-editor-widget.c
index 23f8143..83f43a3 100644
--- a/e-util/e-editor-widget.c
+++ b/e-util/e-editor-widget.c
@@ -806,6 +806,15 @@ e_editor_widget_class_init (EEditorWidgetClass *klass)
FALSE,
G_PARAM_READABLE));
+ g_object_class_install_property (
+ object_class,
+ PROP_SPELL_LANGUAGES,
+ g_param_spec_pointer (
+ "spell-languages",
+ "Active spell checking languages",
+ NULL,
+ G_PARAM_READWRITE));
+
signals[POPUP_EVENT] = g_signal_new (
"popup-event",
G_TYPE_FROM_CLASS (klass),
@@ -1064,20 +1073,12 @@ void
e_editor_widget_set_spell_languages (EEditorWidget *widget,
GList *spell_languages)
{
- GList *iter;
-
g_return_if_fail (E_IS_EDITOR_WIDGET (widget));
g_return_if_fail (spell_languages);
- g_list_free_full (widget->priv->spelling_langs, g_free);
-
- widget->priv->spelling_langs = NULL;
- for (iter = spell_languages; iter; iter = g_list_next (iter)) {
- widget->priv->spelling_langs =
- g_list_append (
- widget->priv->spelling_langs,
- g_strdup (iter->data));
- }
+ g_list_free_full (widget->priv->spelling_langs, g_object_unref);
+ widget->priv->spelling_langs = g_list_copy (spell_languages);
+ g_list_foreach (widget->priv->spelling_langs, (GFunc) g_object_ref, NULL);
g_object_notify (G_OBJECT (widget), "spell-languages");
}
diff --git a/e-util/e-editor-window.c b/e-util/e-editor-window.c
index fd55563..30a4286 100644
--- a/e-util/e-editor-window.c
+++ b/e-util/e-editor-window.c
@@ -147,8 +147,10 @@ e_editor_window_pack_above (EEditorWindow *window,
g_return_if_fail (E_IS_EDITOR_WINDOW (window));
g_return_if_fail (GTK_IS_WIDGET (child));
- gtk_grid_insert_row (window->priv->main_layout, window->priv->editor_row);
+ gtk_grid_insert_row (
+ window->priv->main_layout, window->priv->editor_row);
window->priv->editor_row++;
+
gtk_grid_attach_next_to (
window->priv->main_layout, child,
GTK_WIDGET (window->priv->editor),
diff --git a/e-util/e-spell-dictionary.c b/e-util/e-spell-dictionary.c
index e5ed02a..002a5ac 100644
--- a/e-util/e-spell-dictionary.c
+++ b/e-util/e-spell-dictionary.c
@@ -647,5 +647,8 @@ gint
e_spell_dictionary_compare (ESpellDictionary *dict1,
ESpellDictionary *dict2)
{
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dict1), 0);
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dict2), 0);
+
return strcmp (dict1->priv->collate_key, dict2->priv->collate_key);
}
diff --git a/e-util/e-spell-entry.c b/e-util/e-spell-entry.c
index f29ad1e..65e424e 100644
--- a/e-util/e-spell-entry.c
+++ b/e-util/e-spell-entry.c
@@ -37,7 +37,7 @@ struct _ESpellEntryPrivate {
gint entry_scroll_offset;
gboolean custom_checkers;
gboolean checking_enabled;
- GSList *dictionaries;
+ GList *dictionaries;
gchar **words;
gint *word_starts;
gint *word_ends;
@@ -76,12 +76,12 @@ word_misspelled (ESpellEntry *entry,
g_strlcpy (word, text + start, end - start + 1);
if (g_unichar_isalpha (*word)) {
- ESpellChecker *spell_checker;
+ GList *li;
gssize wlen = strlen (word);
- for (li = entry->priv->dictionaries; li; li = g_slist_next (li)) {
- EnchantDict *dict = li->data;
- if (enchant_dict_check (dict, word, wlen)) {
+ for (li = entry->priv->dictionaries; li; li = g_list_next (li)) {
+ ESpellDictionary *dict = li->data;
+ if (e_spell_dictionary_check (dict, word, wlen)) {
result = FALSE;
break;
}
@@ -269,7 +269,7 @@ add_to_dictionary (GtkWidget *menuitem,
{
gchar *word;
gint start, end;
- EnchantDict *dict;
+ ESpellDictionary *dict;
get_word_extents_from_position (
entry, &start, &end, entry->priv->mark_character);
@@ -277,7 +277,7 @@ add_to_dictionary (GtkWidget *menuitem,
dict = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker");
if (dict != NULL)
- enchant_dict_add_to_personal (dict, word, -1);
+ e_spell_dictionary_learn_word (dict, word, -1);
g_free (word);
@@ -303,14 +303,15 @@ ignore_all (GtkWidget *menuitem,
ESpellChecker *spell_checker;
gchar *word;
gint start, end;
+ GList *li;
get_word_extents_from_position (
entry, &start, &end, entry->priv->mark_character);
word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
- for (li = entry->priv->dictionaries; li; li = g_slist_next (li)) {
- EnchantDict *dict = li->data;
- enchant_dict_add_to_session (dict, word, -1);
+ for (li = entry->priv->dictionaries; li; li = g_list_next (li)) {
+ ESpellDictionary *dict = li->data;
+ e_spell_dictionary_ignore_word (dict, word, -1);
}
g_free (word);
@@ -338,7 +339,7 @@ replace_word (GtkWidget *menuitem,
const gchar *newword;
gint start, end;
gint cursor;
- EnchantDict *dict;
+ ESpellDictionary *dict;
get_word_extents_from_position (
entry, &start, &end, entry->priv->mark_character);
@@ -362,7 +363,7 @@ replace_word (GtkWidget *menuitem,
dict = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker");
if (dict != NULL)
- enchant_dict_store_replacement (
+ e_spell_dictionary_store_correction (
dict, oldword, -1, newword, -1);
g_free (oldword);
@@ -448,10 +449,11 @@ build_spelling_menu (ESpellEntry *entry,
dict = entry->priv->dictionaries->data;
build_suggestion_menu (entry, topmenu, dict, word);
} else {
+ GList *li;
GtkWidget *menu;
GList *list, *link;
- for (li = entry->priv->dictionaries; li; li = g_slist_next (li)) {
+ for (li = entry->priv->dictionaries; li; li = g_list_next (li)) {
dict = li->data;
lang_name = e_spell_dictionary_get_name (dict);
@@ -491,13 +493,14 @@ build_spelling_menu (ESpellEntry *entry,
mi, "activate",
G_CALLBACK (add_to_dictionary), entry);
} else {
+ GList *li;
GtkWidget *menu, *submi;
GList *list, *link;
menu = gtk_menu_new ();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
- for (li = entry->priv->dictionaries; li; li = g_slist_next (li)) {
+ for (li = entry->priv->dictionaries; li; li = g_list_next (li)) {
dict = li->data;
lang_name = e_spell_dictionary_get_name (dict);
@@ -791,7 +794,7 @@ spell_entry_dispose (GObject *object)
g_clear_object (&priv->settings);
g_clear_object (&priv->spell_checker);
- g_slist_free_full (
+ g_list_free_full (
priv->dictionaries, (GDestroyNotify) g_object_unref);
priv->dictionaries = NULL;
@@ -944,30 +947,23 @@ e_spell_entry_new (void)
void
e_spell_entry_set_languages (ESpellEntry *spell_entry,
- GList *languages)
+ GList *dictionaries)
{
- GList *iter;
-
- g_return_if_fail (spell_entry != NULL);
+ g_return_if_fail (E_IS_SPELL_ENTRY (spell_entry));
spell_entry->priv->custom_checkers = TRUE;
- if (spell_entry->priv->dictionaries)
- g_slist_free_full (spell_entry->priv->dictionaries, g_object_unref);
- spell_entry->priv->dictionaries = NULL;
-
- for (iter = languages; iter; iter = g_list_next (iter)) {
- ESpellDictionary *dict = iter->data;
-
- if (dict)
- spell_entry->priv->dictionaries =
- g_slist_prepend (spell_entry->priv->dictionaries, dict);
+ if (spell_entry->priv->dictionaries) {
+ g_list_free_full (spell_entry->priv->dictionaries, g_object_unref);
}
+ spell_entry->priv->dictionaries = NULL;
- spell_entry->priv->dictionaries = g_slist_reverse (spell_entry->priv->dictionaries);
+ spell_entry->priv->dictionaries = g_list_copy (dictionaries);
+ g_list_foreach (spell_entry->priv->dictionaries, (GFunc) g_object_ref, NULL);
- if (gtk_widget_get_realized (GTK_WIDGET (spell_entry)))
+ if (gtk_widget_get_realized (GTK_WIDGET (spell_entry))) {
spell_entry_recheck_all (spell_entry);
+ }
}
gboolean
diff --git a/modules/mail/e-mail-shell-backend.c b/modules/mail/e-mail-shell-backend.c
index e69c55f..cb7ed89 100644
--- a/modules/mail/e-mail-shell-backend.c
+++ b/modules/mail/e-mail-shell-backend.c
@@ -503,7 +503,7 @@ mail_shell_backend_window_added_cb (GtkApplication *application,
spell_languages = e_load_spell_languages (spell_checker);
e_editor_widget_set_spell_languages (editor_widget, spell_languages);
- g_list_free (spell_languages);
+ g_list_free_full (spell_languages, g_object_unref);
settings = g_settings_new ("org.gnome.evolution.mail");
diff --git a/widgets/editor/selection-style.c b/widgets/editor/selection-style.c
new file mode 100644
index 0000000..d75f1f0
--- /dev/null
+++ b/widgets/editor/selection-style.c
@@ -0,0 +1,288 @@
+
+
+/*
+ * DO NOT replace type casting of WebKit types by GLib macros unless
+ * you know what you are doing (I do).
+ *
+ * Probably due to bugs in WebKitGtk+ DOM bindings these macros will
+ * produce runtime warnings, but the objects and class hierarchy ARE VALID.
+ *
+ * This mostly affects only WebKitDOMText, which is subclass
+ * of WebKitDOMNode, but the text nodes are rarely created as instances of
+ * WebKitDOMText. To make sure that you really can cast WebKitDOMNode to
+ * WebKitDOMText, check whether webkit_dom_node_get_node_type() == 3
+ * (3 is "text" node type). WebKitDOMNode is just a thin wrapper around
+ * WebKit's internal WebCore objects. Using get_node_type() is evaluated
+ * against properties of these internal object.
+ */
+
+
+
+static void
+normalize (WebKitDOMNode *node)
+{
+ WebKitDOMNodeList *children;
+ gulong ii;
+
+ /* Standard normalization */
+ webkit_dom_node_normalize (node);
+
+ children = webkit_dom_node_get_child_nodes (node);
+
+ ii = 0;
+ while (ii < webkit_dom_node_list_get_length (children)) {
+ WebKitDOMNode *child, *sibling;
+ gchar *tag_name, *sibling_tag_name;
+
+ child = webkit_dom_node_list_item (children, ii);
+
+ /* We are interested only in nodes representing HTML
+ * elements */
+ if (webkit_dom_node_get_node_type (child) != 1) {
+ ii++;
+ continue;
+ }
+
+ sibling = webkit_dom_node_get_next_sibling (child);
+
+ /* If sibling node is not an element, then skip the current
+ * element and the sibling node as well */
+ if (webkit_dom_node_get_node_type (sibling) != 1) {
+ ii += 2;
+ continue;
+ }
+
+ /* Recursively normalize the child element */
+ normalize (child);
+
+ tag_name = webkit_dom_element_get_tag_name (
+ WEBKIT_DOM_ELEMENT (child));
+ sibling_tag_name = webkit_dom_element_get_tag_name (
+ WEBKIT_DOM_ELEMENT (sibling));
+
+ if (g_strcmp0 (tag_name, sibling_tag_name) == 0) {
+ gchar *str1, *str2, *inner_html;
+
+ str1 = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (child));
+ str2 = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (sibling));
+ inner_html = g_strconcat (str1, str2, NULL);
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (child), inner_html, NULL);
+
+ g_free (str1);
+ g_free (str2);
+ g_free (inner_html);
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (sibling),
+ sibling, NULL);
+ }
+
+ ii++;
+ }
+}
+
+static void
+remove_format (EEditorSelection *selection,
+ const gchar *format_tag)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMRange *range;
+ WebKitDOMNode *start_node, *end_node;
+ WebKitDOMElement *common_ancestor;
+
+ document = webkit_web_view_get_dom_document (selection->priv->webview);
+ range = editor_selection_get_current_range (selection);
+
+ start_node = webkit_dom_range_get_start_container (range, NULL);
+ end_node = webkit_dom_range_get_end_container (range, NULL);
+
+ common_ancestor = webkit_dom_node_get_parent_element (
+ webkit_dom_node_get_parent_node (
+ webkit_dom_range_get_common_ancestor_container (
+ range, NULL)));
+
+ /* Cool! The selection is all within one node */
+ if (start_node == end_node) {
+ WebKitDOMElement *element;
+ WebKitDOMNode *node = start_node;
+ WebKitDOMNodeList *children;
+ gchar *wrapper_tag_name;
+
+ if (webkit_dom_node_get_node_type (start_node) != 3) {
+ /* XXX Is it possible for selection to start somewhere
+ * else then in a text node? If yes, what should we
+ * do about it? */
+ return;
+ }
+
+ /* Split <b>|blabla SELECTED TEXT bla|</b> to
+ * <b>|blabla |SELECTED TEXT| bla|</b> (| indicates node) */
+ node = (WebKitDOMNode *) webkit_dom_text_split_text (
+ (WebKitDOMText *) (node),
+ webkit_dom_range_get_start_offset (range, NULL), NULL);
+ webkit_dom_text_split_text (
+ (WebKitDOMText *) node,
+ webkit_dom_range_get_end_offset (range, NULL),
+ NULL);
+
+ element = webkit_dom_node_get_parent_element (node);
+ children = webkit_dom_node_get_child_nodes (WEBKIT_DOM_NODE (element));
+ wrapper_tag_name = webkit_dom_element_get_tag_name (element);
+
+ while (webkit_dom_node_list_get_length (children) > 0) {
+ WebKitDOMNode *child;
+
+ child = webkit_dom_node_list_item (children, 0);
+
+ if (child != node) {
+ WebKitDOMElement *wrapper;
+ wrapper = webkit_dom_document_create_element (
+ document, wrapper_tag_name, NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (wrapper), child, NULL);
+
+ child = WEBKIT_DOM_NODE (wrapper);
+ }
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (element)),
+ child, WEBKIT_DOM_NODE (element), NULL);
+ }
+
+ /* Remove the now empty container */
+ /*
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (element)),
+ WEBKIT_DOM_NODE (element), NULL);
+ */
+
+ g_free (wrapper_tag_name);
+ }
+
+ normalize (WEBKIT_DOM_NODE (common_ancestor));
+}
+
+static void
+apply_format (EEditorSelection *editor_selection,
+ const gchar *format_tag)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMRange *range;
+ WebKitDOMNode *selection, *node;
+ WebKitDOMNode *new_parent;
+ gint format_tag_len = strlen (format_tag);
+ gboolean prev_sibling_match, next_sibling_match;
+
+ prev_sibling_match = FALSE;
+ next_sibling_match = FALSE;
+
+ document = webkit_web_view_get_dom_document (editor_selection->priv->webview);
+ range = editor_selection_get_current_range (editor_selection);
+
+ if (webkit_dom_range_get_start_offset (range, NULL) != 0) {
+ node = webkit_dom_range_get_start_container (range, NULL);
+ selection = (WebKitDOMNode*) webkit_dom_text_split_text (
+ (WebKitDOMText *) node,
+ webkit_dom_range_get_start_offset (range, NULL), NULL);
+ } else {
+ selection = webkit_dom_range_get_start_container (range, NULL);
+ }
+
+ webkit_dom_text_split_text ((WebKitDOMText *) selection,
+ webkit_dom_range_get_end_offset (range, NULL), NULL);
+
+ /* The split above might have produced an empty text node
+ * (for example splitting "TEXT" on offset 4 will produce
+ * "TEXT" and "" nodes), so remove it */
+ node = webkit_dom_node_get_next_sibling (selection);
+ if (webkit_dom_node_get_node_type (node) == 3) {
+ gchar *content;
+
+ content = webkit_dom_node_get_text_content (node);
+ if (!content || (strlen (content) == 0)) {
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (node),
+ node, NULL);
+ }
+
+ g_free (content);
+ }
+
+ /* Check whether previous sibling is an element and whether it is <format_tag> */
+ node = webkit_dom_node_get_previous_sibling (selection);
+ if (node && (webkit_dom_node_get_node_type (node) == 1)) {
+ gchar *tag_name;
+
+ tag_name = webkit_dom_element_get_tag_name (
+ (WebKitDOMElement *) node);
+ prev_sibling_match = ((format_tag_len == strlen (tag_name)) &&
+ (g_ascii_strncasecmp (
+ format_tag, tag_name,
+ format_tag_len) == 0));
+ g_free (tag_name);
+ }
+
+ /* Check whether next sibling is an element and whether it is <format_tag> */
+ node = webkit_dom_node_get_next_sibling (selection);
+ if (node && (webkit_dom_node_get_node_type (node) == 1)) {
+ gchar *tag_name;
+
+ tag_name = webkit_dom_element_get_tag_name (
+ (WebKitDOMElement *) node);
+ next_sibling_match = ((format_tag_len == strlen (tag_name)) &&
+ (g_ascii_strncasecmp (
+ format_tag, tag_name,
+ format_tag_len) == 0));
+ g_free (tag_name);
+ }
+
+ /* Merge selection and next sibling to the orevious sibling */
+ if (prev_sibling_match && next_sibling_match) {
+ WebKitDOMNode *next_sibling, *child;
+
+ new_parent = webkit_dom_node_get_previous_sibling (selection);
+ next_sibling = webkit_dom_node_get_next_sibling (selection);
+
+ /* Append selection to the new parent */
+ webkit_dom_node_append_child (new_parent, selection, NULL);
+
+ /* Append all children of next sibling to the new parent */
+ while ((child = webkit_dom_node_get_first_child (next_sibling)) != NULL) {
+ webkit_dom_node_append_child (new_parent, child, NULL);
+ }
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (next_sibling),
+ next_sibling, NULL);
+
+ /* Merge selection to the previous sibling */
+ } else if (prev_sibling_match && !next_sibling_match) {
+ new_parent = webkit_dom_node_get_previous_sibling (selection);
+ webkit_dom_node_append_child (new_parent, selection, NULL);
+
+ /* Merge selection to the next sibling */
+ } else if (!prev_sibling_match && next_sibling_match) {
+ new_parent = webkit_dom_node_get_next_sibling (selection);
+ webkit_dom_node_insert_before (
+ new_parent, selection,
+ webkit_dom_node_get_first_child (new_parent), NULL);
+
+ /* Just wrap the selection to <tag_name> */
+ } else {
+ new_parent = (WebKitDOMNode *)
+ webkit_dom_document_create_element (
+ document, format_tag, NULL);
+ webkit_dom_range_surround_contents (range, new_parent, NULL);
+ }
+
+ webkit_dom_node_normalize (
+ (WebKitDOMNode *) webkit_dom_node_get_parent_element (new_parent));
+}
+
+
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]