[gnome-sudoku] Add ability to enter puzzles manually



commit 092d2f625fc7edeebb3f67016e555b2269430071
Author: Parin Porecha <parinporecha gmail com>
Date:   Thu Apr 16 22:24:50 2015 +0530

    Add ability to enter puzzles manually
    
    The custom puzzle creator presents an empty board with a couple of additions to help editing -
    - Earmarks and Timer are disabled.
    - Warnings are enabled irrespective of the setting.
    - Basic puzzle validation checks.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=559298

 data/gnome-sudoku.ui      |   33 ++++++++++++++++-
 lib/sudoku-board.vala     |   22 ++++++++++-
 lib/sudoku-game.vala      |   65 ++++++++++++++++++++++++++-----
 lib/sudoku-generator.vala |    4 +-
 lib/sudoku-saver.vala     |    6 ++-
 src/gnome-sudoku.vala     |   91 ++++++++++++++++++++++++++++++++++++--------
 src/sudoku-view.vala      |   38 ++++++++++++-------
 7 files changed, 213 insertions(+), 46 deletions(-)
---
diff --git a/data/gnome-sudoku.ui b/data/gnome-sudoku.ui
index 094d139..bdf0e2e 100644
--- a/data/gnome-sudoku.ui
+++ b/data/gnome-sudoku.ui
@@ -122,7 +122,6 @@
                                 <property name="visible">True</property>
                                 <property name="halign">center</property>
                                 <property name="valign">center</property>
-                                <property name="homogeneous">True</property>
                                 <property name="margin">0</property>
                                 <property name="width-request">350</property>
                                 <property name="height-request">350</property>
@@ -182,6 +181,20 @@
                                         <property name="position">3</property>
                                     </packing>
                                 </child>
+                                <child>
+                                    <object class="GtkButton" id="create_game_button">
+                                        <property name="visible">True</property>
+                                        <property name="use_underline">True</property>
+                                        <property name="margin_top">30</property>
+                                        <property name="label" translatable="yes">_Create your own 
puzzle</property>
+                                        <property name="action-name">app.create-game</property>
+                                    </object>
+                                    <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">5</property>
+                                    </packing>
+                                </child>
                             </object> <!-- End of start_box -->
                             <packing>
                                 <property name="name">start_box</property>
@@ -267,6 +280,24 @@
                                                         <property name="position">2</property>
                                                     </packing>
                                                 </child>
+                                                <child>
+                                                    <object class="GtkButton" id="play_custom_game_button">
+                                                        <property name="visible">False</property>
+                                                        <property name="use_underline">True</property>
+                                                        <property name="label" translatable="yes">_Start 
Playing</property>
+                                                        <property name="halign">fill</property>
+                                                        <property name="valign">center</property>
+                                                        <property 
name="action-name">app.play-custom-game</property>
+                                                        <property name="tooltip-text" 
translatable="yes">Start playing the custom puzzle you have created</property>
+                                                        <property name="width-request">120</property>
+                                                        <property name="height-request">60</property>
+                                                    </object>
+                                                    <packing>
+                                                        <property name="expand">True</property>
+                                                        <property name="fill">True</property>
+                                                        <property name="position">3</property>
+                                                    </packing>
+                                                </child>
                                             </object>  <!-- End of controls_box -->
                                             <packing>
                                                 <property name="expand">False</property>
diff --git a/lib/sudoku-board.vala b/lib/sudoku-board.vala
index 500ceea..4507ac8 100644
--- a/lib/sudoku-board.vala
+++ b/lib/sudoku-board.vala
@@ -67,6 +67,11 @@ public class SudokuBoard : Object
     /* the number of fixed squares on the board */
     public int fixed { get; private set; }
 
+    public int size
+    {
+        get { return rows * cols; }
+    }
+
     public bool complete
     {
         get { return filled == cols * rows && !broken; }
@@ -77,6 +82,11 @@ public class SudokuBoard : Object
         return filled == fixed && n_earmarks == 0;
     }
 
+    public bool is_fully_filled ()
+    {
+        return filled == cols * rows;
+    }
+
     public signal void completed ();
 
     /* The set of coordinates on the board which are invalid */
@@ -377,6 +387,9 @@ public class SudokuBoard : Object
         }
 
         filled--;
