[gedit] [snippets] Make better use of new plugin architecture



commit de138c65f93dc35f0b328098e45fd3f2ff058d04
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Fri Apr 22 20:49:03 2011 +0200

    [snippets] Make better use of new plugin architecture

 plugins/snippets/snippets/Makefile.am          |    3 +-
 plugins/snippets/snippets/__init__.py          |    1 +
 plugins/snippets/snippets/document.py          |  176 +++++++++---------------
 plugins/snippets/snippets/manager.py           |    5 -
 plugins/snippets/snippets/shareddata.py        |   38 +++++-
 plugins/snippets/snippets/signals.py           |   92 ++++++++++++
 plugins/snippets/snippets/windowactivatable.py |  109 +++++++--------
 7 files changed, 245 insertions(+), 179 deletions(-)
---
diff --git a/plugins/snippets/snippets/Makefile.am b/plugins/snippets/snippets/Makefile.am
index 9466996..f8f8dd5 100644
--- a/plugins/snippets/snippets/Makefile.am
+++ b/plugins/snippets/snippets/Makefile.am
@@ -18,7 +18,8 @@ plugin_PYTHON = \
 	importer.py \
 	exporter.py \
 	languagemanager.py \
-	completion.py
+	completion.py \
+	signals.py
 
 uidir = $(GEDIT_PLUGINS_DATA_DIR)/snippets/ui
 ui_DATA = snippets.ui
diff --git a/plugins/snippets/snippets/__init__.py b/plugins/snippets/snippets/__init__.py
index 2e27417..0d4d92e 100644
--- a/plugins/snippets/snippets/__init__.py
+++ b/plugins/snippets/snippets/__init__.py
@@ -17,5 +17,6 @@
 
 from appactivatable import AppActivatable
 from windowactivatable import WindowActivatable
+from document import Document
 
 # ex:ts=8:et:
diff --git a/plugins/snippets/snippets/document.py b/plugins/snippets/snippets/document.py
index 41bd43e..4c6c284 100644
--- a/plugins/snippets/snippets/document.py
+++ b/plugins/snippets/snippets/document.py
@@ -25,25 +25,27 @@ from library import Library
 from snippet import Snippet
 from placeholder import *
 import completion
+from signals import Signals
+from shareddata import SharedData
 
 class DynamicSnippet(dict):
         def __init__(self, text):
                 self['text'] = text
                 self.valid = True
 
-class Document:
-        TAB_KEY_VAL = (Gdk.KEY_Tab, \
-                        Gdk.KEY_ISO_Left_Tab)
+class Document(GObject.Object, Gedit.ViewActivatable, Signals):
+        TAB_KEY_VAL = (Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab)
         SPACE_KEY_VAL = (Gdk.KEY_space,)
 
-        def __init__(self, instance, view):
-                self.view = None
-                self.instance = instance
+        view = GObject.property(type=Gedit.View)
+
+        def __init__(self):
+                GObject.Object.__init__(self)
+                Signals.__init__(self)
 
                 self.placeholders = []
                 self.active_snippets = []
                 self.active_placeholder = None
-                self.signal_ids = {}
 
                 self.ordered_placeholders = []
                 self.update_placeholders = []
@@ -54,107 +56,58 @@ class Document:
                 self.provider = completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated)
                 self.defaults_provider = completion.Defaults(self.on_default_activated)
 
+        def do_activate(self):
                 # Always have a reference to the global snippets
                 Library().ref(None)
-                self.set_view(view)
-
-        # Stop controlling the view. Remove all active snippets, remove references
-        # to the view and the plugin instance, disconnect all signal handlers
-        def stop(self):
-                if self.timeout_update_id != 0:
-                        GObject.source_remove(self.timeout_update_id)
-                        self.timeout_update_id = 0
-                        del self.update_placeholders[:]
-                        del self.jump_placeholders[:]
-
-                # Always release the reference to the global snippets
-                Library().unref(None)
-                self.set_view(None)
-                self.instance = None
-                self.active_placeholder = None
 
