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() {
+ }
+
+}