/* * Jajuk * Copyright (C) The Jajuk Team * http://jajuk.info * * 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 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. * */ package org.jajuk.ui.views; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.List; import java.util.Properties; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import net.miginfocom.swing.MigLayout; import org.jajuk.base.Album; import org.jajuk.base.Artist; import org.jajuk.base.File; import org.jajuk.base.Genre; import org.jajuk.base.Item; import org.jajuk.base.Track; import org.jajuk.base.TrackComparator.TrackComparatorType; import org.jajuk.base.TrackManager; import org.jajuk.base.Year; import org.jajuk.events.JajukEvent; import org.jajuk.events.JajukEvents; import org.jajuk.events.ObservationManager; import org.jajuk.services.players.QueueModel; import org.jajuk.services.players.StackItem; import org.jajuk.ui.actions.ActionManager; import org.jajuk.ui.actions.JajukActions; import org.jajuk.ui.helpers.FontManager; import org.jajuk.ui.helpers.FontManager.JajukFont; import org.jajuk.ui.helpers.JajukMouseAdapter; import org.jajuk.ui.helpers.TreeRootElement; import org.jajuk.ui.helpers.TreeTransferHandler; import org.jajuk.ui.perspectives.PerspectiveManager; import org.jajuk.ui.widgets.InformationJPanel; import org.jajuk.util.Conf; import org.jajuk.util.Const; import org.jajuk.util.IconLoader; import org.jajuk.util.JajukIcons; import org.jajuk.util.Messages; import org.jajuk.util.UtilGUI; import org.jajuk.util.error.JajukException; import org.jajuk.util.log.Log; import org.pushingpixels.substance.api.renderers.SubstanceDefaultTreeCellRenderer; /** * Logical tree view. */ public class TracksTreeView extends AbstractTreeView implements ActionListener { /** Generated serialVersionUID. */ private static final long serialVersionUID = 1L; /** Sorting method selection combo. * <br>DO NOT PARAMETRIZE THIS COMBO, 1.6 COMPILATION WOULD FAIL*/ private JComboBox jcbSort; /* * (non-Javadoc) * * @see org.jajuk.ui.IView#getDesc() */ @Override public String getDesc() { return Messages.getString("TracksTreeView.0"); } /** * Constructor. */ public TracksTreeView() { super(); } /* * (non-Javadoc) * * @see org.jajuk.ui.IView#display() */ @Override public void initUI() { super.initUI(); // ComboBox sort JLabel jlSort = new JLabel(Messages.getString("Sort")); jcbSort = new JComboBox(); jcbSort.addItem(Messages.getHumanPropertyName(Const.XML_GENRE)); // sort by // Genre/Artist/Album jcbSort.addItem(Messages.getHumanPropertyName(Const.XML_ARTIST)); // sort by // Artist/Album jcbSort.addItem(Messages.getHumanPropertyName(Const.XML_ALBUM)); // sort by Album jcbSort.addItem(Messages.getHumanPropertyName(Const.XML_YEAR)); // sort by Year jcbSort.addItem(Messages.getString("TracksTreeView.35")); // sort by // Discovery Date jcbSort.addItem(Messages.getHumanPropertyName(Const.XML_TRACK_RATE)); // sort by rate jcbSort.addItem(Messages.getHumanPropertyName(Const.XML_TRACK_HITS)); // sort by hits // Load stored index, reset to index 0 in case of out of bounds (can happen after a version // upgrade) if (Conf.getInt(Const.CONF_LOGICAL_TREE_SORT_ORDER) >= jcbSort.getItemCount()) { Conf.setProperty(Const.CONF_LOGICAL_TREE_SORT_ORDER, "0"); } jcbSort.setSelectedIndex(Conf.getInt(Const.CONF_LOGICAL_TREE_SORT_ORDER)); jcbSort.setActionCommand(JajukEvents.LOGICAL_TREE_SORT.toString()); jcbSort.addActionListener(this); // Album details final JMenuItem jmiShowAlbumDetails = new JMenuItem( ActionManager.getAction(JajukActions.SHOW_ALBUM_DETAILS)); jmiShowAlbumDetails.putClientProperty(Const.DETAIL_SELECTION, alSelected); top = new TreeRootElement(Messages.getString("TracksTreeView.27")); // Register on the list for subject we are interested in ObservationManager.register(this); // populate the tree populateTree(); // create tree createTree(false); jtree.setCellRenderer(new TracksTreeCellRenderer()); /** * CAUTION ! we register several listeners against this tree Swing can't * ensure the order where listeners will treat them so don't rely on the * mouse listener to get correct selection from selection listener */ // Tree selection listener to detect a selection jtree.addTreeSelectionListener(new TracksTreeSelectionListener()); // Listen for double click jtree.addMouseListener(new TracksMouseAdapter(jmiShowAlbumDetails)); // Expansion analyze to keep expended state jtree.addTreeExpansionListener(new TracksTreeExpansionListener()); jtree.setAutoscrolls(true); // DND support jtree.setTransferHandler(new TreeTransferHandler(jtree)); jtree.setDragEnabled(true); jspTree = new JScrollPane(jtree); jspTree.setBorder(BorderFactory.createEmptyBorder(0, 1, 0, 0)); setLayout(new MigLayout("ins 3", "[][grow][][]", "[][grow]")); add(jlSort, "left,gapx 5::"); add(jcbSort, "grow,left"); add(jtbSync, "right"); add(jbCollapseAll, "right,wrap"); add(jspTree, "grow,span"); expand(); } /** * Fill the tree. */ @Override public void populateTree() { // Use a refreshing flag, not a 'synchronized' here (see deadlock, bug #1756 (Deadlock in AbstractTreeView and PerspectiveManager) if (refreshing) { Log.debug("Tree view already refreshing. Leaving."); return; } try { refreshing = true; // delete previous tree top.removeAllChildren(); TrackComparatorType comparatorType = TrackComparatorType.values()[Conf .getInt(Const.CONF_LOGICAL_TREE_SORT_ORDER)]; if (comparatorType == TrackComparatorType.GENRE_ARTIST_ALBUM) { populateTreeByGenre(); }// Artist/album else if (comparatorType == TrackComparatorType.ARTIST_ALBUM) { populateTreeByArtist(); } // Album else if (comparatorType == TrackComparatorType.ALBUM) { populateTreeByAlbum(); } // Year / album else if (comparatorType == TrackComparatorType.YEAR_ALBUM) { populateTreeByYear(); } // discovery date / album else if (comparatorType == TrackComparatorType.DISCOVERY_ALBUM) { populateTreeByDiscovery(); } // Rate / album else if (comparatorType == TrackComparatorType.RATE_ALBUM) { populateTreeByRate(); } // Hits / album else if (comparatorType == TrackComparatorType.HITS_ALBUM) { populateTreeByHits(); } // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6472844 for a // small memory leak that is caused here... if (jtree != null && jtree.getModel() != null) { ((DefaultTreeModel) (jtree.getModel())).reload(); } } finally { refreshing = false; } } /** * Fill the tree by genre. */ @SuppressWarnings("unchecked") public void populateTreeByGenre() { List<Track> tracks = TrackManager.getInstance().getTracks(); Collections.sort(tracks, TrackManager.getInstance().getComparator()); for (Track track : tracks) { if (!track.shouldBeHidden()) { GenreNode genreNode = null; Genre genre = track.getGenre(); ArtistNode artistNode = null; Artist artist = track.getArtist(); AlbumNode albumNode = null; Album album = track.getAlbum(); // create genre { Enumeration<GenreNode> e = top.children(); boolean b = false; while (e.hasMoreElements()) { // check the genre doesn't // already exist GenreNode sn = e.nextElement(); if (sn.getGenre().equals(genre)) { b = true; genreNode = sn; break; } } if (!b) { genreNode = new GenreNode(genre); top.add(genreNode); } } if (genreNode == null) { continue; } // create artist { Enumeration<ArtistNode> e2 = genreNode.children(); boolean b = false; while (e2.hasMoreElements()) { // check if the artist doesn't // already exist ArtistNode an = e2.nextElement(); if (an.getArtist().equals(artist)) { b = true; artistNode = an; break; } } if (!b) { artistNode = new ArtistNode(artist); genreNode.add(artistNode); } } // create album if (artistNode == null) { continue; } Enumeration<AlbumNode> e3 = artistNode.children(); boolean b = false; while (e3.hasMoreElements()) { AlbumNode an = e3.nextElement(); if (an.getAlbum().equals(album)) { b = true; albumNode = an; break; } } if (!b) { albumNode = new AlbumNode(album); artistNode.add(albumNode); } // create track assert albumNode != null; albumNode.add(new TrackNode(track)); } } } /** * Fill the tree by artist. */ @SuppressWarnings("unchecked") public void populateTreeByArtist() { List<Track> tracks = TrackManager.getInstance().getTracks(); Collections.sort(tracks, TrackManager.getInstance().getComparator()); for (Track track : tracks) { if (!track.shouldBeHidden()) { ArtistNode artistNode = null; Artist artist = track.getArtist(); AlbumNode albumNode = null; Album album = track.getAlbum(); // create artist { Enumeration<ArtistNode> e = top.children(); boolean b = false; while (e.hasMoreElements()) { // check if the artist doesn't // already exist ArtistNode an = e.nextElement(); if (an.getArtist().equals(artist)) { b = true; artistNode = an; break; } } if (!b) { artistNode = new ArtistNode(artist); top.add(artistNode); } } if (artistNode == null) { continue; } // create album Enumeration<AlbumNode> e2 = artistNode.children(); boolean b = false; while (e2.hasMoreElements()) { // check if the album doesn't // already exist AlbumNode an = e2.nextElement(); if (an.getAlbum().equals(album)) { b = true; albumNode = an; break; } } if (!b) { albumNode = new AlbumNode(album); artistNode.add(albumNode); } // create track if (albumNode != null) { albumNode.add(new TrackNode(track)); } } } } /** * Fill the tree by year. */ @SuppressWarnings("unchecked") public void populateTreeByYear() { List<Track> tracks = TrackManager.getInstance().getTracks(); Collections.sort(tracks, TrackManager.getInstance().getComparator()); for (Track track : tracks) { if (!track.shouldBeHidden()) { YearNode yearNode = null; AlbumNode albumNode = null; Album album = track.getAlbum(); Year year = track.getYear(); // create Year { Enumeration<YearNode> e = top.children(); boolean b = false; // check if the artist doesn't already exist while (e.hasMoreElements()) { YearNode yn = e.nextElement(); if (yn.getYear().equals(year)) { b = true; yearNode = yn; break; } } if (!b) { yearNode = new YearNode(year); top.add(yearNode); } } if (yearNode == null) { continue; } // create album Enumeration<AlbumNode> e1 = yearNode.children(); boolean b = false; while (e1.hasMoreElements()) { // check if the album doesn't // already exist AlbumNode an = e1.nextElement(); if (an.getAlbum().equals(album)) { b = true; albumNode = an; break; } } if (!b) { albumNode = new AlbumNode(album); yearNode.add(albumNode); } // create track if (albumNode != null) { albumNode.add(new TrackNode(track)); } } } } /** * Fill the tree. */ public void populateTreeByAlbum() { List<Track> tracks = TrackManager.getInstance().getTracks(); Collections.sort(tracks, TrackManager.getInstance().getComparator()); for (Track track : tracks) { if (!track.shouldBeHidden()) { addTrackAndAlbum(top, track); } } } /** * Fill the tree by discovery. */ public void populateTreeByDiscovery() { List<Track> tracks = TrackManager.getInstance().getTracks(); Collections.sort(tracks, TrackManager.getInstance().getComparator()); // Create separator nodes DefaultMutableTreeNode nodeWeekly = new DiscoveryDateNode( Messages.getString("TracksTreeView.36")); DefaultMutableTreeNode nodeMontly = new DiscoveryDateNode( Messages.getString("TracksTreeView.37")); DefaultMutableTreeNode nodeThreeMontly = new DiscoveryDateNode( Messages.getString("TracksTreeView.44")); DefaultMutableTreeNode nodeSixMontly = new DiscoveryDateNode( Messages.getString("TracksTreeView.38")); DefaultMutableTreeNode nodeYearly = new DiscoveryDateNode( Messages.getString("TracksTreeView.40")); DefaultMutableTreeNode nodeTwoYearly = new DiscoveryDateNode( Messages.getString("TracksTreeView.41")); DefaultMutableTreeNode nodeFiveYearly = new DiscoveryDateNode( Messages.getString("TracksTreeView.42")); DefaultMutableTreeNode nodeTenYearly = new DiscoveryDateNode( Messages.getString("TracksTreeView.43")); DefaultMutableTreeNode nodeOlder = new DiscoveryDateNode( Messages.getString("TracksTreeView.39")); // Add separator nodes top.add(nodeWeekly); top.add(nodeMontly); top.add(nodeThreeMontly); top.add(nodeSixMontly); top.add(nodeYearly); top.add(nodeTwoYearly); top.add(nodeFiveYearly); top.add(nodeTenYearly); top.add(nodeOlder); Date today = new Date(); // Sort tracks into these categories for (Track track : tracks) { if (track.shouldBeHidden()) { continue; } // less than one week ? long diff = today.getTime() - track.getDiscoveryDate().getTime(); if (diff < 604800000l) { addTrackAndAlbum(nodeWeekly, track); } else if (diff < 2628000000l) { addTrackAndAlbum(nodeMontly, track); } else if (diff < 7884000000l) { addTrackAndAlbum(nodeThreeMontly, track); } else if (diff < 15768000000l) { addTrackAndAlbum(nodeSixMontly, track); } else if (diff < 31536000000l) { addTrackAndAlbum(nodeYearly, track); } else if (diff < 63072000000l) { addTrackAndAlbum(nodeTwoYearly, track); } else if (diff < 157680000000l) { addTrackAndAlbum(nodeFiveYearly, track); } else if (diff < 315360000000l) { addTrackAndAlbum(nodeTenYearly, track); } else { addTrackAndAlbum(nodeOlder, track); } } } /** * Fill the tree by Rate. */ public void populateTreeByRate() { List<Track> tracks = TrackManager.getInstance().getTracks(); Collections.sort(tracks, TrackManager.getInstance().getComparator()); for (Track track : tracks) { if (!track.shouldBeHidden()) { addTrackAndAlbum(top, track); } } } /** * Fill the tree by Hits. */ public void populateTreeByHits() { List<Track> tracks = TrackManager.getInstance().getTracks(); Collections.sort(tracks, TrackManager.getInstance().getComparator()); for (Track track : tracks) { if (!track.shouldBeHidden()) { addTrackAndAlbum(top, track); } } } /** * Utility method used by populateByDiscovery method. * * @param node * @param track */ @SuppressWarnings("unchecked") private void addTrackAndAlbum(DefaultMutableTreeNode node, Track track) { boolean bAlbumExists = false; AlbumNode currentAlbum = null; Enumeration<AlbumNode> e = node.children(); while (e.hasMoreElements()) { AlbumNode an = e.nextElement(); if (an.getAlbum().equals(track.getAlbum())) { bAlbumExists = true; currentAlbum = an; break; } } if (!bAlbumExists) { currentAlbum = new AlbumNode(track.getAlbum()); node.add(currentAlbum); } // create track if (currentAlbum != null) { currentAlbum.add(new TrackNode(track)); } } /* * (non-Javadoc) * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ @Override public void actionPerformed(final ActionEvent e) { if (e.getSource() == jcbSort) { UtilGUI.waiting(); SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>() { @Override public Void doInBackground() { // Set comparator Conf.setProperty(Const.CONF_LOGICAL_TREE_SORT_ORDER, Integer.toString(jcbSort.getSelectedIndex())); populateTree(); return null; } @Override public void done() { SwingUtilities.updateComponentTreeUI(jtree); UtilGUI.stopWaiting(); } }; sw.execute(); } } /** * Manages auto-expand. */ @Override void expand() { // expand all for (int i = 0; i < jtree.getRowCount(); i++) { boolean bExp = false; Object o = jtree.getPathForRow(i).getLastPathComponent(); if (o instanceof GenreNode) { Genre genre = ((GenreNode) o).getGenre(); bExp = genre.getBooleanValue(Const.XML_EXPANDED); } else if (o instanceof ArtistNode) { Artist artist = ((ArtistNode) o).getArtist(); bExp = artist.getBooleanValue(Const.XML_EXPANDED); } else if (o instanceof AlbumNode) { Album album = ((AlbumNode) o).getAlbum(); bExp = album.getBooleanValue(Const.XML_EXPANDED); } else if (o instanceof YearNode) { Year year = ((YearNode) o).getYear(); bExp = year.getBooleanValue(Const.XML_EXPANDED); } // now expand row if it should be expanded if (bExp) { jtree.expandRow(i); } } } // needs to be inner class as it accesses various members /** * . */ class TracksTreeSelectionListener implements TreeSelectionListener { /* * (non-Javadoc) * * @see javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event * .TreeSelectionEvent) */ @Override public void valueChanged(TreeSelectionEvent e) { TreePath[] tpSelected = jtree.getSelectionModel().getSelectionPaths(); if (tpSelected == null) { return; } // get all components recursively alSelected.clear(); selectedRecursively.clear(); int items = handleSelected(tpSelected); StringBuilder sbOut = new StringBuilder().append(items).append( Messages.getString("TracksTreeView.31")); InformationJPanel.getInstance().setSelection(sbOut.toString()); // Notify the tree selection change (used by tree/table sync) if (!bInternalAction) { Properties properties = new Properties(); properties.put(Const.DETAIL_SELECTION, selectedRecursively); properties .put(Const.DETAIL_PERSPECTIVE, PerspectiveManager.getCurrentPerspective().getID()); properties.put(Const.DETAIL_VIEW, getID()); ObservationManager.notify(new JajukEvent(JajukEvents.TREE_SELECTION_CHANGED, properties)); } // Update preference menu pjmTracks.resetUI(alSelected); } /** * Handle selected. * * @param tpSelected * * @return the int */ @SuppressWarnings("unchecked") private int handleSelected(TreePath[] tpSelected) { int items = 0; for (TreePath element : tpSelected) { Object o = element.getLastPathComponent(); if (o instanceof TreeRootElement) { // collection node items = TrackManager.getInstance().getElementCount(); List<Track> allTracks = TrackManager.getInstance().getTracks(); selectedRecursively.addAll(allTracks); break; } else { Object userObject = ((DefaultMutableTreeNode) o).getUserObject(); if (userObject instanceof Item) { alSelected.add((Item) userObject); } } // return all child nodes recursively Enumeration<DefaultMutableTreeNode> e2 = ((DefaultMutableTreeNode) o) .depthFirstEnumeration(); while (e2.hasMoreElements()) { DefaultMutableTreeNode node = e2.nextElement(); if (node instanceof TrackNode) { Track track = ((TrackNode) node).getTrack(); // don't count the same track several time // if user select directory and then tracks // inside selectedRecursively.add(track); items++; } } } return items; } } /** * Tracks Tree view mouse adapter. */ class TracksMouseAdapter extends JajukMouseAdapter { private final JMenuItem jmiShowAlbumDetails; /** * Instantiates a new tracks mouse adapter. * * @param jmiShowAlbumDetails */ public TracksMouseAdapter(JMenuItem jmiShowAlbumDetails) { super(); this.jmiShowAlbumDetails = jmiShowAlbumDetails; } /* (non-Javadoc) * @see org.jajuk.ui.helpers.JajukMouseAdapter#handleActionSeveralClicks(java.awt.event.MouseEvent) */ @Override public void handleActionSeveralClicks(final MouseEvent e) { TreePath path = jtree.getPathForLocation(e.getX(), e.getY()); if (path != null) { Object o = path.getLastPathComponent(); if (o instanceof TrackNode) { Track track = ((TrackNode) o).getTrack(); File file = track.getBestFile(false); if (file != null) { try { QueueModel.push(new StackItem(file, Conf.getBoolean(Const.CONF_STATE_REPEAT), true), Conf.getBoolean(Const.CONF_OPTIONS_PUSH_ON_CLICK)); } catch (JajukException je) { Log.error(je); } } else { Messages.showErrorMessage(10, track.getName()); } } } } /* (non-Javadoc) * @see org.jajuk.ui.helpers.JajukMouseAdapter#handlePopup(java.awt.event.MouseEvent) */ @Override public void handlePopup(final MouseEvent e) { TreePath path = jtree.getPathForLocation(e.getX(), e.getY()); if (path == null) { return; } // right click on a selected node set right click behavior // identical to konqueror tree: // if none or 1 node is selected, a right click on // another node select it. if more than 1, we keep selection and // display a popup for them if (jtree.getSelectionCount() < 2) { jtree.getSelectionModel().setSelectionPath(path); } paths = jtree.getSelectionModel().getSelectionPaths(); // test mix between types ( not allowed ) String sClass = paths[0].getLastPathComponent().getClass().toString(); for (int i = 0; i < paths.length; i++) { if (!paths[i].getLastPathComponent().getClass().toString().equals(sClass)) { return; } } // display menus according node type buildMenu(e); } /** * Builds the menu. * * @param e */ private void buildMenu(final MouseEvent e) { if (paths[0].getLastPathComponent() instanceof TrackNode) { jmenu = new JPopupMenu(); jmenu.add(jmiPlay); jmenu.add(jmiFrontPush); jmenu.add(jmiPush); jmenu.addSeparator(); jmenu.add(jmiDelete); jmenu.addSeparator(); jmenu.add(pjmTracks); jmenu.add(jmiAddFavorite); jmenu.addSeparator(); jmenu.add(jmiProperties); jmenu.show(jtree, e.getX(), e.getY()); } else if (paths[0].getLastPathComponent() instanceof AlbumNode) { jmenu = new JPopupMenu(); jmenu.add(jmiPlay); jmenu.add(jmiFrontPush); jmenu.add(jmiPush); jmenu.add(jmiPlayShuffle); jmenu.add(jmiPlayRepeat); jmenu.addSeparator(); jmenu.add(jmiDelete); jmenu.addSeparator(); jmenu.add(jmiCDDBWizard); jmenu.add(jmiReport); jmenu.add(jmiShowAlbumDetails); jmenu.addSeparator(); jmenu.add(jmiAddFavorite); jmenu.add(pjmTracks); jmenu.addSeparator(); jmenu.add(jmiProperties); jmenu.show(jtree, e.getX(), e.getY()); } else if (paths[0].getLastPathComponent() instanceof ArtistNode) { jmenu = new JPopupMenu(); jmenu.add(jmiPlay); jmenu.add(jmiFrontPush); jmenu.add(jmiPush); jmenu.add(jmiPlayShuffle); jmenu.add(jmiPlayRepeat); jmenu.addSeparator(); jmenu.add(jmiDelete); jmenu.addSeparator(); jmenu.add(jmiReport); jmenu.addSeparator(); jmenu.add(pjmTracks); jmenu.addSeparator(); jmenu.add(jmiProperties); jmenu.show(jtree, e.getX(), e.getY()); } else if (paths[0].getLastPathComponent() instanceof GenreNode) { jmenu = new JPopupMenu(); jmenu.add(jmiPlay); jmenu.add(jmiFrontPush); jmenu.add(jmiPush); jmenu.add(jmiPlayShuffle); jmenu.add(jmiPlayRepeat); jmenu.addSeparator(); jmenu.add(jmiDelete); jmenu.addSeparator(); jmenu.add(jmiReport); jmenu.addSeparator(); jmenu.add(pjmTracks); jmenu.addSeparator(); jmenu.add(jmiProperties); jmenu.show(jtree, e.getX(), e.getY()); } else if (paths[0].getLastPathComponent() instanceof YearNode) { jmenu = new JPopupMenu(); jmenu.add(jmiPlay); jmenu.add(jmiFrontPush); jmenu.add(jmiPush); jmenu.add(jmiPlayShuffle); jmenu.add(jmiPlayRepeat); jmenu.addSeparator(); jmenu.add(pjmTracks); jmenu.addSeparator(); jmenu.add(jmiProperties); jmenu.show(jtree, e.getX(), e.getY()); } else if (paths[0].getLastPathComponent() instanceof DefaultMutableTreeNode) { // Collection menu JPopupMenu jmenuCollection = new JPopupMenu(); // Collection Report Action actionReportCollection = ActionManager.getAction(JajukActions.CREATE_REPORT); JMenuItem jmiCollectionReport = new JMenuItem(actionReportCollection); // Add custom data to this component in order to allow the ReportAction // to be able to get it jmiCollectionReport.putClientProperty(Const.DETAIL_ORIGIN, COLLECTION_LOGICAL); jmenuCollection.add(jmiCollectionReport); // Find duplicate files Action actionDuplicateFiles = ActionManager.getAction(JajukActions.FIND_DUPLICATE_FILES); JMenuItem jmiCollectionDuplicateFiles = new JMenuItem(actionDuplicateFiles); jmenuCollection.add(jmiCollectionDuplicateFiles); jmenuCollection.show(jtree, e.getX(), e.getY()); } } } @Override void scrollTo(Item item) { // Set manual change because we force here tree selection and // we don't want to force table views to synchronize bInternalAction = true; try { // make sure the main element is expanded jtree.expandRow(0); Track track = null; // received item is a file when the event comes from a queue view in the // track perspective if (item instanceof File) { track = ((File) item).getTrack(); } else { track = (Track) item; } for (int i = 0; i < jtree.getRowCount(); i++) { Object o = jtree.getPathForRow(i).getLastPathComponent(); if (o instanceof AlbumNode) { Album testedAlbum = ((AlbumNode) o).getAlbum(); if (track.getAlbum().equals(testedAlbum)) { jtree.expandRow(i); jtree.scrollPathToVisible(jtree.getPathForRow(i)); } } else if (o instanceof ArtistNode) { Artist testedArtist = ((ArtistNode) o).getArtist(); if (track.getArtist().equals(testedArtist)) { jtree.expandRow(i); jtree.scrollPathToVisible(jtree.getPathForRow(i)); } } else if (o instanceof GenreNode) { Genre testedGenre = ((GenreNode) o).getGenre(); if (track.getGenre().equals(testedGenre)) { jtree.expandRow(i); jtree.scrollPathToVisible(jtree.getPathForRow(i)); } } else if (o instanceof YearNode) { Year testedYear = ((YearNode) o).getYear(); if (track.getYear().equals(testedYear)) { jtree.expandRow(i); jtree.scrollPathToVisible(jtree.getPathForRow(i)); } } else if (o instanceof TrackNode) { Track tested = ((TrackNode) o).getTrack(); // == here thanks to .intern optimization if (tested.getID() == track.getID()) { jtree.expandRow(i); jtree.scrollPathToVisible(jtree.getPathForRow(i)); } } } } finally { bInternalAction = false; } } @Override void selectNodes(List<Item> items) { // Set manual change because we force here tree selection and // we don't want to force table views to synchronize bInternalAction = true; try { // Clear selection so we only select new synchronized item jtree.getSelectionModel().clearSelection(); // make sure the main element is expanded jtree.expandRow(0); for (Item item : items) { Track track = null; // received item is a file when the event comes from a queue view in the // track perspective if (item instanceof File) { track = ((File) item).getTrack(); } else { track = (Track) item; } for (int i = 0; i < jtree.getRowCount(); i++) { Object o = jtree.getPathForRow(i).getLastPathComponent(); if (o instanceof AlbumNode) { Album testedAlbum = ((AlbumNode) o).getAlbum(); if (track.getAlbum().equals(testedAlbum)) { jtree.expandRow(i); } } else if (o instanceof ArtistNode) { Artist testedArtist = ((ArtistNode) o).getArtist(); if (track.getArtist().equals(testedArtist)) { jtree.expandRow(i); } } else if (o instanceof GenreNode) { Genre testedGenre = ((GenreNode) o).getGenre(); if (track.getGenre().equals(testedGenre)) { jtree.expandRow(i); } } else if (o instanceof YearNode) { Year testedYear = ((YearNode) o).getYear(); if (track.getYear().equals(testedYear)) { jtree.expandRow(i); } } else if (o instanceof TrackNode) { Track tested = ((TrackNode) o).getTrack(); // == here thanks to .intern optimization if (tested.getID() == track.getID()) { jtree.expandRow(i); jtree.getSelectionModel().addSelectionPath(jtree.getPathForRow(i)); } } } } } finally { bInternalAction = false; } } } /** * Genre node */ class GenreNode extends DefaultMutableTreeNode { private static final long serialVersionUID = 1L; /** * Constructor * * @param track */ public GenreNode(Genre track) { super(track); } /** * return a string representation of this track node */ @Override public String toString() { return getGenre().getName2(); } /** * @return Returns the track. */ public Genre getGenre() { return (Genre) super.getUserObject(); } } /** * Artist node */ class ArtistNode extends DefaultMutableTreeNode { /** * */ private static final long serialVersionUID = 1L; /** * Constructor * * @param artist */ public ArtistNode(Artist artist) { super(artist); } /** * return a string representation of this artist node */ @Override public String toString() { return getArtist().getName2(); } /** * @return Returns the artist. */ public Artist getArtist() { return (Artist) super.getUserObject(); } } /** * Year node */ class YearNode extends DefaultMutableTreeNode { /** * */ private static final long serialVersionUID = 1L; /** * Constructor * * @param artist */ public YearNode(Year year) { super(year); } /** * return a string representation of this node */ @Override public String toString() { if (getYear().getValue() > 0) { return getYear().getName(); } else { return Messages.getString("unknown_year"); } } /** * @return Returns the year. */ public Year getYear() { return (Year) super.getUserObject(); } } /** * Album node */ class AlbumNode extends DefaultMutableTreeNode { private static final long serialVersionUID = 1L; /** * Constructor * * @param album */ public AlbumNode(Album album) { super(album); } /** * return a string representation of this album node */ @Override public String toString() { return getAlbum().getName2(); } /** * @return Returns the album. */ public Album getAlbum() { return (Album) super.getUserObject(); } } /** * Track node */ class TrackNode extends DefaultMutableTreeNode { private static final long serialVersionUID = 1L; /** * Constructor * * @param track */ public TrackNode(Track track) { super(track); } /** * return a string representation of this track node */ @Override public String toString() { return getTrack().getName(); } /** * @return Returns the track. */ public Track getTrack() { return (Track) super.getUserObject(); } } /** * * Discovery date filter tree node */ class DiscoveryDateNode extends DefaultMutableTreeNode { /** * @param string */ public DiscoveryDateNode(String string) { super(string); } /** * We have to override this method for drag and drop * whish waits for an item. A period is not an item. * * @see DefaultMutableTreeNode.getUserObject() */ @SuppressWarnings("unchecked") @Override public Object getUserObject() { List<Item> out = new ArrayList<Item>(10); Enumeration<DefaultMutableTreeNode> childrens = children(); while (childrens.hasMoreElements()) { DefaultMutableTreeNode node = childrens.nextElement(); out.add((Item) node.getUserObject()); } return out; } private static final long serialVersionUID = 7123195836014138019L; } class TracksTreeCellRenderer extends SubstanceDefaultTreeCellRenderer { private static final long serialVersionUID = 1L; @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); setFont(FontManager.getInstance().getFont(JajukFont.PLAIN)); if (value instanceof GenreNode) { setIcon(IconLoader.getIcon(JajukIcons.GENRE)); } else if (value instanceof ArtistNode) { setIcon(IconLoader.getIcon(JajukIcons.ARTIST)); } else if (value instanceof YearNode) { setIcon(IconLoader.getIcon(JajukIcons.YEAR)); } else if (value instanceof AlbumNode) { setIcon(IconLoader.getIcon(JajukIcons.ALBUM)); } else if (value instanceof TrackNode) { setIcon(IconLoader.getIcon(JajukIcons.TRACK)); // Discovery date filter } else if (value instanceof DiscoveryDateNode) { setIcon(IconLoader.getIcon(JajukIcons.DISCOVERY_DATE)); // collection node } else { setIcon(IconLoader.getIcon(JajukIcons.LIST)); } return this; } } class TracksTreeExpansionListener implements TreeExpansionListener { @Override public void treeCollapsed(TreeExpansionEvent event) { Object o = event.getPath().getLastPathComponent(); if (o instanceof GenreNode) { Genre genre = ((GenreNode) o).getGenre(); genre.removeProperty(Const.XML_EXPANDED); } else if (o instanceof ArtistNode) { Artist artist = ((ArtistNode) o).getArtist(); artist.removeProperty(Const.XML_EXPANDED); } else if (o instanceof AlbumNode) { Album album = ((AlbumNode) o).getAlbum(); album.removeProperty(Const.XML_EXPANDED); } else if (o instanceof YearNode) { Year year = ((YearNode) o).getYear(); year.removeProperty(Const.XML_EXPANDED); } } @Override public void treeExpanded(TreeExpansionEvent event) { Object o = event.getPath().getLastPathComponent(); if (o instanceof GenreNode) { Genre genre = ((GenreNode) o).getGenre(); genre.setProperty(Const.XML_EXPANDED, true); } else if (o instanceof ArtistNode) { Artist artist = ((ArtistNode) o).getArtist(); artist.setProperty(Const.XML_EXPANDED, true); } else if (o instanceof AlbumNode) { Album album = ((AlbumNode) o).getAlbum(); album.setProperty(Const.XML_EXPANDED, true); } else if (o instanceof YearNode) { Year year = ((YearNode) o).getYear(); year.setProperty(Const.XML_EXPANDED, true); } } }