-        def disconnect_signal(self, obj, signal):
-                if (obj, signal) in self.signal_ids:
-                        obj.disconnect(self.signal_ids[(obj, signal)])
-                        del self.signal_ids[(obj, signal)]
-
-        def connect_signal(self, obj, signal, cb):
-                self.disconnect_signal(obj, signal)
-                self.signal_ids[(obj, signal)] = obj.connect(signal, cb)
-
-        def connect_signal_after(self, obj, signal, cb):
-                self.disconnect_signal(obj, signal)
-                self.signal_ids[(obj, signal)] = obj.connect_after(signal, cb)
-
-        # Set the view to be controlled. Installs signal handlers and sets current
-        # language. If there is already a view set this function will first remove
-        # all currently active snippets and disconnect all current signals. So
-        # self.set_view(None) will effectively remove all the control from the
-        # current view
-        def _set_view(self, view):
-                if self.view:
-                        buf = self.view.get_buffer()
-
-                        # Remove signals
-                        signals = {self.view: ('key-press-event', 'destroy',
-                                               'notify::editable', 'drag-data-received', 'expose-event'),
-                                   buf:       ('notify::language', 'changed', 'cursor-moved', 'insert-text'),
-                                   self.view.get_completion(): ('hide',)}
-
-                        for obj, sig in signals.items():
-                                if obj:
-                                        for s in sig:
-                                                self.disconnect_signal(obj, s)
-
-                        # Remove all active snippets
-                        for snippet in list(self.active_snippets):
-                                self.deactivate_snippet(snippet, True)
+                buf = self.view.get_buffer()
 
-                        completion = self.view.get_completion()
+                self.connect_signal(self.view, 'key-press-event', self.on_view_key_press)
+                self.connect_signal(buf, 'notify::language', self.on_notify_language)
+                self.connect_signal(self.view, 'drag-data-received', self.on_drag_data_received)
 
-                        if completion:
-                                completion.remove_provider(self.provider)
-                                completion.remove_provider(self.defaults_provider)
+                self.connect_signal_after(self.view, 'draw', self.on_draw)
 
-                self.view = view
+                self.update_language()
 
-                if view != None:
-                        buf = view.get_buffer()
+                completion = self.view.get_completion()
 
-                        self.connect_signal(view, 'destroy', self.on_view_destroy)
+                completion.add_provider(self.provider)
+                completion.add_provider(self.defaults_provider)
 
-                        if view.get_editable():
-                                self.connect_signal(view, 'key-press-event', self.on_view_key_press)
+                self.connect_signal(completion, 'hide', self.on_completion_hide)
 
-                        self.connect_signal(buf, 'notify::language', self.on_notify_language)
-                        self.connect_signal(view, 'notify::editable', self.on_notify_editable)
-                        self.connect_signal(view, 'drag-data-received', self.on_drag_data_received)
-                        self.connect_signal_after(view, 'draw', self.on_draw)
+                SharedData().register_controller(self.view, self)
 
-                        self.update_language()
+        def do_deactivate(self):
+                if self.timeout_update_id != 0:
+                        GObject.source_remove(self.timeout_update_id)
+                        self.timeout_update_id = 0
 
-                        completion = view.get_completion()
+                        del self.update_placeholders[:]
+                        del self.jump_placeholders[:]
 
-                        completion.add_provider(self.provider)
-                        completion.add_provider(self.defaults_provider)
+                # Always release the reference to the global snippets
+                Library().unref(None)
+                self.active_placeholder = None
 
-                        self.connect_signal(completion, 'hide', self.on_completion_hide)
-                elif self.language_id != 0:
-                        langid = self.language_id
+                self.disconnect_signals(self.view)
+                self.disconnect_signals(self.view.get_buffer())
 
-                        self.language_id = None;
-                        self.provider.language_id = self.language_id
+                # Remove all active snippets
+                for snippet in list(self.active_snippets):
+                        self.deactivate_snippet(snippet, True)
 
