banshee r3583 - in trunk/banshee: . src/Core/Banshee.Services/Banshee.Database src/Core/Banshee.ThickClient/Banshee.Gui src/Core/Banshee.Widgets/Banshee.Widgets src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio src/Libraries/Hyena.Gui/Hyena.Widgets src/Libraries/Lastfm/Lastfm



Author: gburt
Date: Sat Mar 29 07:37:47 2008
New Revision: 3583
URL: http://svn.gnome.org/viewvc/banshee?rev=3583&view=rev

Log:
2008-03-29  Gabriel Burt  <gabriel burt gmail com>

	* src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs:
	Make much much prettier.  Pack in a RoundedFrame, draw white bg, add Top
	Artists, make button presses pretty, make vertically scrollable.

	* src/Core/Banshee.Widgets/Banshee.Widgets/TileView.cs: Fix bug where
	wouldn't expand set a height request of more than 1, so unless packed in a
	vbox with fill/expand = true, it wasn't visible.

	* src/Libraries/Hyena.Gui/Hyena.Widgets/RoundedFrame.cs: Fix bug with not
	having the child calculate its size request before trying to get it.

	* src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs:
	Add improved metadata refreshing support - store a value in the
	CoreConfiguration when its successful, meaning it will keep retrying unti
	it is (so if you close it or it dies before its done, no big deal).

	* src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs:
	* src/Libraries/Lastfm/Lastfm/RadioConnection.cs: Clean up
	comments/debugging.

	* src/Core/Banshee.ThickClient/Banshee.Gui/IconThemeUtils.cs: Check name
	and return if null/empty.


Modified:
   trunk/banshee/ChangeLog
   trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/IconThemeUtils.cs
   trunk/banshee/src/Core/Banshee.Widgets/Banshee.Widgets/TileView.cs
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs
   trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Widgets/RoundedFrame.cs
   trunk/banshee/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs
   trunk/banshee/src/Libraries/Lastfm/Lastfm/RadioConnection.cs

Modified: trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Services/Banshee.Database/BansheeDbFormatMigrator.cs	Sat Mar 29 07:37:47 2008
@@ -61,7 +61,8 @@
         // NOTE: Whenever there is a change in ANY of the database schema,
         //       this version MUST be incremented and a migration method
         //       MUST be supplied to match the new version number
-        protected const int CURRENT_VERSION = 5;
+        protected const int CURRENT_VERSION = 6;
+        protected const int CURRENT_METADATA_VERSION = 1;
         
         protected class DatabaseVersionAttribute : Attribute 
         {
@@ -130,6 +131,12 @@
                 Execute ("BEGIN");
                 InnerMigrate ();
                 Execute ("COMMIT");
+
+                // Trigger metadata refreshes if necessary
+                int metadata_version = connection.Query<int> ("SELECT Value FROM CoreConfiguration WHERE Key = 'MetadataVersion'");
+                if (DatabaseVersion == CURRENT_VERSION && metadata_version < CURRENT_METADATA_VERSION) {
+                    ServiceManager.ServiceStarted += OnServiceStarted;
+                }
             } catch (Exception e) {
                 Console.WriteLine ("Rolling back transaction");
                 Console.WriteLine (e);
@@ -293,9 +300,13 @@
             Execute ("CREATE INDEX IF NOT EXISTS CoreSmartPlaylistEntriesIndex ON CoreSmartPlaylistEntries(SmartPlaylistID, TrackID)");
             Execute ("CREATE INDEX IF NOT EXISTS CorePlaylistEntriesIndex ON CorePlaylistEntries(PlaylistID, TrackID)");
             
-            refresh_from_file = false;
-            ServiceManager.ServiceStarted += OnServiceStarted;
-            
+            return true;
+        }
+
+        [DatabaseVersion (6)]
+        private bool Migrate_6 ()
+        {
+            Execute ("INSERT INTO CoreConfiguration VALUES (null, 'MetadataVersion', 0)");
             return true;
         }
         
@@ -557,9 +568,6 @@
 
             Execute ("UPDATE CoreSmartPlaylists SET PrimarySourceID = 1");
             Execute ("UPDATE CorePlaylists SET PrimarySourceID = 1");
