/* * 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.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseEvent; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JLayeredPane; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTree; 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.MutableTreeNode; import javax.swing.tree.TreePath; import net.miginfocom.swing.MigLayout; import org.jajuk.base.Device; import org.jajuk.base.DeviceManager; import org.jajuk.base.Directory; import org.jajuk.base.DirectoryManager; import org.jajuk.base.File; import org.jajuk.base.FileManager; import org.jajuk.base.Item; import org.jajuk.base.Playlist; import org.jajuk.base.Type; import org.jajuk.base.TypeManager; 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.actions.RefactorAction; import org.jajuk.ui.helpers.FontManager; import org.jajuk.ui.helpers.FontManager.JajukFont; import org.jajuk.ui.helpers.ItemMoveManager; import org.jajuk.ui.helpers.ItemMoveManager.MoveActions; import org.jajuk.ui.helpers.JajukMouseAdapter; import org.jajuk.ui.helpers.LazyLoadingTreeNode; 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.ui.wizard.DeviceWizard; 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.UtilFeatures; import org.jajuk.util.UtilGUI; import org.jajuk.util.UtilSystem; import org.jajuk.util.error.JajukException; import org.jajuk.util.log.Log; import org.pushingpixels.substance.api.renderers.SubstanceDefaultTreeCellRenderer; /** * Physical tree view. */ public class FilesTreeView extends AbstractTreeView implements ActionListener { /** Generated serialVersionUID. */ private static final long serialVersionUID = 1L; /** Directories selection. */ List<Directory> alDirs = new ArrayList<Directory>(10); JMenuItem jmiDirRefresh; JMenuItem jmiDirDesynchro; JMenuItem jmiDirResynchro; JMenuItem jmiDirCreatePlaylist; JMenuItem jmiDirRefactor; JMenuItem jmiDirCopyURL; JMenuItem jmiOpenExplorer; JMenuItem jmiDevMount; JMenuItem jmiDevUnmount; JMenuItem jmiDevRefresh; JMenuItem jmiDevSynchronize; JMenuItem jmiDevTest; JMenuItem jmiDevOrganize; JMenuItem jmiDevConfiguration; JMenuItem jmiDevDelete; JMenuItem jmiPlaylistFileCopy; JMenuItem jmiPlaylistFileCut; JMenuItem jmiPlaylistFilePaste; JMenuItem jmiPlaylistCopyURL; JMenuItem jmiPlaylistPrepareParty; /* * (non-Javadoc) * * @see org.jajuk.ui.IView#getDesc() */ @Override public String getDesc() { return Messages.getString("FilesTreeView.0"); } /** * Constructor. */ public FilesTreeView() { } /* * (non-Javadoc) * * @see org.jajuk.ui.IView#display() */ @Override public void initUI() { super.initUI(); // Directory menu Action actionRefreshDir = ActionManager.getAction(JajukActions.REFRESH); jmiDirRefresh = new JMenuItem(actionRefreshDir); jmiDirRefresh.putClientProperty(Const.DETAIL_SELECTION, alSelected); jmiDirRefresh.addActionListener(this); jmiDirDesynchro = new JMenuItem(Messages.getString("FilesTreeView.14"), IconLoader.getIcon(JajukIcons.DIRECTORY_DESYNCHRO)); jmiDirDesynchro.addActionListener(this); jmiDirResynchro = new JMenuItem(Messages.getString("FilesTreeView.15"), IconLoader.getIcon(JajukIcons.DIRECTORY_SYNCHRO)); jmiDirResynchro.addActionListener(this); jmiDirCreatePlaylist = new JMenuItem(Messages.getString("FilesTreeView.16")); jmiDirCreatePlaylist.setEnabled(false); jmiDirCreatePlaylist.addActionListener(this); jmiDirRefactor = new JMenuItem(Messages.getString(("FilesTreeView.62")), IconLoader.getIcon(JajukIcons.REORGANIZE)); jmiDirRefactor.addActionListener(this); jmiDirCopyURL = new JMenuItem(ActionManager.getAction(JajukActions.COPY_TO_CLIPBOARD)); jmiDirCopyURL.putClientProperty(Const.DETAIL_CONTENT, alSelected); jmiOpenExplorer = new JMenuItem(ActionManager.getAction(JajukActions.OPEN_EXPLORER)); jmiOpenExplorer.putClientProperty(Const.DETAIL_CONTENT, alSelected); // Device menu jmiDevMount = new JMenuItem(Messages.getString("FilesTreeView.28"), IconLoader.getIcon(JajukIcons.UNMOUNT)); jmiDevMount.addActionListener(this); jmiDevUnmount = new JMenuItem(Messages.getString("FilesTreeView.29"), IconLoader.getIcon(JajukIcons.UNMOUNT)); jmiDevUnmount.addActionListener(this); jmiDevRefresh = new JMenuItem(Messages.getString("FilesTreeView.30"), IconLoader.getIcon(JajukIcons.REFRESH)); jmiDevRefresh.addActionListener(this); jmiDevSynchronize = new JMenuItem(Messages.getString("FilesTreeView.31"), IconLoader.getIcon(JajukIcons.SYNCHRO)); jmiDevSynchronize.addActionListener(this); jmiDevTest = new JMenuItem(Messages.getString("FilesTreeView.32"), IconLoader.getIcon(JajukIcons.TEST)); jmiDevTest.addActionListener(this); jmiDevConfiguration = new JMenuItem(Messages.getString("FilesTreeView.55"), IconLoader.getIcon(JajukIcons.CONFIGURATION)); jmiDevConfiguration.addActionListener(this); jmiDevDelete = new JMenuItem(Messages.getString("DeviceView.13"), IconLoader.getIcon(JajukIcons.DELETE)); jmiDevDelete.addActionListener(this); jmiDevOrganize = new JMenuItem(Messages.getString(("FilesTreeView.62")), IconLoader.getIcon(JajukIcons.REORGANIZE)); jmiDevOrganize.addActionListener(this); // playlist menu jmiPlaylistFileCopy = new JMenuItem(Messages.getString("FilesTreeView.40")); jmiPlaylistFileCopy.setEnabled(false); jmiPlaylistFileCopy.addActionListener(this); jmiPlaylistFileCut = new JMenuItem(Messages.getString("FilesTreeView.41")); jmiPlaylistFileCut.setEnabled(false); jmiPlaylistFileCut.addActionListener(this); jmiPlaylistFilePaste = new JMenuItem(Messages.getString("FilesTreeView.42")); jmiPlaylistFilePaste.setEnabled(false); jmiPlaylistFilePaste.addActionListener(this); jmiPlaylistCopyURL = new JMenuItem(ActionManager.getAction(JajukActions.COPY_TO_CLIPBOARD)); jmiPlaylistCopyURL.putClientProperty(Const.DETAIL_CONTENT, alSelected); jmiPlaylistPrepareParty = new JMenuItem(ActionManager.getAction(JajukActions.PREPARE_PARTY)); jmiPlaylistPrepareParty.putClientProperty(Const.DETAIL_SELECTION, alSelected); // Add Action Listener jmiCopy.addActionListener(this); jmiCut.addActionListener(this); jmiPaste.addActionListener(this); // By default disable paste jmiPaste.setEnabled(false); top = new TreeRootElement(Messages.getString("FilesTreeView.47")); // Register on the list for subject we are interested in ObservationManager.register(this); // fill the tree model populateTree(); // create tree createTree(true); /** * CAUTION ! we register several listeners against this tree Swing can't * ensure the order where listeners will treat them so don't count in the * mouse listener to get correct selection from selection listener */ jtree.setCellRenderer(new FilesTreeCellRenderer()); // Tree selection listener to detect a selection (single click // , manages simple or multiple selections) jtree.addTreeSelectionListener(new FilesTreeSelectionListener()); // Listen for single / double click jtree.addMouseListener(new FilesMouseAdapter()); // Expansion analyzed to keep expended state jtree.addTreeExpansionListener(new FilesTreeExpansionListener()); jtree.setAutoscrolls(true); jspTree = new JScrollPane(jtree); jspTree.setBorder(BorderFactory.createEmptyBorder(0, 1, 0, 0)); // DND support jtree.setDragEnabled(true); jtree.setTransferHandler(new TreeTransferHandler(jtree)); // layout : the tree takes all the available height and we display the // command buttons on a different layer (because we don't want to use a // dedicated row like in the Tracks tree table : it's too ugly and // space-consuming) setLayout(new MigLayout("ins 3", "[grow]", "[grow]")); final JLayeredPane lp = new JLayeredPane(); addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { // At first display and afterwards at each view resize, we cleanup and // re-add tree and command buttons adapted to the new view size. lp.removeAll(); jspTree.setBounds(0, 0, getWidth() - 5, getHeight() - 5); lp.add(jspTree, JLayeredPane.DEFAULT_LAYER); jtbSync.setBounds(getWidth() - 80, 0, 25, 22); lp.add(jtbSync, JLayeredPane.POPUP_LAYER); jbCollapseAll.setBounds(getWidth() - 50, 0, 23, 22); lp.add(jbCollapseAll, JLayeredPane.POPUP_LAYER); lp.revalidate(); lp.repaint(); } }); add(lp, "grow"); // expand all 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; top.removeAllChildren(); // add all devices as "LazyLoading" nodes so all subsequent elements are // only populated if necessary List<Device> devices = DeviceManager.getInstance().getDevices(); for (Device device : devices) { DefaultMutableTreeNode nodeDevice = new DeviceNode(device); top.add(nodeDevice); } // 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; } } /* * (non-Javadoc) * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ @Override public void actionPerformed(final ActionEvent e) { // multiple selection on properties(note we handle files and dirs // properties later) if ((e.getSource() == jmiDirRefactor || e.getSource() == jmiDevOrganize)) { UtilGUI.waiting(); for (Item item : alSelected) { // Check if user made a global cancel if (RefactorAction.isStopAll()) { RefactorAction.resetStopAll(); return; } // If user selected a device, take associated directory if (item instanceof Device) { item = ((Device) item).getRootDirectory(); } final Directory dir = (Directory) item; UtilGUI.waiting(); new RefactorAction(dir.getFilesRecursively()); } } else if (e.getSource() == jmiDevMount) { for (TreePath element : paths) { DeviceNode node = (DeviceNode) (element.getLastPathComponent()); Device device = node.getDevice(); try { device.mount(true); jtree.expandPath(new TreePath(node.getPath())); } catch (JajukException je) { Messages.showErrorMessage(je.getCode()); } catch (Exception ex) { Messages.showErrorMessage(11); } } } else if (e.getSource() == jmiDevUnmount) { for (TreePath element : paths) { DeviceNode node = (DeviceNode) (element.getLastPathComponent()); Device device = node.getDevice(); try { device.unmount(); jtree.collapsePath(new TreePath(node.getPath())); } catch (Exception ex) { Messages.showErrorMessage(12); } } } else if (e.getSource() == jmiDevRefresh) { Device device = ((DeviceNode) (paths[0].getLastPathComponent())).getDevice(); // ask user if he wants to make deep // or fast scan device.refresh(true, true, false, null); } else if (e.getSource() == jmiDevSynchronize) { Device device = ((DeviceNode) (paths[0].getLastPathComponent())).getDevice(); device.synchronize(true); } else if (e.getSource() == jmiDevTest) { new Thread("Files Tree Action Thread") { // test asynchronously in case of delay (samba // pbm for ie) @Override public void run() { Device device = ((DeviceNode) (paths[0].getLastPathComponent())).getDevice(); if (device.test()) { Messages.showInfoMessage(Messages.getString("DeviceView.21"), IconLoader.getIcon(JajukIcons.OK)); } else { Messages.showInfoMessage(Messages.getString("DeviceView.22"), IconLoader.getIcon(JajukIcons.KO)); } } }.start(); } else if (e.getSource() == jmiDirDesynchro) { setSynchonizationStateRecursively(false); jtree.revalidate(); jtree.repaint(); } else if (e.getSource() == jmiDirResynchro) { setSynchonizationStateRecursively(true); jtree.revalidate(); jtree.repaint(); } else if (e.getSource() == jmiCopy || e.getSource() == jmiCut) { jmiPaste.setEnabled(true); jmenu.repaint(); } else if (e.getSource() == jmiPaste) { if (MoveActions.CUT.equals(ItemMoveManager.getInstance().getAction())) { jmiPaste.setEnabled(false); jmenu.repaint(); } } else if (e.getSource() == jmiDevConfiguration) { Device device = ((DeviceNode) paths[0].getLastPathComponent()).getDevice(); DeviceWizard dw = new DeviceWizard(); dw.updateWidgets(device); dw.pack(); dw.setVisible(true); } else if (e.getSource() == jmiDevDelete) { Device device = ((DeviceNode) paths[0].getLastPathComponent()).getDevice(); DeviceManager.getInstance().removeDevice(device); // refresh views ObservationManager.notify(new JajukEvent(JajukEvents.DEVICE_REFRESH)); } } /** * Sets the synchronization state recursively for all files selected. * * @param sync whether the directories should be synchronized */ private void setSynchonizationStateRecursively(boolean sync) { Set<Directory> directories = new HashSet<Directory>(); for (Item item : alSelected) { Directory dir = (Directory) item; directories.add(dir); directories.addAll(dir.getDirectoriesRecursively()); } for (Directory dir : directories) { dir.setProperty(Const.XML_DIRECTORY_SYNCHRONIZED, sync); } } /** * Manages auto-expand Expand behavior is: * <p> * At startup, tree expand state is the same that the one kept at last session * (we use XML_EXPANDED stored properties to restore it) * </p> * <p> * When mounting a device from the tree, the device node is expanded * </p> * <p> * When unmounting a device from the tree, the device node is collapsed * </p> * . */ @Override void expand() { // make sure the main element is expanded jtree.expandRow(0); // begin by expanding all needed devices and directory, only after, // collapse unmounted devices if required for (int i = 0; i < jtree.getRowCount(); i++) { Object o = jtree.getPathForRow(i).getLastPathComponent(); if (o instanceof DeviceNode) { Device device = ((DeviceNode) o).getDevice(); if (device.getBooleanValue(Const.XML_EXPANDED)) { jtree.expandRow(i); } // Collapse node (useful to hide an live-unmounted device for ie) else { jtree.collapseRow(i); } } else if (o instanceof DirectoryNode) { Directory dir = ((DirectoryNode) o).getDirectory(); if (dir.getBooleanValue(Const.XML_EXPANDED)) { jtree.expandRow(i); } } } } @Override void selectNodes(List<Item> items) { if (items == null || items.size() == 0) { Log.warn("None item to select in Tree view"); return; } // 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(); for (Item item : items) { // Expand recursively item's directory because of the lazy loading stuff expandRecursively(item); // Now scroll to the item and select it for (int i = 0; i < jtree.getRowCount(); i++) { Object o = jtree.getPathForRow(i).getLastPathComponent(); if (o instanceof FileNode) { o = ((FileNode) o).getFile(); } else if (o instanceof PlaylistFileNode) { o = ((PlaylistFileNode) o).getPlaylistFile(); } else { continue; } if (item.equals(o)) { jtree.getSelectionModel().addSelectionPath(jtree.getPathForRow(i)); } } } } finally { bInternalAction = false; } } @Override void scrollTo(Item item) { // Make sure item is a file (may be webradio) if (FileManager.getInstance().getFileByID(item.getID()) == null) { return; } // Set manual change because we force here tree selection and // we don't want to force table views to synchronize bInternalAction = true; try { // Expand recursively item's directory because of the lazy loading stuff expandRecursively(item); // Now scroll to the item and select it for (int i = 0; i < jtree.getRowCount(); i++) { Object o = jtree.getPathForRow(i).getLastPathComponent(); if (o instanceof FileNode) { o = ((FileNode) o).getFile(); } else if (o instanceof PlaylistFileNode) { o = ((PlaylistFileNode) o).getPlaylistFile(); } else { continue; } if (item.equals(o)) { jtree.scrollRowToVisible(i); } } } finally { bInternalAction = false; } } /** * Expand recursively all directory nodes of given item. * * @param item : file or playlist */ private void expandRecursively(Item item) { jtree.expandRow(0); boolean stopLoop = false; // Keep tree path list here, do not put this call in the loop as // it would change at each node expand List<TreePath> paths = new ArrayList<TreePath>(); for (int i = 0; i < jtree.getRowCount(); i++) { TreePath path = jtree.getPathForRow(i); paths.add(path); } // item is either a file or a playlist for (int i = 0; i < paths.size(); i++) { Object o = paths.get(i).getLastPathComponent(); if (o instanceof DirectoryNode || o instanceof DeviceNode) { Directory testedDirectory = null; // If the node is a device, search its root directory and check it if (o instanceof DeviceNode) { Device testedDevice = ((DeviceNode) o).getDevice(); testedDirectory = testedDevice.getRootDirectory(); } else { testedDirectory = ((DirectoryNode) o).getDirectory(); } if (item instanceof File) { File file = (File) item; if (file.hasAncestor(testedDirectory)) { jtree.expandRow(i); } if (testedDirectory.equals(file.getDirectory())) { stopLoop = true; } } else if (item instanceof Playlist) { Playlist playlist = (Playlist) item; if (playlist.hasAncestor(testedDirectory)) { jtree.expandRow(i); } if (testedDirectory.equals(playlist.getDirectory())) { stopLoop = true; } } } } if (!stopLoop) { expandRecursively(item); } } /** * . */ private class FilesMouseAdapter extends JajukMouseAdapter { /* * (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) { return; } Object o = path.getLastPathComponent(); if (o instanceof FileNode) { File file = ((FileNode) o).getFile(); 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); } } // double click on a playlist else if (o instanceof PlaylistFileNode) { Playlist plf = ((PlaylistFileNode) o).getPlaylistFile(); List<File> alToPlay = null; try { alToPlay = plf.getFiles(); } catch (JajukException je) { Log.error(je.getCode(), "{{" + plf.getName() + "}}", null); Messages.showErrorMessage(je.getCode(), plf.getName()); return; } // check playlist contains accessible // tracks if (alToPlay == null || alToPlay.size() == 0) { Messages.showErrorMessage(18); return; } else { QueueModel.push( UtilFeatures.createStackItems(UtilFeatures.applyPlayOption(alToPlay), Conf.getBoolean(Const.CONF_STATE_REPEAT), true), false); } } } /* * (non-Javadoc) * * @see org.jajuk.ui.helpers.JajukMouseAdapter#handlePopup(java.awt.event.MouseEvent) */ @Override @SuppressWarnings("unchecked") 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(); alDirs.clear(); // test mix between types ( not allowed ) Class<?> c = paths[0].getLastPathComponent().getClass(); for (int i = 0; i < paths.length; i++) { if (!paths[i].getLastPathComponent().getClass().equals(c)) { return; } } // Test that all items are mounted or hide menu item // device:mono selection for the moment if (c.equals(DeviceNode.class)) { Device device = ((DeviceNode) (paths[0].getLastPathComponent())).getDevice(); if (device.isMounted()) { jmiDevMount.setEnabled(false); jmiDevUnmount.setEnabled(true); } else { jmiDevMount.setEnabled(true); jmiDevUnmount.setEnabled(false); } final Directory dir = DirectoryManager.getInstance().registerDirectory(device); boolean bShowCDDB = false; if (dir.getFiles().size() > 0) { bShowCDDB = true; } jmiCDDBWizard.setEnabled(bShowCDDB); } if (c.equals(DirectoryNode.class)) { for (TreePath element : paths) { Directory dir = ((DirectoryNode) (element.getLastPathComponent())).getDirectory(); if (!dir.getDevice().isMounted()) { continue; } } } if (c.equals(FileNode.class)) { for (TreePath element : paths) { File file = ((FileNode) (element.getLastPathComponent())).getFile(); if (!file.isReady()) { continue; } } } jmiDelete.setEnabled(true); if (c.equals(PlaylistFileNode.class)) { for (TreePath element : paths) { Playlist plf = ((PlaylistFileNode) (element.getLastPathComponent())).getPlaylistFile(); if (!plf.isReady()) { jmiDelete.setEnabled(false); continue; } } } // get all components recursively for (TreePath element : paths) { Object o = element.getLastPathComponent(); // return all childs nodes recursively Enumeration<DefaultMutableTreeNode> e2 = ((DefaultMutableTreeNode) o) .depthFirstEnumeration(); while (e2.hasMoreElements()) { DefaultMutableTreeNode node = e2.nextElement(); if (node instanceof DirectoryNode) { Directory dir = ((DirectoryNode) node).getDirectory(); alDirs.add(dir); } } } // display menus according node type if (paths[0].getLastPathComponent() instanceof FileNode) { jmenu = new JPopupMenu(); jmenu.add(jmiPlay); jmenu.add(jmiFrontPush); jmenu.add(jmiPush); jmenu.addSeparator(); jmenu.add(jmiCut); jmenu.add(jmiCopy); jmenu.add(jmiRename); jmenu.add(jmiDelete); jmenu.add(jmiCopyURL); jmenu.add(jmiOpenExplorer); 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 DirectoryNode) { jmenu = new JPopupMenu(); jmenu.add(jmiPlay); jmenu.add(jmiFrontPush); jmenu.add(jmiPush); jmenu.add(jmiPlayShuffle); jmenu.add(jmiPlayRepeat); jmenu.addSeparator(); jmenu.add(jmiCut); jmenu.add(jmiCopy); jmenu.add(jmiPaste); jmenu.add(jmiNewFolder); jmenu.add(jmiDelete); jmenu.add(jmiDirCopyURL); jmenu.add(jmiOpenExplorer); jmenu.addSeparator(); jmenu.add(jmiDirRefresh); jmenu.add(jmiRename); jmenu.add(jmiDirDesynchro); jmenu.add(jmiDirResynchro); jmenu.addSeparator(); jmenu.add(jmiCDDBWizard); jmenu.add(jmiReport); jmenu.add(jmiDirRefactor); jmenu.addSeparator(); jmenu.add(pjmTracks); jmenu.addSeparator(); jmenu.add(jmiProperties); jmenu.show(jtree, e.getX(), e.getY()); } else if (paths[0].getLastPathComponent() instanceof PlaylistFileNode) { jmenu = new JPopupMenu(); jmenu.add(jmiPlay); jmenu.add(jmiFrontPush); jmenu.add(jmiPush); jmenu.add(jmiPlayShuffle); jmenu.add(jmiPlayRepeat); jmenu.addSeparator(); jmenu.add(jmiPlaylistCopyURL); jmenu.add(jmiPlaylistPrepareParty); jmenu.add(jmiOpenExplorer); jmenu.add(jmiDelete); jmenu.addSeparator(); jmenu.add(jmiProperties); jmenu.show(jtree, e.getX(), e.getY()); } else if (paths[0].getLastPathComponent() instanceof DeviceNode) { jmenu = new JPopupMenu(); jmenu.add(jmiPlay); jmenu.add(jmiFrontPush); jmenu.add(jmiPush); jmenu.add(jmiPaste); jmenu.add(jmiPlayShuffle); jmenu.add(jmiPlayRepeat); jmenu.addSeparator(); jmenu.add(jmiNewFolder); jmenu.add(jmiDevMount); jmenu.add(jmiDevUnmount); jmenu.add(jmiDevRefresh); jmenu.add(jmiDevSynchronize); jmenu.addSeparator(); jmenu.add(jmiDevTest); jmenu.add(jmiCDDBWizard); jmenu.add(jmiDevOrganize); jmenu.add(jmiReport); jmenu.addSeparator(); jmenu.add(jmiDevDelete); jmenu.add(jmiDevConfiguration); jmenu.addSeparator(); jmenu.add(jmiProperties); Device device = ((DeviceNode) paths[0].getLastPathComponent()).getDevice(); // if the device is not synchronized if (device.getValue(Const.XML_DEVICE_SYNCHRO_SOURCE).equals("")) { jmiDevSynchronize.setEnabled(false); } else { jmiDevSynchronize.setEnabled(true); } // operations on devices are mono-target expect for // reporting if (paths.length > 1) { // Disable all menu items except reporting for (int i = 0; i < jmenu.getSubElements().length; i++) { ((JMenuItem) jmenu.getSubElements()[i]).setEnabled(false); } jmiReport.setEnabled(true); } else { // Enable all menu items for (int i = 0; i < jmenu.getSubElements().length; i++) { ((JMenuItem) jmenu.getSubElements()[i]).setEnabled(true); } } jmenu.show(jtree, e.getX(), e.getY()); } else if (paths[0].getLastPathComponent() instanceof DefaultMutableTreeNode) { // Collection menu JPopupMenu jmenuCollection = new JPopupMenu(); // Export 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_PHYSICAL); jmenuCollection.add(jmiCollectionReport); Action actionDuplicateFiles = ActionManager.getAction(JajukActions.FIND_DUPLICATE_FILES); JMenuItem jmiCollectionDuplicateFiles = new JMenuItem(actionDuplicateFiles); jmenuCollection.add(jmiCollectionDuplicateFiles); // collection jmenuCollection.show(jtree, e.getX(), e.getY()); } } } /** * . */ private class FilesTreeSelectionListener implements TreeSelectionListener { /* * (non-Javadoc) * * @see javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event * .TreeSelectionEvent) */ @Override public void valueChanged(TreeSelectionEvent e) { paths = jtree.getSelectionModel().getSelectionPaths(); // nothing selected, can be called during dnd if (paths == null || paths.length == 0) { return; } // get all components recursively selectedRecursively.clear(); alSelected.clear(); // Treat case when use selected the tree's root Object firstPath = paths[0].getLastPathComponent(); if (firstPath instanceof TreeRootElement) { selectedRecursively.addAll(FileManager.getInstance().getFiles()); } else { // Regular selection, one or more nodes for (TreePath element : paths) { // return all child nodes recursively, do not count on the tree's // model as it is lazy loaded Object o = element.getLastPathComponent(); Item item = (Item) ((DefaultMutableTreeNode) o).getUserObject(); alSelected.add(item); Directory directory = null; if (o instanceof DeviceNode) { directory = ((DeviceNode) o).getDevice().getRootDirectory(); } else if (o instanceof DirectoryNode) { directory = ((DirectoryNode) o).getDirectory(); } if (directory != null) { selectedRecursively.addAll(directory.getFilesRecursively()); selectedRecursively.addAll(directory.getPlaylistFiles()); } else if (o instanceof FileNode) { selectedRecursively.add(((FileNode) o).getFile()); } else if (o instanceof PlaylistFileNode) { selectedRecursively.add(((PlaylistFileNode) o).getPlaylistFile()); } } } updateSelectionMessage(); // 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)); } // Enable CDDB retagging only for a single directory selection jmiCDDBWizard.setEnabled(alSelected.size() == 1 && alSelected.get(0) instanceof Directory); // Enable device refresh for a single item jmiDevRefresh.setEnabled(alSelected.size() == 1 && alSelected.get(0) instanceof Device); // Enable Copy url for a single item only jmiCopyURL.setEnabled(alSelected.size() == 1 && alSelected.get(0) instanceof File); jmiDirCopyURL.setEnabled(alSelected.size() == 1 && alSelected.get(0) instanceof Directory); jmiOpenExplorer .setEnabled(alSelected.size() == 1 && (alSelected.get(0) instanceof Directory || alSelected.get(0) instanceof File || alSelected .get(0) instanceof Playlist)); jmiPlaylistCopyURL .setEnabled(alSelected.size() == 1 && alSelected.get(0) instanceof Playlist); jmiPlaylistPrepareParty.setEnabled(alSelected.size() == 1 && alSelected.get(0) instanceof Playlist); // Update preference menu pjmTracks.resetUI(alSelected); } private void updateSelectionMessage() { long lSize = 0; int items; // Compute recursive selection size, nb of items... for (Item item : selectedRecursively) { if (item instanceof File) { lSize += ((File) item).getSize(); } } items = selectedRecursively.size(); lSize /= 1048576; // set size in MB StringBuilder sbOut = new StringBuilder().append(items).append( Messages.getString("FilesTreeView.52")); if (lSize > 1024) { // more than 1024 MB -> in GB sbOut.append(lSize / 1024).append('.').append(lSize % 1024) .append(Messages.getString("FilesTreeView.53")); } else { sbOut.append(lSize).append(Messages.getString("FilesTreeView.54")); } InformationJPanel.getInstance().setSelection(sbOut.toString()); } } /** * . */ private class FilesTreeExpansionListener implements TreeExpansionListener { /* * (non-Javadoc) * * @see javax.swing.event.TreeExpansionListener#treeCollapsed(javax.swing.event * .TreeExpansionEvent) */ @Override public void treeCollapsed(TreeExpansionEvent event) { Object o = event.getPath().getLastPathComponent(); if (o instanceof DirectoryNode && bManualAction) { Directory dir = ((DirectoryNode) o).getDirectory(); dir.removeProperty(Const.XML_EXPANDED); } else if (o instanceof DeviceNode && bManualAction) { Device device = ((DeviceNode) o).getDevice(); device.removeProperty(Const.XML_EXPANDED); } } /* * (non-Javadoc) * * @see javax.swing.event.TreeExpansionListener#treeExpanded(javax.swing.event * .TreeExpansionEvent) */ @Override public void treeExpanded(TreeExpansionEvent event) { Object o = event.getPath().getLastPathComponent(); if (o instanceof DirectoryNode && bManualAction) { Directory dir = ((DirectoryNode) o).getDirectory(); dir.setProperty(Const.XML_EXPANDED, true); } else if (o instanceof DeviceNode && bManualAction) { Device device = ((DeviceNode) o).getDevice(); device.setProperty(Const.XML_EXPANDED, true); } } } /** * Fill the provided list with sub-elements for that directory, i.e. * sub-directories, files and playlists. * * @param parent The parent-directory to start from. * @param list The list to add elements to. This list can contain elements before * which will not be touched. */ static void populateFromDirectory(Directory parent, List<MutableTreeNode> list) { // now we get all directories in this device for (Directory directory : parent.getDirectories()) { if (directory.shouldBeHidden()) { continue; } list.add(new DirectoryNode(directory)); } // then files for (File file : parent.getFiles()) { if (file.shouldBeHidden()) { continue; } list.add(new FileNode(file)); } // and playlists for (Playlist pl : parent.getPlaylistFiles()) { if (pl.shouldBeHidden()) { continue; } list.add(new PlaylistFileNode(pl)); } } } /** * File node */ class FileNode extends DefaultMutableTreeNode { private static final long serialVersionUID = 1L; /** * Constructor * * @param file */ public FileNode(File file) { super(file); } /** * return a string representation of this file node */ @Override public String toString() { return getFile().getName(); } /** * @return Returns the file. */ public File getFile() { return (File) super.getUserObject(); } } /** * Device node */ class DeviceNode extends LazyLoadingTreeNode { private static final long serialVersionUID = 1L; /** * Constructor * * @param device */ public DeviceNode(Device device) { super(device); } /** * return a string representation of this device node */ @Override public String toString() { return getDevice().getName(); } /** * @return Returns the device. */ public Device getDevice() { return (Device) super.getUserObject(); } /* * (non-Javadoc) * * @see org.jajuk.ui.widgets.LazyLoadingTreeNode#loadChildren(javax.swing.tree. DefaultTreeModel) */ @Override public MutableTreeNode[] loadChildren(DefaultTreeModel model) { List<MutableTreeNode> list = new ArrayList<MutableTreeNode>(); // first level is the directory of the device itself, usually only one for (Directory parent : getDevice().getDirectories()) { // so for each directory that is listed for that Device we build up the // list of sub-elements FilesTreeView.populateFromDirectory(parent, list); } return list.toArray(new MutableTreeNode[list.size()]); } } /** * Directory node */ class DirectoryNode extends LazyLoadingTreeNode { private static final long serialVersionUID = 1L; /** * Constructor * * @param Directory */ public DirectoryNode(Directory directory) { super(directory); } /** * return a string representation of this directory node */ @Override public String toString() { return getDirectory().getName(); } /** * @return Returns the directory. */ public Directory getDirectory() { return (Directory) getUserObject(); } /* * (non-Javadoc) * * @see org.jajuk.ui.helpers.LazyLoadingTreeNode#loadChildren(javax.swing.tree .DefaultTreeModel) */ @Override public MutableTreeNode[] loadChildren(DefaultTreeModel model) { List<MutableTreeNode> list = new ArrayList<MutableTreeNode>(); // simply collect all items one level below that directory FilesTreeView.populateFromDirectory(getDirectory(), list); return list.toArray(new MutableTreeNode[list.size()]); } } /** * Playlist node */ class PlaylistFileNode extends DefaultMutableTreeNode { private static final long serialVersionUID = 1L; /** * Constructor * * @param Playlist */ public PlaylistFileNode(Playlist playlistFile) { super(playlistFile); } /** * return a string representation of this playlistFile node */ @Override public String toString() { return getPlaylistFile().getName(); } /** * @return Returns the playlist node. */ public Playlist getPlaylistFile() { return (Playlist) getUserObject(); } } class FilesTreeCellRenderer 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 FileNode) { setBorder(null); File file = ((FileNode) value).getFile(); // Note: file.getName() is better here as it will do less and not // create java.io.File in File String ext = UtilSystem.getExtension(file.getName()); Type type = TypeManager.getInstance().getTypeByExtension(ext); // Find associated icon with this type URL icon = null; String sIcon; if (type != null) { sIcon = (String) type.getValue(Const.XML_TYPE_ICON); try { icon = new URL(sIcon); } catch (MalformedURLException e) { Log.error(e); } } if (icon == null) { setIcon(IconLoader.getIcon(JajukIcons.TYPE_WAV)); } else { setIcon(new ImageIcon(icon)); } } else if (value instanceof PlaylistFileNode) { setBorder(null); setIcon(IconLoader.getIcon(JajukIcons.PLAYLIST_FILE)); } else if (value instanceof DeviceNode) { setBorder(BorderFactory.createEmptyBorder(2, 0, 3, 0)); Device device = ((DeviceNode) value).getDevice(); ImageIcon deviceIconSmall = device.getIconRepresentation(); setIcon(deviceIconSmall); } else if (value instanceof DirectoryNode) { setBorder(null); Directory dir = ((DirectoryNode) value).getDirectory(); boolean bSynchro = dir.getBooleanValue(Const.XML_DIRECTORY_SYNCHRONIZED); if (bSynchro) { // means this device is not synchronized setIcon(IconLoader.getIcon(JajukIcons.DIRECTORY_SYNCHRO)); } else { setIcon(IconLoader.getIcon(JajukIcons.DIRECTORY_DESYNCHRO)); } // collection node } else { setIcon(IconLoader.getIcon(JajukIcons.LIST)); } return this; } }