-                        if self.instance:
-                                self.instance.language_changed(self)
+                completion = self.view.get_completion()
 
-                        Library().unref(langid)
+                if completion:
+                        completion.remove_provider(self.provider)
+                        completion.remove_provider(self.defaults_provider)
 
-        def set_view(self, view):
-                if view == self.view:
-                        return
+                if self.language_id != 0:
+                        Library().unref(self.language_id)
 
-                self._set_view(view)
+                SharedData().unregister_controller(self.view, self)
 
         # Call this whenever the language in the view changes. This makes sure that
         # the correct language is used when finding snippets
@@ -173,15 +126,14 @@ class Document:
                 else:
                         self.language_id = None
 
-                if self.instance:
-                        self.instance.language_changed(self)
-
                 if langid != 0:
                         Library().unref(langid)
 
                 Library().ref(self.language_id)
                 self.provider.language_id = self.language_id
 
+                SharedData().update_state(self.view.get_toplevel())
+
         def accelerator_activate(self, keyval, mod):
                 if not self.view or not self.view.get_editable():
                         return False
@@ -190,8 +142,6 @@ class Document:
                 snippets = Library().from_accelerator(accelerator, \
                                 self.language_id)
 
-                snippets_debug('Accel!')
-
                 if len(snippets) == 0:
                         return False
                 elif len(snippets) == 1:
@@ -558,14 +508,12 @@ class Document:
                         cur = buf.get_iter_at_mark(buf.get_insert())
                         last = sn.end_iter()
 
-                        # FIXME: get_iter_location doesnt work
+                        curloc = self.view.get_iter_location(cur)
+                        lastloc = self.view.get_iter_location(last)
 
-                        #curloc = self.view.get_iter_location(cur)
-                        #lastloc = self.view.get_iter_location(last)
-
-                        #if (lastloc.y + lastloc.height) - curloc.y <= \
-                        #   self.view.get_visible_rect().height:
-                        #        self.view.scroll_mark_onscreen(sn.end_mark)
+                        if (lastloc.y + lastloc.height) - curloc.y <= \
+                           self.view.get_visible_rect().height:
+                                self.view.scroll_mark_onscreen(sn.end_mark)
 
                 buf.end_user_action()
                 self.view.grab_focus()
@@ -604,12 +552,18 @@ class Document:
                 return (word, start, end)
 
         def parse_and_run_snippet(self, data, iter):
+                if not self.view.get_editable():
+                        return
+
                 self.apply_snippet(DynamicSnippet(data), iter, iter)
 
         def run_snippet_trigger(self, trigger, bounds):
                 if not self.view:
                         return False
 
+                if not self.view.get_editable():
+                        return False
+
                 snippets = Library().from_tag(trigger, self.language_id)
                 buf = self.view.get_buffer()
 
@@ -630,6 +584,9 @@ class Document:
                 if not self.view:
                         return False
 
+                if not self.view.get_editable():
+                        return False
+
                 buf = self.view.get_buffer()
 
                 # get the word preceding the current insertion position
@@ -702,11 +659,6 @@ class Document:
 
                 return False
 
-        # Callbacks
-        def on_view_destroy(self, view):
-                self.stop()
-                return
-
         def on_buffer_cursor_moved(self, buf):
                 piter = buf.get_iter_at_mark(buf.get_insert())
 
@@ -784,14 +736,14 @@ class Document:
         def on_notify_language(self, buf, spec):
                 self.update_language()
 
-        def on_notify_editable(self, view, spec):
-                self._set_view(view)
-
         def on_view_key_press(self, view, event):
                 library = Library()
 
                 state = event.get_state()
 
+                if not self.view.get_editable():
+                        return False
+
                 if not (state & Gdk.ModifierType.CONTROL_MASK) and \
                                 not (state & Gdk.ModifierType.MOD1_MASK) and \
                                 event.keyval in self.TAB_KEY_VAL:
@@ -909,6 +861,9 @@ class Document:
                 if not (Gtk.targets_include_uri(context.targets) and data.data and self.in_bounds(x, y)):
                         return
 