-
-            refresh_from_file = true;
-            ServiceManager.ServiceStarted += OnServiceStarted;
         }
 
         private void OnServiceStarted (ServiceStartedArgs args)
@@ -593,7 +601,6 @@
             return false;
         }
 
-        private bool refresh_from_file = false;
         private void RefreshMetadataThread (object state)
         {
             int total = ServiceManager.DbConnection.Query<int> ("SELECT count(*) FROM CoreTracks");
@@ -619,27 +626,33 @@
             int count = 0;
             using (System.Data.IDataReader reader = ServiceManager.DbConnection.Query (select_command)) {
                 while (reader.Read ()) {
-                    DatabaseTrackInfo track = DatabaseTrackInfo.Provider.Load (reader, 0);
-                    
+                    DatabaseTrackInfo track = null;
                     try {
-                        if (refresh_from_file) {
+                        track = DatabaseTrackInfo.Provider.Load (reader, 0);
+                        
+                        try {
                             TagLib.File file = StreamTagger.ProcessUri (track.Uri);
                             StreamTagger.TrackInfoMerge (track, file, true);
+                        } catch (Exception e) {
+                            Log.Warning (String.Format ("Failed to update metadata for {0}", track),
+                                e.GetType ().ToString (), false);
                         }
+                        
+                        track.Save (false);
+                        track.Artist.Save ();
+                        track.Album.Save ();
                     } catch (Exception e) {
-                        Log.Warning (String.Format ("Failed to update metadata for {0}", track),
-                            e.GetType ().ToString (), false);
+                        Log.Warning (String.Format ("Failed to update metadata for {0}", track), e.ToString (), false);
+                        throw;
                     }
-                    
-                    track.Save (false);
-                    track.Artist.Save ();
-                    track.Album.Save ();
 
                     job.Status = String.Format ("{0} - {1}", track.DisplayArtistName, track.DisplayTrackTitle);
                     job.Progress = (double)++count / (double)total;
                 }
             }
 
+            Execute (String.Format ("UPDATE CoreConfiguration SET Value = {0} WHERE Key = 'MetadataVersion'", CURRENT_METADATA_VERSION));
+
             job.Finish ();
             ServiceManager.SourceManager.MusicLibrary.NotifyTracksChanged ();
         }

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/IconThemeUtils.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/IconThemeUtils.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/IconThemeUtils.cs	Sat Mar 29 07:37:47 2008
@@ -70,6 +70,9 @@
 
         public static Gdk.Pixbuf LoadIcon (Assembly assembly, string name, int size, bool fallBackOnResource)
         {
+            if (String.IsNullOrEmpty (name))
+                return null;
+
             Gdk.Pixbuf pixbuf = null;
             try {
                 pixbuf = IconTheme.Default.LoadIcon (name, size, (IconLookupFlags)0);

Modified: trunk/banshee/src/Core/Banshee.Widgets/Banshee.Widgets/TileView.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Widgets/Banshee.Widgets/TileView.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Widgets/Banshee.Widgets/TileView.cs	Sat Mar 29 07:37:47 2008
@@ -1,30 +1,31 @@
-/***************************************************************************
- *  TileView.cs
- *
- *  Copyright (C) 2006 Novell, Inc.
- *  Written by Aaron Bockover <aaron abock org>
- ****************************************************************************/
-
-/*  THIS FILE IS LICENSED UNDER THE MIT LICENSE AS OUTLINED IMMEDIATELY BELOW: 
- *
- *  Permission is hereby granted, free of charge, to any person obtaining a
- *  copy of this software and associated documentation files (the "Software"),  
- *  to deal in the Software without restriction, including without limitation  
- *  the rights to use, copy, modify, merge, publish, distribute, sublicense,  
- *  and/or sell copies of the Software, and to permit persons to whom the  
- *  Software is furnished to do so, subject to the following conditions:
- *
- *  The above copyright notice and this permission notice shall be included in 
- *  all copies or substantial portions of the Software.
- *
- *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
- *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
- *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
- *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
- *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
- *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
- *  DEALINGS IN THE SOFTWARE.
- */
+//
+// TileView.cs
+//
+// Authors:
+//   Aaron Bockover <abockover novell com>
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2006-2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
  
 using System;
 using System.Collections.Generic;
@@ -46,9 +47,14 @@
             current_column_count = initialColumnCount;
             
             Table table = new Table(1, 1, true);
+
             table.Show();
             cached_tables.Add(table);
             Add(table);
+
+            Show ();
+
+            ModifyBg (StateType.Normal, Style.Base (StateType.Normal));
         }
         
         public void AddWidget(Widget widget)
@@ -69,14 +75,6 @@
             LayoutTableDefault(cached_tables[0], widgets);
         }
 
-        protected override void OnSizeRequested(ref Requisition requisition)
-        {
-            SetSizeRequest(requisition.Width, requisition.Height);
-            base.OnSizeRequested(ref requisition);
-        }
-
-        private bool first_size_allocation = true;
-        
         protected override void OnSizeAllocated(Gdk.Rectangle allocation)
         {
             Widget child = null;
@@ -85,10 +83,9 @@
                 child = Children[0];
             }
 
-            if(first_size_allocation || child == null) {
+            if(child == null) {
                 base.OnSizeAllocated(allocation);
                 SetSize((uint)allocation.Width, (uint)allocation.Height);
-                first_size_allocation = false;
                 return;
             }
             
@@ -103,7 +100,6 @@
 
                 child.SizeAllocate(child_allocation);
                 SetSize((uint)child_allocation.Width, (uint)child_allocation.Height);
-                
                 return;
             }
             
@@ -117,7 +113,8 @@
             }
             
             base.OnSizeAllocated(allocation);
