Diff for revision 1232

=== modified file 'src/com/pugh/sockso/gui/MusicPanel.java'
--- src/com/pugh/sockso/gui/MusicPanel.java	2008-09-13 23:23:32 +0000
+++ src/com/pugh/sockso/gui/MusicPanel.java	2010-02-20 14:57:53 +0000
@@ -78,6 +78,8 @@
             getUserPlaylistsPanel( userPlaylists )
         );
         final JTabbedPane tabbedPane = getTabbedPane( musicTree, playlistsPanel );
+
+        musicTree.init();

         setLayout( new BorderLayout() );
         add(

=== modified file 'src/com/pugh/sockso/gui/MusicTree.java'
--- src/com/pugh/sockso/gui/MusicTree.java	2008-09-28 21:08:07 +0000
+++ src/com/pugh/sockso/gui/MusicTree.java	2010-02-20 14:57:53 +0000
@@ -34,6 +34,8 @@
 import java.awt.dnd.DragSourceDragEvent;

 import javax.swing.JTree;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeExpansionListener;
 import javax.swing.tree.TreeNode;
 import javax.swing.tree.TreePath;
 import javax.swing.tree.DefaultTreeModel;
@@ -41,12 +43,15 @@

 import org.apache.log4j.Logger;

-public class MusicTree extends JTree implements DragSourceListener, DragGestureListener, CollectionManagerListener {
+public class MusicTree extends JTree implements DragSourceListener, DragGestureListener, CollectionManagerListener, TreeExpansionListener {

     private static Logger log = Logger.getLogger( MusicTree.class );

     private DragSource dragSource;
-    private Database db;
+
+    private final Database db;
+    private final CollectionManager cm;
+    private final Resources r;

     /**
      *  constructor
@@ -62,7 +67,18 @@
         super( new MusicTreeNode(new Collection()) );

         this.db = db;
+        this.cm = cm;
+        this.r = r;

+    }
+
+    /**
+     *  Initialise the music tree
+     *
+     */
+
+    public void init() {
+
         cm.addCollectionManagerListener( this );

         dragSource = new DragSource();
@@ -70,93 +86,234 @@

         setShowsRootHandles( true );
         setCellRenderer( new MusicTreeCellRenderer(r) );
-        totalRefresh();
-
-    }
-
-    /**
-     *  does a complete refresh of the tree, wipes the nodes
-     *  and builds the whole thing from scratch.  this is done in
-     *  a seperate thread not to hold things up
-     *
-     */
-
-    private void totalRefresh() {
-        new Thread( new Runnable() {
-            public void run() {
-
-                ResultSet rs = null;
-                PreparedStatement st = null;
-
-                try {
-
-                    final DefaultTreeModel model = (DefaultTreeModel) MusicTree.this.getModel();
-                    final DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
-                    final TreeNode parentNode;
-
-                    root.removeAllChildren();
-
-                    final String sql = Track.getSelectFromSql() +
-                                 " order by ar.name asc, al.name asc, t.track_no asc, t.name asc ";
-                    st = db.prepare( sql );
-                    rs = st.executeQuery();
-
-                    String artist = Utils.getRandomString(10), album = Utils.getRandomString(10);
-                    MusicTreeNode artistNode = null, albumNode = null;
-
-                    while ( rs.next() ) {
-
-                        final boolean artistChanged =  !artist.equals(rs.getString("artistName"));
-                        final boolean albumChanged = artistChanged || !album.equals(rs.getString("albumName"));
-
-                        // do we need to create a new artist node?
-                        if ( artistChanged ) {
-                            artistNode = new MusicTreeNode(
-                                new Artist( rs.getInt("artistId"), rs.getString("artistName") )
-                            );
-                            root.add( artistNode );
-                        }
-
-                        // do we need to create a new album node?
-                        if ( albumChanged ) {
-                            albumNode = new MusicTreeNode(
-                                new Album(
-                                    new Artist( rs.getInt("artistId"), rs.getString("artistName") ),
-                                    rs.getInt("albumId"), rs.getString("albumName") )
-                            );
-                            artistNode.add( albumNode );
-                        }
-
-                        artist = rs.getString( "artistName" );
-                        album = rs.getString( "albumName" );
-
-                        // create the track node
-                        final Artist artistObj = new Artist( rs.getInt("artistId"), rs.getString("artistName") );
-                        final MusicTreeNode trackNode = new MusicTreeNode(
-                            Track.createFromResultSet(rs)
-                        );
-                        albumNode.add( trackNode );
-
-                    }
-
-                    model.reload( root );
-
-                    // expand root node
-                    expandPath( new TreePath(root.getPath()) );
-
-                }
-
-                catch ( SQLException e ) {
-                    log.error( "Error Refreshing: " + e.getMessage() );
-                }
-
-                finally {
-                    Utils.close( rs );
-                    Utils.close( st );
-                }
-
-            }
-        }).start();
+
+        refresh();
+
+        addTreeExpansionListener( this );
+
+    }
+
+    /**
+     *  Handler for when tree nodes are collapsed
+     *
+     *  @param evt
+     *
+     */
+
+    public void treeCollapsed( final TreeExpansionEvent evt ) {}
+
+    /**
+     *  Handler for when tree nodes are expanded, we may need to load their children
+     *
+     *  @param evt
+     *
+     */
+
+    public void treeExpanded( final TreeExpansionEvent evt ) {
+
+        final TreePath path = evt.getPath();
+
+        try {
+
+            final MusicTreeNode node = (MusicTreeNode) path.getLastPathComponent();
+            final MusicItem item = (MusicItem) node.getUserObject();
+
+            if ( item.getType().equals(MusicItem.ARTIST) ) {
+                fillArtistNode( node, item );
+            }
+
+            else if ( item.getType().equals(MusicItem.ALBUM) ) {
+                fillAlbumNode( node, item );
+            }
+
+        }
+
+        catch ( final SQLException e ) {
+            log.debug( e );
+        }
+
+    }
+
+    /**
+     *  Fills the collection node with all the artists
+     *
+     *  @param root
+     *
+     *  @throws SQLException
+     *
+     */
+
+    private void fillCollectionNode( final DefaultMutableTreeNode root ) throws SQLException {
+
+        ResultSet rs = null;
+        PreparedStatement st = null;
+
+        try {
+
+            final String sql = " select ar.id, ar.name " +
+                               " from artists ar " +
+                               " order by ar.name asc ";
+            st = db.prepare( sql );
+            rs = st.executeQuery();
+
+            while ( rs.next() ) {
+                final Artist artist = new Artist( rs.getInt("id"), rs.getString("name") );
+                final MusicTreeNode node = new MusicTreeNode( artist );
+                node.add( new DefaultMutableTreeNode() );
+                root.add( node );
+            }
+
+            reloadNode( root );
+
+        }
+
+        finally {
+            Utils.close( rs );
+            Utils.close( st );
+        }
+
+    }
+
+    /**
+     *  Fills an artist node with albums
+     *
+     *  @param node
+     *  @param item
+     *
+     */
+
+    private void fillArtistNode( final MusicTreeNode node, final MusicItem item ) throws SQLException {
+
+        ResultSet rs = null;
+        PreparedStatement st = null;
+
+        try {
+
+            final Artist artist = new Artist( item.getId(), item.getName() );
+            final String sql = " select al.id, al.name " +
+                               " from albums al " +
+                               " where al.artist_id = ? ";
+
+            st = db.prepare( sql );
+            st.setInt( 1, item.getId() );
+            rs = st.executeQuery();
+
+            node.removeAllChildren();
+
+            while ( rs.next() ) {
+                final Album album = new Album( artist, rs.getInt("id"), rs.getString("name") );
+                final MusicTreeNode child = new MusicTreeNode( album );
+                child.add( new DefaultMutableTreeNode() );
+                node.add( child );
+            }
+
+            reloadNode( node );
+
+        }
+
+        finally {
+            rs.close();
+            st.close();
+        }
+
+    }
+
+    /**
+     *  Fills an album node with albums
+     *
+     *  @param node
+     *  @param item
+     *
+     */
+
+    private void fillAlbumNode( final MusicTreeNode node, final MusicItem item ) throws SQLException {
+
+        ResultSet rs = null;
+        PreparedStatement st = null;
+
+        try {
+
+            final String sql = Track.getSelectFromSql() +
+                               " where t.album_id = ? ";
+
+            st = db.prepare( sql );
+            st.setInt( 1, item.getId() );
+            rs = st.executeQuery();
+
+            node.removeAllChildren();
+
+            while ( rs.next() ) {
+                final Track track = Track.createFromResultSet( rs );
+                final MusicTreeNode child = new MusicTreeNode( track );
+                node.add( child );
+            }
+
+            reloadNode( node );
+
+        }
+
+        finally {
+            rs.close();
+            st.close();
+        }
+
+    }
+
+    /**
+     *  Refreshes the tree with the artists from the collection
+     *
+     */
+
+    public void refresh() {
+
+        try {
+
+            final DefaultTreeModel model = (DefaultTreeModel) this.getModel();
+            final DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
+
+            root.removeAllChildren();
+
+            fillCollectionNode( root );
+            reloadNode( root );
+            expandNode( root );
+
+        }
+
+        catch ( final SQLException e ) {
+            log.error( e );
+        }
+
+    }
+
+    /**
+     *  Expands a tree node and fires the expanded event
+     *
+     *  @param node
+     *
+     */
+
+    protected void expandNode( final TreeNode node ) {
+
+        final TreePath path = new TreePath( ((DefaultMutableTreeNode)node).getPath() );
+
+        expandPath( path );
+        fireTreeExpanded( path );
+
+    }
+
+    /**
+     *  Informs the tree model that a node has changed
+     *
+     *  @param node
+     *
+     */
+
+    private void reloadNode( final TreeNode node ) {
+
+        final DefaultTreeModel model = (DefaultTreeModel) this.getModel();
+
+        model.nodeStructureChanged( node );
+
     }

     /**
@@ -169,8 +326,9 @@

     public void collectionManagerChangePerformed( int type, String message ) {

-        if ( type == CollectionManagerListener.UPDATE_COMPLETE )
-            totalRefresh();
+        if ( type == CollectionManagerListener.UPDATE_COMPLETE ) {
+            refresh();
+        }

     }

@@ -185,7 +343,7 @@

         try {

-            MusicTreeNode node = (MusicTreeNode) getSelectionPath().getLastPathComponent();
+            final MusicTreeNode node = (MusicTreeNode) getSelectionPath().getLastPathComponent();

             dragSource.startDrag(
                 evt, DragSource.DefaultMoveDrop,
@@ -193,7 +351,8 @@
             );

         }
-        catch ( ClassCastException e ) {
+
+        catch ( final ClassCastException e ) {
             log.error( "Error starting drag: " + e.getMessage() );
         }


=== modified file 'src/com/pugh/sockso/gui/MusicTreeCellRenderer.java'
--- src/com/pugh/sockso/gui/MusicTreeCellRenderer.java	2008-09-28 21:08:07 +0000
+++ src/com/pugh/sockso/gui/MusicTreeCellRenderer.java	2010-02-20 14:57:53 +0000
@@ -1,11 +1,3 @@
-/*
- * MusicTreeCellRenderer.java
- *
- * Created on May 16, 2007, 10:43:31 PM
- *
- * A custom renderer for the music tree
- *
- */

 package com.pugh.sockso.gui;

@@ -20,16 +12,45 @@

 import org.apache.log4j.Logger;

+/**
+ *  A custom renderer for the music tree
+ *
+ */
+
 public class MusicTreeCellRenderer extends DefaultTreeCellRenderer {

     private static Logger log = Logger.getLogger( MusicTreeCellRenderer.class );

-    private Resources r;
-
+    private final Resources r;
+
+    /**
+     *  Constructor
+     *
+     *  @param r
+     *
+     */
+
     public MusicTreeCellRenderer( Resources r ) {
+
         this.r = r;
+
     }
-
+
+    /**
+     *  Renders and returns the component to display in the tree
+     *
+     *  @param tree
+     *  @param value
+     *  @param sel
+     *  @param expanded
+     *  @param leaf
+     *  @param row
+     *  @param hasFocus
+     *
+     *  @return
+     *
+     */
+
     @Override
     public Component getTreeCellRendererComponent(
                         JTree tree,
@@ -42,25 +63,30 @@

         super.getTreeCellRendererComponent( tree, value, sel, expanded, leaf, row, hasFocus );

-        try {
-
-            MusicTreeNode node = (MusicTreeNode) value;
-            MusicItem item = (MusicItem) node.getUserObject();
-            String type = item.getType();
-
-            if ( type.equals(MusicItem.COLLECTION) )
+        if ( value.getClass().equals(MusicTreeNode.class) ) {
+
+            // @TODO move icon fetching to MusicItem class
+
+            final MusicTreeNode node = (MusicTreeNode) value;
+            final MusicItem item = (MusicItem) node.getUserObject();
+            final String type = item.getType();
+
+            if ( type.equals(MusicItem.COLLECTION) ) {
                 setIcon( new ImageIcon(r.getImage("icons/16x16/collection.png")) );
-            else if ( type.equals(MusicItem.ARTIST) )
+            }
+
+            else if ( type.equals(MusicItem.ARTIST) ) {
                 setIcon( new ImageIcon(r.getImage("icons/16x16/artist.png")) );
-            else if ( type.equals(MusicItem.ALBUM) )
+            }
+
+            else if ( type.equals(MusicItem.ALBUM) ) {
                 setIcon( new ImageIcon(r.getImage("icons/16x16/album.png")) );
-            else if ( type.equals(MusicItem.TRACK) )
+            }
+
+            else if ( type.equals(MusicItem.TRACK) ) {
                 setIcon( new ImageIcon(r.getImage("icons/16x16/tracks.png")) );
-
-        }
-
-        catch ( ClassCastException e ) {
-            log.error( e.getMessage() );
+            }
+
         }

         return this;

=== added file 'test-data/fixtures/musicTree.fix'
--- test-data/fixtures/musicTree.fix	1970-01-01 00:00:00 +0000
+++ test-data/fixtures/musicTree.fix	2010-02-20 14:57:53 +0000
@@ -0,0 +1,10 @@
+
+collection:1,/music
+
+artists:1,A Artist,now(),A Artist
+artists:2,Empty Artist,now(),Empty Artist
+
+albums:1,1,A Album,now()
+albums:2,1,Empty Album,now()
+
+tracks:1,1,1,My Track,/music/track.mp3,180,now(),1,1

=== added file 'test/com/pugh/sockso/gui/MusicTreeCellRendererTest.java'
--- test/com/pugh/sockso/gui/MusicTreeCellRendererTest.java	1970-01-01 00:00:00 +0000
+++ test/com/pugh/sockso/gui/MusicTreeCellRendererTest.java	2010-02-20 14:57:53 +0000
@@ -0,0 +1,43 @@
+
+package com.pugh.sockso.gui;
+
+import com.pugh.sockso.music.MusicItem;
+import com.pugh.sockso.resources.FileResources;
+import com.pugh.sockso.tests.SocksoTestCase;
+
+import javax.swing.JTree;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+public class MusicTreeCellRendererTest extends SocksoTestCase {
+
+    private MusicTreeCellRenderer rend;
+
+    @Override
+    public void setUp() {
+        rend = new MusicTreeCellRenderer( new FileResources() );
+    }
+
+    public void testConstructor() {
+        assertNotNull( new MusicTreeCellRenderer(null) );
+    }
+
+    public void testRenderingMusicItems() {
+        String[] types = new String[] {
+            MusicItem.COLLECTION,
+            MusicItem.ARTIST,
+            MusicItem.ALBUM,
+            MusicItem.TRACK,
+        };
+        for ( String type : types ) {
+            MusicItem item = new MusicItem( type, 1, "foo" );
+            rend.getTreeCellRendererComponent( new JTree(), new MusicTreeNode(item), true,true,false,0,true );
+        }
+    }
+
+    public void testRenderingNonMusicItemNodeHandledOk() {
+        TreeNode node = new DefaultMutableTreeNode();
+        rend.getTreeCellRendererComponent( new JTree(), node, true,true,false,0,true );
+    }
+
+}

=== added file 'test/com/pugh/sockso/gui/MusicTreeTest.java'
--- test/com/pugh/sockso/gui/MusicTreeTest.java	1970-01-01 00:00:00 +0000
+++ test/com/pugh/sockso/gui/MusicTreeTest.java	2010-02-20 14:57:53 +0000
@@ -0,0 +1,80 @@
+
+package com.pugh.sockso.gui;
+
+import com.pugh.sockso.music.CollectionManager;
+import com.pugh.sockso.resources.FileResources;
+import com.pugh.sockso.resources.Resources;
+import com.pugh.sockso.tests.SocksoTestCase;
+import com.pugh.sockso.tests.TestDatabase;
+
+import javax.swing.JTree;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import static org.easymock.EasyMock.*;
+
+public class MusicTreeTest extends SocksoTestCase {
+
+    private MusicTree t;
+    private DefaultTreeModel model;
+    private DefaultMutableTreeNode root;
+
+    @Override
+    public void setUp() throws Exception {
+        Resources r = new FileResources();
+        CollectionManager cm = createNiceMock( CollectionManager.class );
+        TestDatabase db = new TestDatabase();
+        db.fixture( "musicTree" );
+        t = new MusicTree( db, cm, r );
+        t.init();
+        model = (DefaultTreeModel) t.getModel();
+        root = (DefaultMutableTreeNode) model.getRoot();
+    }
+
+    public void testConstructorDoesNoWork() {
+        new MusicTree( null, null, null );
+    }
+
+    public void testTreeCreatedWhenInitialised() {
+        assertEquals( 2, root.getChildCount() );
+    }
+
+    public void testArtistNodesHaveDummyNodesWhenCreated() {
+        assertEquals( 1, root.getChildAt(0).getChildCount() );
+    }
+
+    public void testAlbumsHaveDummyNodeWhenCreated() {
+        TreeNode artist = root.getChildAt( 0 );
+        t.expandNode( artist );
+        assertEquals( 1, artist.getChildAt(0).getChildCount() );
+        assertEquals( 1, artist.getChildAt(1).getChildCount() );
+    }
+
+    public void testAlbumsLoadedWhenArtistExpanded() {
+        TreeNode artist = root.getChildAt( 0 );
+        assertEquals( 1, artist.getChildCount() );
+        t.expandNode( artist );
+        assertEquals( 2, artist.getChildCount() );
+    }
+
+    public void testTracksLoadedWhenAlbumExpanded() {
+        TreeNode artist = root.getChildAt( 0 );
+        t.expandNode( artist );
+        TreeNode album = artist.getChildAt( 0 );
+        assertEquals( 1, album.getChildCount() );
+        t.expandNode( album );
+        assertEquals( 1, album.getChildCount() );
+    }
+
+    public void testArtistNodesNotExpandableIfTheyHaveNoAlbums() {
+    }
+
+    public void testAlbumNodesNotExpandableIfTheyHaveNoTracks() {
+    }
+
+    public void testRefreshingTreeAgainCreatesTreeWithJustArtists() {
+    }
+
+}