+                if not self.view.get_editable():
+                        return
+
                 uris = drop_get_uris(data)
                 uris.reverse()
                 stop = False
@@ -943,6 +898,9 @@ class Document:
                 self.provider.set_proposals(None)
 
         def on_proposal_activated(self, proposal, piter):
+                if not self.view.get_editable():
+                        return False
+
                 buf = self.view.get_buffer()
                 bounds = buf.get_selection_bounds()
 
diff --git a/plugins/snippets/snippets/manager.py b/plugins/snippets/snippets/manager.py
index 7ec4b95..8649a05 100644
--- a/plugins/snippets/snippets/manager.py
+++ b/plugins/snippets/snippets/manager.py
@@ -47,7 +47,6 @@ class Manager(Gtk.Dialog, Gtk.Buildable):
         def __init__(self):
                 self.snippet = None
                 self._temp_export = None
-                self.snippets_doc = None
 
                 self.key_press_id = 0
 
@@ -310,7 +309,6 @@ class Manager(Gtk.Dialog, Gtk.Buildable):
                 if lang:
                         source_view.get_buffer().set_highlight_syntax(True)
                         source_view.get_buffer().set_language(lang)
-                        self.snippets_doc = Document(None, source_view)
 
                 combo = self['combo_drop_targets']
 
@@ -588,9 +586,6 @@ class Manager(Gtk.Dialog, Gtk.Buildable):
                       shutil.rmtree(os.path.dirname(self._temp_export))
                       self._temp_export = None
 
-                if self.snippets_doc:
-                        self.snippets_doc.stop()
-
                 self.unref_languages()
                 self.snippet = None
                 self.model = None
diff --git a/plugins/snippets/snippets/shareddata.py b/plugins/snippets/snippets/shareddata.py
index f1d8f0d..1b5c9c2 100644
--- a/plugins/snippets/snippets/shareddata.py
+++ b/plugins/snippets/snippets/shareddata.py
@@ -26,6 +26,40 @@ class SharedData(object):
     def __init__(self):
         self.dlg = None
         self.dlg_default_size = None
+        self.controller_registry = {}
+        self.windows = {}
+
+    def register_controller(self, view, controller):
+        self.controller_registry[view] = controller
+    
+    def unregister_controller(self, view, controller):
+        if self.controller_registry[view] == controller:
+            del self.controller_registry[view]
+
+    def register_window(self, window):
+        self.windows[window.window] = window
+
+    def unregister_window(self, window):
+        if window.window in self.windows:
+            del self.windows[window.window]
+
+    def update_state(self, window):
+        if window in self.windows:
+            self.windows[window].do_update_state()
+
+    def get_active_controller(self, window):
+        view = window.get_active_view()
+
+        if not view or not view in self.controller_registry:
+            return None
+
+        return self.controller_registry[view]
+
+    def get_controller(self, view):
+        if view in self.controller_registry:
+            return self.controller_registry[view]
+        else:
+            return None
 
     def manager_destroyed(self, dlg):
         self.dlg_default_size = [dlg.get_allocation().width, dlg.get_allocation().height]
@@ -42,9 +76,7 @@ class SharedData(object):
             if self.dlg_default_size:
                 self.dlg.set_default_size(self.dlg_default_size[0], self.dlg_default_size[1])
 
-        if window:
-            self.dlg.set_transient_for(window)
-
+        self.dlg.set_transient_for(window)
         self.dlg.present()
 
 # vi:ex:ts=4:et