-            SetSize((uint)child.Allocation.Width, (uint)child.Allocation.Height);
+            SetSizeRequest (child.Allocation.Width, child.Allocation.Height);
+            SetSize ((uint)child.Allocation.Width, (uint)child.Allocation.Height);
         }
 
         private void RemoveContainerEntries(Container widget)

Modified: trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs	Sat Mar 29 07:37:47 2008
@@ -19,22 +19,47 @@
 
 namespace Banshee.Lastfm.Radio
 {
-    public class LastfmSourceContents : VBox, ISourceContents
+    public class LastfmSourceContents : Hyena.Widgets.ScrolledWindow, ISourceContents
     {
+        private VBox main_box;
         private LastfmSource lastfm;
 
         private NumberedList recently_loved;
         private NumberedList recently_played;
-        //private NumberedList recommended_artists;
+        private NumberedList top_artists;
+
+        private Viewport viewport;
 
         static LastfmSourceContents () {
             DataCore.UserAgent = Banshee.Web.Browser.UserAgent;
             DataCore.CachePath = System.IO.Path.Combine (Banshee.Base.Paths.ExtensionCacheRoot, "lastfm");
         }
 
-        // "Coming Soon: Profile, Friends, Recently Loved Songs, etc")
+        // "Coming Soon: Profile, Friends, Events etc")
         public LastfmSourceContents () : base ()
         {
+            HscrollbarPolicy = PolicyType.Never;
+            VscrollbarPolicy = PolicyType.Automatic;
+
+            viewport = new Viewport ();
+            viewport.ShadowType = ShadowType.None;
+
+            main_box = new VBox ();
+            main_box.Spacing = 6;
+            main_box.BorderWidth = 5;
+            main_box.ReallocateRedraws = true;
+
+            // Clamp the width, preventing horizontal scrolling
+            SizeAllocated += delegate (object o, SizeAllocatedArgs args) {
+                main_box.WidthRequest = args.Allocation.Width - 10;
+            };
+
+            viewport.Add (main_box);
+            
+            StyleSet += delegate { viewport.ModifyBg (StateType.Normal, Style.Base (StateType.Normal)); };
+
+            AddWithFrame (viewport);
+            ShowAll ();
         }
 
         public bool SetSource (ISource src)
@@ -66,7 +91,21 @@
             get { return this; }
         }
 
+        public void Refresh ()
+        {
+            if (user != null) {
+                user.RecentLovedTracks.Refresh ();
+                user.RecentTracks.Refresh ();
+                user.GetTopArtists (TopType.Overall).Refresh ();
+
+                recently_loved.SetList (user.RecentLovedTracks);
+                recently_played.SetList (user.RecentTracks);
+                top_artists.SetList (user.GetTopArtists (TopType.Overall));
+            }
+        }
+
         private string last_user;
+        private LastfmUserData user;
         private void UpdateForUser (string username)
         {
             if (username == last_user) {
@@ -76,32 +115,31 @@
             last_user = username;
 
             if (recently_loved != null) {
-                Remove (recently_loved);
-                Remove (recently_played);
+                main_box.Remove (recently_loved);
+                main_box.Remove (recently_played);
+                main_box.Remove (top_artists);
             }
 
             recently_loved = new NumberedList (lastfm, Catalog.GetString ("Recently Loved Tracks"));
             recently_played = new NumberedList (lastfm, Catalog.GetString ("Recently Played Tracks"));
+            top_artists = new NumberedList (lastfm, Catalog.GetString ("My Top Artists"));
             //recommended_artists = new NumberedList (Catalog.GetString ("Recommended Artists"));
 
-            PackStart (recently_loved, true, true, 0);
-            PackStart (recently_played, true, true, 0);
+            main_box.PackStart (recently_loved, false, false, 0);
+            main_box.PackStart (new HSeparator (), false, false, 5);
+            main_box.PackStart (recently_played, false, false, 0);
+            main_box.PackStart (new HSeparator (), false, false, 5);
+            main_box.PackStart (top_artists, false, false, 0);
             //PackStart (recommended_artists, true, true, 0);
 
-            LastfmUserData user = new LastfmUserData (username);
+            user = new LastfmUserData (username);
             recently_loved.SetList (user.RecentLovedTracks);
             recently_played.SetList (user.RecentTracks);
-
+            top_artists.SetList (user.GetTopArtists (TopType.Overall));
+            
             ShowAll ();
         }
 
-        /*private void TileMenuPosition (Menu menu, out int x, out int y, out bool push_in)
-        {
-            push_in = false;
-            x = 0;
-            y = 0;
-        }*/
-
         private void HandleConnectionStateChanged (object sender, ConnectionStateChangedArgs args)
         {
             if (args.State == ConnectionState.Connected) {
@@ -131,11 +169,18 @@
 
         public class NumberedTileView : TileView
         {
+            private int i = 1;
+
             public NumberedTileView (int cols) : base (cols)
             {
             }
 
-            private int i = 1;
+            public new void ClearWidgets ()
+            {
+                i = 1;
+                base.ClearWidgets ();
+            }
+
             public void AddNumberedWidget (Tile tile)
             {
                 tile.PrimaryText = String.Format ("{0}. {1}", i++, tile.PrimaryText);
@@ -159,6 +204,20 @@
                 tile_view.Show ();
             }
 
+            // TODO generalize this
+            public void SetList (LastfmData<UserTopArtist> artists)
+            {
+                tile_view.ClearWidgets ();
+
+                foreach (UserTopArtist artist in artists) {
+                    MenuTile tile = new MenuTile ();
+                    tile.PrimaryText = artist.Name;
+                    tile_view.AddNumberedWidget (tile);
+                }
+
+                tile_view.ShowAll ();
+            }
+
             public void SetList (LastfmData<RecentTrack> tracks)
             {
                 tile_view.ClearWidgets ();
@@ -182,12 +241,14 @@
                     }
 
                     tile_view.AddNumberedWidget (tile);
-                    tile.Show ();
                 }
+
+                tile_view.ShowAll ();
             }
 
             private void OnTileActivated (object sender, EventArgs args)
             {
+                (sender as Button).Relief = ReliefStyle.Normal;
                 RecentTrack track = widget_track_map [sender];
                 lastfm.Actions.CurrentArtist = track.Artist;
                 lastfm.Actions.CurrentAlbum = track.Album;
@@ -207,6 +268,9 @@
 
                 menu.ShowAll (); 
                 menu.Popup (null, null, null, 0, Gtk.Global.CurrentEventTime);
+                menu.Deactivated += delegate {
+                    (sender as Button).Relief = ReliefStyle.None;
+                };
             }
         }
     }

Modified: trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Widgets/RoundedFrame.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Widgets/RoundedFrame.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Widgets/RoundedFrame.cs	Sat Mar 29 07:37:47 2008
@@ -1,8 +1,9 @@
 //
 // RoundedFrame.cs
 //
-// Author:
+// Authors:
 //   Aaron Bockover <abockover novell com>
+//   Gabriel Burt <gburt novell com>
 //
 // Copyright (C) 2008 Novell, Inc.
 //
@@ -85,6 +86,7 @@
             int width = requisition.Width;
             int height = requisition.Height;
                 
+            child.SizeRequest ();
             child.GetSizeRequest (out width, out height);
             if (width == -1 || height == -1) {
                 width = height = 80;

Modified: trunk/banshee/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs
==============================================================================
--- trunk/banshee/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs	(original)
+++ trunk/banshee/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs	Sat Mar 29 07:37:47 2008
@@ -108,7 +108,7 @@
         
         public void UpdateNetworkState (bool connected)
         {
-            Log.DebugFormat ("Changing Audioscrobbler connected state: {0}", connected ? "connected" : "disconnected");
+            Log.DebugFormat ("Audioscrobbler state: {0}", connected ? "connected" : "disconnected");
             this.connected = connected;
         }
 
@@ -169,10 +169,9 @@
 
         private void StateTransitionHandler (object o, ElapsedEventArgs e)
         {
-            Hyena.Log.DebugFormat ("State transition handler running; state: {0}, connected: {1}", state, connected);
+            Log.DebugFormat ("State transition handler running; state: {0}, connected: {1}", state, connected);
             
-            /* if we're not connected, don't bother doing anything
-             * involving the network. */
+            // if we're not connected, don't bother doing anything involving the network.
             if (!connected) {
                 return;
             }
@@ -182,9 +181,7 @@
                 hard_failures = 0;
             }
             
-            
-
-            /* and address changes in our engine state */
+            // and address changes in our engine state
             switch (state) {
             case State.Idle:
                 if (LastfmCore.Account.UserName != null &&
@@ -218,7 +215,7 @@
             case State.WaitingForResponse:
             case State.WaitingForRequestStream:
             case State.WaitingForHandshakeResp:
-                /* nothing here */
+                // nothing here
                 break;
             }
         }
@@ -236,9 +233,9 @@
         {
             int num_tracks_transmitted;
 
-            /* save here in case we're interrupted before we complete
-             * the request.  we save it again when we get an OK back
-             * from the server */
+            // save here in case we're interrupted before we complete
+            // the request.  we save it again when we get an OK back
+            // from the server
             queue.Save ();
 
             next_interval = DateTime.MinValue;
@@ -464,7 +461,7 @@
             }
             
             if (success == true) {
-                Hyena.Log.Debug ("Audioscrobbler sign-on succeeded", "Session ID received"); 
+                Log.Debug ("Audioscrobbler sign-on succeeded", "Session ID received"); 
                 session_id = sr.ReadLine ().Trim ();
                 now_playing_url = sr.ReadLine ().Trim ();
                 post_url = sr.ReadLine ().Trim ();

Modified: trunk/banshee/src/Libraries/Lastfm/Lastfm/RadioConnection.cs
==============================================================================
--- trunk/banshee/src/Libraries/Lastfm/Lastfm/RadioConnection.cs	(original)
+++ trunk/banshee/src/Libraries/Lastfm/Lastfm/RadioConnection.cs	Sat Mar 29 07:37:47 2008
@@ -272,7 +272,8 @@
                     // Set us as connecting, assuming the connection attempt wasn't changed out from under us
                     if (ParseHandshake (new StreamReader (stream).ReadToEnd ()) && session != null) {
                         State = ConnectionState.Connected;
-                        Log.Debug (String.Format ("Logged into Last.fm as {0}", LastfmCore.Account.UserName), null);
+                        Log.Debug (String.Format ("Logged into Last.fm as {0} ({1}subscriber)",
+                            LastfmCore.Account.UserName, subscriber ? null : "not a "), null);
                         return;
                     }
                 } catch (Exception e) {
@@ -288,7 +289,6 @@
 
         private bool ParseHandshake (string content) 
         {
-            Log.Debug ("Got Last.fm Handshake Response", content);
             string [] lines = content.Split (new Char[] {'\n'});
             foreach (string line in lines) {
                 string [] opts = line.Split (new Char[] {'='});



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