+
+        if (is_fixed)
+            fixed--;
     }
 
     public Set<Coord?> get_occurances(Gee.List<Coord?> coords, int val)
@@ -587,7 +600,8 @@ public enum DifficultyCategory {
     EASY,
     MEDIUM,
     HARD,
-    VERY_HARD;
+    VERY_HARD,
+    CUSTOM;
 
     public string to_string ()
     {
@@ -603,6 +617,8 @@ public enum DifficultyCategory {
                 return _("Hard Difficulty");
             case VERY_HARD:
                 return _("Very Hard Difficulty");
+            case CUSTOM:
+                return _("Custom Puzzle");
             default:
                 assert_not_reached ();
         }
@@ -622,6 +638,8 @@ public enum DifficultyCategory {
                 return "Hard Difficulty";
             case VERY_HARD:
                 return "Very Hard Difficulty";
+            case CUSTOM:
+                return "Custom Puzzle";
             default:
                 assert_not_reached ();
         }
@@ -641,6 +659,8 @@ public enum DifficultyCategory {
                 return HARD;
             case "Very Hard Difficulty":
                 return VERY_HARD;
+            case "Custom Puzzle":
+                return CUSTOM;
             default:
                 warning ("Could not parse difficulty level. Falling back to Easy difficulty");
                 return EASY;
diff --git a/lib/sudoku-game.vala b/lib/sudoku-game.vala
index 82df700..1e6ce4c 100644
--- a/lib/sudoku-game.vala
+++ b/lib/sudoku-game.vala
@@ -24,6 +24,7 @@ using Gee;
 public class SudokuGame : Object
 {
     public SudokuBoard board;
+    public GameMode mode;
     public GLib.Timer timer;
     private uint clock_timeout;
 
@@ -66,17 +67,24 @@ public class SudokuGame : Object
     public SudokuGame (SudokuBoard board)
     {
         this.board = board;
+        this.mode = GameMode.PLAY;
         timer = new Timer();
         undostack = new ArrayList<UndoItem?> ();
         redostack = new ArrayList<UndoItem?> ();
-        board.completed.connect (() => stop_clock ());
     }
 
     public void insert (int row, int col, int val)
     {
         var old_val = board[row, col];
         update_undo (row, col, old_val, val);
-        board.insert (row, col, val);
+
+        if (mode == GameMode.CREATE) {
+            board.insert (row, col, val, true);
+            board.is_fixed[row, col] = true;
+        }
+        else
+            board.insert (row, col, val);
+
         cell_changed (row, col, old_val, val);
     }
 
@@ -84,10 +92,25 @@ public class SudokuGame : Object
     {
         int old_val = board[row, col];
         update_undo (row, col, old_val, 0);
-        board.remove (row, col);
+
+        if (mode == GameMode.CREATE) {
+            board.remove (row, col, true);
+            board.is_fixed[row, col] = false;
+        }
+        else
+            board.remove (row, col);
+
         cell_changed (row, col, old_val, 0);
     }
 
+    public bool is_empty ()
+    {
+        if (mode == GameMode.CREATE)
+            return board.filled == 0;
+        else
+            return board.is_empty ();
+    }
+
     public void undo ()
     {
         apply_stack (undostack, redostack);
@@ -108,11 +131,12 @@ public class SudokuGame : Object
         {
             for (var l2 = 0; l2 < board.cols; l2++)
             {
-                if (!board.is_fixed[l1, l2])
-                {
-                    board.remove (l1, l2);
-                    cell_changed (l1, l2, board.get (l1, l2), 0);
-                }
+                if (mode == GameMode.PLAY && board.is_fixed[l1, l2])
+                    continue;
+
+                board.remove (l1, l2, board.is_fixed[l1, l2]);
+                board.is_fixed[l1, l2] = false;
+                cell_changed (l1, l2, board.get (l1, l2), 0);
             }
         }
         board.broken_coords.clear ();
@@ -143,9 +167,23 @@ public class SudokuGame : Object
         var top = from.remove_at (from.size - 1);
         int old_val = board [top.row, top.col];
         add_to_stack (to, top.row, top.col, old_val);
-        board.remove (top.row, top.col);
-        if (top.val != 0)
-            board.insert (top.row, top.col, top.val);
+
+        if (mode == GameMode.CREATE) {
+            board.remove (top.row, top.col, board.is_fixed[top.row, top.col]);
+            board.is_fixed[top.row, top.col] = false;
+        }
+        else
+            board.remove (top.row, top.col);
+
+        if (top.val != 0) {
+            if (mode == GameMode.CREATE) {
+                board.insert (top.row, top.col, top.val, board.is_fixed[top.row, top.col]);
+                board.is_fixed[top.row, top.col] = true;
+            }
+            else
+                board.insert (top.row, top.col, top.val);
+        }
+
         cell_changed (top.row, top.col, old_val, top.val);
     }
 
@@ -189,3 +227,8 @@ public class SudokuGame : Object
         timeout_cb ();
     }
 }
+
+public enum GameMode {
+    PLAY,
+    CREATE;
+}
diff --git a/lib/sudoku-generator.vala b/lib/sudoku-generator.vala
index 65f30c8..1c1db49 100644
--- a/lib/sudoku-generator.vala
+++ b/lib/sudoku-generator.vala
@@ -66,7 +66,9 @@ public class SudokuGenerator : Object
     private static SudokuBoard generate_board (DifficultyCategory category)
     {
         var board = new SudokuBoard ();
-        int[] puzzle = QQwing.generate_puzzle ((int) category);
+        int[] puzzle = new int[board.rows * board.cols];
+        if (category != DifficultyCategory.CUSTOM)
+            puzzle = QQwing.generate_puzzle ((int) category);
 
         for (var row = 0; row < board.rows; row++)
             for (var col = 0; col < board.cols; col++)
diff --git a/lib/sudoku-saver.vala b/lib/sudoku-saver.vala
index 22cca26..31f78df 100644
--- a/lib/sudoku-saver.vala
+++ b/lib/sudoku-saver.vala
@@ -106,8 +106,12 @@ public class SudokuSaver : Object
         builder.begin_object ();
         builder.set_member_name ("difficulty_category");
         builder.add_string_value (board.difficulty_category.to_untranslated_string ());
+
         builder.set_member_name ("time_elapsed");
-        builder.add_double_value (game.get_total_time_played ());
+        if (game.mode == GameMode.CREATE)
+            builder.add_double_value (0);
+        else
+            builder.add_double_value (game.get_total_time_played ());
 
         builder.set_member_name ("cells");
         builder.begin_array ();
diff --git a/src/gnome-sudoku.vala b/src/gnome-sudoku.vala
index 48c6bcb..1ec9ed8 100644
--- a/src/gnome-sudoku.vala
+++ b/src/gnome-sudoku.vala
@@ -28,6 +28,7 @@ public class Sudoku : Gtk.Application
     private bool is_tiled;
     private int window_width;
     private int window_height;
+    private Gtk.Button play_custom_game_button;
     private Gtk.Button play_pause_button;
     private Gtk.Label play_pause_label;
     private Gtk.Label clock_label;
@@ -53,19 +54,23 @@ public class Sudoku : Gtk.Application
     private SimpleAction print_action;
     private SimpleAction print_multiple_action;
     private SimpleAction pause_action;
+    private SimpleAction play_custom_game_action;
     private SimpleAction new_game_action;
 
     private bool show_possibilities = false;
+    private GameMode current_game_mode = GameMode.PLAY;
 
     private const GLib.ActionEntry action_entries[] =
     {
         {"new-game", new_game_cb                                    },
         {"start-game", start_game_cb, "i"                           },
+        {"create-game", create_game_cb                              },
         {"reset", reset_cb                                          },
         {"back", back_cb                                            },
         {"undo", undo_cb                                            },
         {"redo", redo_cb                                            },
         {"print", print_cb                                          },
+        {"play-custom-game", play_custom_game_cb                    },
         {"pause", toggle_pause_cb                                   },
         {"print-multiple", print_multiple_cb                        },
         {"help", help_cb                                            },
@@ -134,7 +139,7 @@ public class Sudoku : Gtk.Application
         settings = new GLib.Settings ("org.gnome.sudoku");
         var action = settings.create_action ("show-warnings");
         action.notify["state"].connect (() => {
-            if (view != null)
+            if (view != null && current_game_mode == GameMode.PLAY)
                 view.show_warnings = settings.get_boolean ("show-warnings");
         });
         add_action (action);
@@ -178,6 +183,7 @@ public class Sudoku : Gtk.Application
         back_button = (Button) builder.get_object ("back_button");
         clock_label = (Gtk.Label) builder.get_object ("clock_label");
         clock_image = (Gtk.Image) builder.get_object ("clock_image");
+        play_custom_game_button = (Gtk.Button) builder.get_object ("play_custom_game_button");
         play_pause_button = (Gtk.Button) builder.get_object ("play_pause_button");
         play_pause_label = (Gtk.Label) builder.get_object ("play_pause_label");
 
@@ -188,6 +194,7 @@ public class Sudoku : Gtk.Application
         print_action = (SimpleAction) lookup_action ("print");
         print_multiple_action = (SimpleAction) lookup_action ("print-multiple");
         pause_action = (SimpleAction) lookup_action ("pause");
+        play_custom_game_action = (SimpleAction) lookup_action ("play-custom-game");
 
         if (!is_desktop ("Unity"))
         {
@@ -202,8 +209,11 @@ public class Sudoku : Gtk.Application
 
         saver = new SudokuSaver ();
         var savegame = saver.get_savedgame ();
-        if (savegame != null)
+        if (savegame != null) {
+            if (savegame.board.difficulty_category == DifficultyCategory.CUSTOM)
+                current_game_mode = savegame.board.filled == savegame.board.fixed ? GameMode.CREATE : 
GameMode.PLAY;
             start_game (savegame.board);
+        }
         else
             show_new_game_screen ();
     }
@@ -217,10 +227,10 @@ public class Sudoku : Gtk.Application
     {
         if (game != null)
         {
-            if (!game.board.is_empty () && !game.board.complete)
+            if (!game.is_empty () && !game.board.complete)
                 saver.save_game (game);
 
-            if (game.board.is_empty () && saver.get_savedgame () != null)
+            if (game.is_empty () && saver.get_savedgame () != null)
             {
                 var file = File.new_for_path (SudokuSaver.savegame_file);
 
@@ -274,13 +284,20 @@ public class Sudoku : Gtk.Application
         else if (game.get_total_time_played () > 0)
         {
             display_pause_button ();
-            clear_action.set_enabled (!game.board.is_empty ());
+            clear_action.set_enabled (!game.is_empty ());
             undo_action.set_enabled (!game.is_undostack_null ());
             redo_action.set_enabled (!game.is_redostack_null ());
             new_game_action.set_enabled (true);
         }
     }
 
+    private void play_custom_game_cb ()
+    {
+        current_game_mode = GameMode.PLAY;
+        game.stop_clock ();
+        start_game (game.board);
+    }
+
     private void toggle_pause_cb ()
     {
        if (game.paused)
@@ -315,27 +332,31 @@ public class Sudoku : Gtk.Application
 
     private void start_game (SudokuBoard board)
     {
-        undo_action.set_enabled (false);
-        redo_action.set_enabled (false);
-
         if (view != null)
             game_box.remove (view);
 
         show_game_view ();
         game = new SudokuGame (board);
+        game.mode = current_game_mode;
 
-        headerbar.title = board.difficulty_category.to_string ();
+        undo_action.set_enabled (false);
+        redo_action.set_enabled (false);
+        set_headerbar_title ();
+        clear_action.set_enabled (!game.is_empty ());
+        play_custom_game_action.set_enabled (!game.is_empty ());
 
         game.tick.connect (tick_cb);
         game.paused_changed.connect (paused_changed_cb);
-
         game.start_clock ();
 
         view = new SudokuView (game);
         view.set_size_request (480, 480);
 
         view.show_possibilities = show_possibilities;
-        view.show_warnings = settings.get_boolean ("show-warnings");
+        if (current_game_mode == GameMode.CREATE)
+            view.show_warnings = true;
+        else
+            view.show_warnings = settings.get_boolean ("show-warnings");
         view.highlighter = settings.get_boolean ("highlighter");
 
         view.show ();
@@ -344,10 +365,17 @@ public class Sudoku : Gtk.Application
         game.cell_changed.connect (() => {
             undo_action.set_enabled (!game.is_undostack_null ());
             redo_action.set_enabled (!game.is_redostack_null ());
-            clear_action.set_enabled (!game.board.is_empty ());
+            clear_action.set_enabled (!game.is_empty ());
+            play_custom_game_action.set_enabled (!game.is_empty () && !game.board.is_fully_filled ());
         });
 
+        if (current_game_mode == GameMode.CREATE)
+            return;
+
         game.board.completed.connect (() => {
+            play_custom_game_button.visible = false;
+            game.stop_clock ();
+
             for (var i = 0; i < game.board.rows; i++)
                 for (var j = 0; j < game.board.cols; j++)
                     view.can_focus = false;
@@ -379,7 +407,6 @@ public class Sudoku : Gtk.Application
     private void show_new_game_screen ()
     {
         main_stack.set_visible_child_name ("start_box");
-        clear_action.set_enabled (false);
         back_button.visible = game != null;
         undo_redo_box.visible = false;
         headerbar.title = _("Select Difficulty");
@@ -395,6 +422,19 @@ public class Sudoku : Gtk.Application
         show_new_game_screen ();
     }
 
+    private void create_game_cb ()
+    {
+        current_game_mode = GameMode.CREATE;
+        SudokuGenerator.generate_boards_async.begin (1, DifficultyCategory.CUSTOM, null, (obj, res) => {
+            try {
+                var gen_boards = SudokuGenerator.generate_boards_async.end (res);
+                start_game (gen_boards[0]);
+            } catch (Error e) {
+                error ("Error: %s", e.message);
+            }
+        });
+    }
+
     private void start_game_cb (SimpleAction action, Variant? difficulty)
     {
         // Since we cannot have enums in .ui file, the 'action-target' property
@@ -404,6 +444,7 @@ public class Sudoku : Gtk.Application
         var selected_difficulty = (DifficultyCategory) difficulty.get_int32 ();
 
         back_button.sensitive = false;
+        current_game_mode = GameMode.PLAY;
 
         SudokuGenerator.generate_boards_async.begin (1, selected_difficulty, null, (obj, res) => {
             try {
@@ -423,8 +464,8 @@ public class Sudoku : Gtk.Application
         dialog.response.connect ((response_id) => {
             if (response_id == ResponseType.OK)
             {
-                view.clear ();
                 game.reset ();
+                view.clear ();
                 undo_action.set_enabled (false);
                 redo_action.set_enabled (false);
             }
@@ -444,16 +485,32 @@ public class Sudoku : Gtk.Application
         clock_image.show ();
 
         if (game != null)
-        {
             game.resume_clock ();
-            clear_action.set_enabled (!game.board.is_empty ());
-            headerbar.title = game.board.difficulty_category.to_string ();
+
+        if (current_game_mode == GameMode.PLAY) {
+            play_custom_game_button.visible = false;
+            play_pause_button.visible = true;
         }
+        else {
+            clock_label.hide ();
+            clock_image.hide ();
+            play_custom_game_button.visible = true;
+            play_pause_button.visible = false;
+        }
+    }
+
+    private void set_headerbar_title ()
+    {
+        if (current_game_mode == GameMode.PLAY)
+            headerbar.title = game.board.difficulty_category.to_string ();
+        else
+            headerbar.title = _("Create Puzzle");
     }
 
     private void back_cb ()
     {
         show_game_view ();
+        set_headerbar_title ();
     }
 
     private void undo_cb ()
diff --git a/src/sudoku-view.vala b/src/sudoku-view.vala
index 1b7ec1b..c4b161e 100644
--- a/src/sudoku-view.vala
+++ b/src/sudoku-view.vala
@@ -46,7 +46,8 @@ private class SudokuCellView : Gtk.DrawingArea
                 string text = "%d".printf (game.board [row, col]);
                 layout = create_pango_layout (text);
                 layout.set_font_description (style.font_desc);
-                return;
+                if (game.mode == GameMode.PLAY)
+                    return;
             }
             if (value == 0)
             {
@@ -55,7 +56,8 @@ private class SudokuCellView : Gtk.DrawingArea
                 layout.set_font_description (style.font_desc);
                 if (game.board [row, col] != 0)
                     game.remove (row, col);
-                return;
+                if (game.mode == GameMode.PLAY)
+                    return;
             }
             if (value == game.board [row, col])
             {
@@ -115,7 +117,7 @@ private class SudokuCellView : Gtk.DrawingArea
         can_focus = true;
         events = EventMask.EXPOSURE_MASK | EventMask.BUTTON_PRESS_MASK | EventMask.KEY_PRESS_MASK;
 
-        if (is_fixed)
+        if (is_fixed && game.mode == GameMode.PLAY)
             return;
 
         focus_out_event.connect (focus_out_cb);
@@ -129,7 +131,7 @@ private class SudokuCellView : Gtk.DrawingArea
 
         if (!is_focus)
             grab_focus ();
-        if (is_fixed || game.paused)
+        if (game.mode == GameMode.PLAY && (is_fixed || game.paused))
             return false;
 
         if (popover != null || earmark_popover != null)
@@ -140,12 +142,12 @@ private class SudokuCellView : Gtk.DrawingArea
 
         if (event.button == 1)            // Left-Click
         {
-            if (!_show_possibilities && (event.state & ModifierType.CONTROL_MASK) > 0)
+            if (!_show_possibilities && (event.state & ModifierType.CONTROL_MASK) > 0 && game.mode == 
GameMode.PLAY)
                 show_earmark_picker ();
             else
                 show_number_picker ();
         }
-        else if (!_show_possibilities && event.button == 3)         // Right-Click
+        else if (!_show_possibilities && event.button == 3 && game.mode == GameMode.PLAY)         // 
Right-Click
             show_earmark_picker ();
 
         return false;
@@ -272,7 +274,7 @@ private class SudokuCellView : Gtk.DrawingArea
 
     public override bool key_press_event (Gdk.EventKey event)
     {
-        if (is_fixed || game.paused)
+        if (game.mode == GameMode.PLAY && (is_fixed || game.paused))
             return false;
         string k_name = Gdk.keyval_name (event.keyval);
         int k_no = int.parse (k_name);
@@ -281,7 +283,7 @@ private class SudokuCellView : Gtk.DrawingArea
             k_no = key_map_keypad (k_name);
         if (k_no >= 1 && k_no <= 9)
         {
-            if ((event.state & ModifierType.CONTROL_MASK) > 0)
+            if ((event.state & ModifierType.CONTROL_MASK) > 0 && game.mode == GameMode.PLAY)
             {
                 var new_state = !game.board.is_earmark_enabled (row, col, k_no);
                 if (earmark_picker == null)
@@ -357,7 +359,7 @@ private class SudokuCellView : Gtk.DrawingArea
             c.restore ();
         }
 
-        if (is_fixed)
+        if (is_fixed && game.mode == GameMode.PLAY)
             return false;
 
         if (!_show_possibilities)
@@ -417,6 +419,14 @@ private class SudokuCellView : Gtk.DrawingArea
         if (row == this.row && col == this.col)
         {
             this.value = new_val;
+
+            if (game.mode == GameMode.CREATE) {
+                if (_selected)
+                    background_color = selected_bg_color;
+                else
+                    background_color = is_fixed ? fixed_cell_color : free_cell_color;
+            }
+
             notify_property ("value");
         }
     }
@@ -427,6 +437,11 @@ private class SudokuCellView : Gtk.DrawingArea
     }
 }
 
+public const RGBA fixed_cell_color = {0.8, 0.8, 0.8, 0};
+public const RGBA free_cell_color = {1.0, 1.0, 1.0, 1.0};
+public const RGBA highlight_color = {0.93, 0.93, 0.93, 0};
+public const RGBA selected_bg_color = {0.7, 0.8, 0.9};
+
 public class SudokuView : Gtk.AspectFrame
 {
     public SudokuGame game;
@@ -438,11 +453,6 @@ public class SudokuView : Gtk.AspectFrame
     private Gtk.DrawingArea drawing;
     private Gtk.Grid grid;
 
-    private const RGBA fixed_cell_color = {0.8, 0.8, 0.8, 0};
-    private const RGBA free_cell_color = {1.0, 1.0, 1.0, 1.0};
-    private const RGBA highlight_color = {0.93, 0.93, 0.93, 0};
-    private const RGBA selected_bg_color = {0.7, 0.8, 0.9};
-
     private int selected_row = 0;
     private int selected_col = 0;
     private void set_selected (int cell_row, int cell_col)


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