diff --git a/plugins/snippets/snippets/signals.py b/plugins/snippets/snippets/signals.py
new file mode 100644
index 0000000..aa895d2
--- /dev/null
+++ b/plugins/snippets/snippets/signals.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+#
+#  signals.py
+#
+#  Copyright (C) 2009 - Jesse van den Kieboom
+#
+#  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, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330,
+#  Boston, MA 02111-1307, USA.
+
+class Signals:
+    def __init__(self):
+        self._signals = {}
+
+    def _connect(self, obj, name, handler, connector):
+        ret = self._signals.setdefault(obj, {})
+
+        hid = connector(name, handler)
+        ret.setdefault(name, []).append(hid)
+
+        return hid
+
+    def connect_signal(self, obj, name, handler):
+        return self._connect(obj, name, handler, obj.connect)
+
+    def connect_signal_after(self, obj, name, handler):
+        return self._connect(obj, name, handler, obj.connect_after)
+
+    def disconnect_signals(self, obj):
+        if obj not in self._signals:
+            return False
+
+        for name in self._signals[obj]:
+            for hid in self._signals[obj][name]:
+                obj.disconnect(hid)
+
+        del self._signals[obj]
+        return True
+
+    def block_signal(self, obj, name):
+        if obj not in self._signals:
+            return False
+
+        if name not in self._signals[obj]:
+            return False
+
+        for hid in self._signals[obj][name]:
+            obj.handler_block(hid)
+
+        return True
+
+    def unblock_signal(self, obj, name):
+        if obj not in self._signals:
+            return False
+
+        if name not in self._signals[obj]:
+            return False
+
+        for hid in self._signals[obj][name]:
+            obj.handler_unblock(hid)
+
+        return True
+
+    def disconnect_signal(self, obj, name):
+        if obj not in self._signals:
+            return False
+
+        if name not in self._signals[obj]:
+            return False
+
+        for hid in self._signals[obj][name]:
+            obj.disconnect(hid)
+
+        del self._signals[obj][name]
+
+        if len(self._signals[obj]) == 0:
+            del self._signals[obj]
+
+        return True
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/windowactivatable.py b/plugins/snippets/snippets/windowactivatable.py
index 9b50ed0..856e07d 100644
--- a/plugins/snippets/snippets/windowactivatable.py
+++ b/plugins/snippets/snippets/windowactivatable.py
@@ -24,6 +24,7 @@ from gi.repository import Gtk, Gdk, Gedit, GObject
 from document import Document
 from library import Library
 from shareddata import SharedData
+from signals import Signals
 
 class Activate(Gedit.Message):
         view = GObject.property(type=Gedit.View)
@@ -31,14 +32,16 @@ class Activate(Gedit.Message):
 #        iter = GObject.property(type=Gtk.TextIter)
         trigger = GObject.property(type=str)
 
-class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
+class WindowActivatable(GObject.Object, Gedit.WindowActivatable, Signals):
         __gtype_name__ = "GeditSnippetsWindowActivatable"
 
         window = GObject.property(type=Gedit.Window)
 
         def __init__(self):
-                self.current_controller = None
-                self.current_language = None
+                GObject.Object.__init__(self)
+                Signals.__init__(self)
+
+                self.current_language_accel_group = None
                 self.signal_ids = {}
 
         def do_activate(self):
@@ -53,15 +56,14 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
                 if self.accel_group:
                         self.window.add_accel_group(self.accel_group)
 
-                self.window.connect('tab-added', self.on_tab_added)
-
-                # Add controllers to all the current views
-                for view in self.window.get_views():
-                        if isinstance(view, Gedit.View) and not self.has_controller(view):
-                                view._snippet_controller = Document(self, view)
+                self.connect_signal(self.window,
+                                    'active-tab-changed',
+                                    self.on_active_tab_changed)
 
                 self.do_update_state()
 
+                SharedData().register_window(self)
+
         def do_deactivate(self):
                 if self.accel_group:
                         self.window.remove_accel_group(self.accel_group)
@@ -71,26 +73,17 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
                 self.remove_menu()
                 self.unregister_messages()
 
-                # Iterate over all the tabs and remove every controller
-                for view in self.window.get_views():
-                        if isinstance(view, Gedit.View) and self.has_controller(view):
-                                view._snippet_controller.stop()
-                                view._snippet_controller = None
-
                 library = Library()
                 library.remove_accelerator_callback(self.accelerator_activated)
 
-        def do_update_state(self):
-                view = self.window.get_active_view()
+                self.disconnect_signals(self.window)
 
-                if not view or not self.has_controller(view):
-                        return
+                SharedData().unregister_window(self)
 
-                controller = view._snippet_controller
+        def do_update_state(self):
+                controller = SharedData().get_active_controller(self.window)
 
-                if controller != self.current_controller:
-                        self.current_controller = controller
-                        self.update_language()
+                self.update_language(controller)
 
         def register_messages(self):
                 bus = self.window.get_message_bus()
@@ -111,7 +104,9 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
                 if not view:
                         view = self.window.get_active_view()
 
-                if not self.has_controller(view):
+                controller = SharedData().get_controller(view)
+
+                if not controller:
                         return
 
                 # TODO: fix me as soon as the property fix lands in pygobject
@@ -119,8 +114,6 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
 
                 #if not iter:
                 iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert())
-
-                controller = view._snippet_controller
                 controller.run_snippet_trigger(message.props.trigger, (iter, iter))
 
         def on_message_parse_and_activate(self, bus, message, userdata):
@@ -129,15 +122,16 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
                 if not view:
                         view = self.window.get_active_view()
 
-                if not self.has_controller(view):
-                        return
-
-                iter = message.props.iter
+                controller = SharedData().get_controller(view)
 
-                if not iter:
-                        iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert())
+                if not controller:
+                        return
 
-                controller = view._snippet_controller
+                # TODO: fix me as soon as the property fix lands in pygobject
+                #iter = message.props.iter
+                
+                #if not iter:
+                iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert())
                 controller.parse_and_run_snippet(message.snippet, iter)
 
         def insert_menu(self):
@@ -170,43 +164,33 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
 
                 return result
 
-        def has_controller(self, view):
-                return hasattr(view, '_snippet_controller') and view._snippet_controller
-
-        def update_language(self):
+        def update_language(self, controller):
                 if not self.window:
                         return
 
-                if self.current_language:
-                        accel_group = Library().get_accel_group( \
-                                        self.current_language)
-                        self.window.remove_accel_group(accel_group)
-
-                if self.current_controller:
-                        self.current_language = self.current_controller.language_id
-
-                        if self.current_language != None:
-                                accel_group = Library().get_accel_group( \
-                                                self.current_language)
-                                self.window.add_accel_group(accel_group)
+                if controller:
+                        langid = controller.language_id
                 else:
-                        self.current_language = None
+                        langid = None
 
-        def language_changed(self, controller):
-                if controller == self.current_controller:
-                        self.update_language()
+                if langid != None:
+                        accelgroup = Library().get_accel_group(langid)
+                else:
+                        accelgroup = None
 
-        # Callbacks
+                if accelgroup != self.current_language_accel_group:
+                        if self.current_language_accel_group:
+                                self.window.remove_accel_group(self.current_language_accel_group)
 
-        def on_tab_added(self, window, tab):
-                # Create a new controller for this tab if it has a standard gedit view
-                view = tab.get_view()
+                        if accelgroup:
+                                self.window.add_accel_group(accelgroup)
 
-                if isinstance(view, Gedit.View) and not self.has_controller(view):
-                        view._snippet_controller = Document(self, view)
+                self.current_language_accel_group = accelgroup
 
-                self.do_update_state()
+        def on_active_tab_changed(self, window, tab):
+                self.update_language(SharedData().get_controller(tab.get_view()))
 
+        # Callbacks
         def create_configure_dialog(self):
                 SharedData().show_manager(self.window, self.plugin_info.get_data_dir())
 
@@ -215,7 +199,10 @@ class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
 
         def accelerator_activated(self, group, obj, keyval, mod):
                 if obj == self.window:
-                        return self.current_controller.accelerator_activate(keyval, mod)
+                        controller = SharedData().get_active_controller(self.window)
+
+                        if controller:
+                                return controller.accelerator_activate(keyval, mod)
                 else:
                         return False
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]