/*
* 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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.jajuk.base.Item;
import org.jajuk.events.JajukEvent;
import org.jajuk.events.JajukEvents;
import org.jajuk.ui.actions.ActionManager;
import org.jajuk.ui.actions.JajukActions;
import org.jajuk.ui.helpers.LazyLoadingTreeExpander;
import org.jajuk.ui.helpers.PreferencesJMenu;
import org.jajuk.ui.widgets.JajukToggleButton;
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.jdesktop.swingx.JXTree;
/**
* An abstract files or tracks tree view. Contains common methods
*/
public abstract class AbstractTreeView extends ViewAdapter {
/** Generated serialVersionUID. */
private static final long serialVersionUID = 8330315957562739918L;
/** The tree scrollpane. */
JScrollPane jspTree;
/** The phyical tree. */
JXTree jtree;
/** The table/tree sync toggle button. */
JajukToggleButton jtbSync;
/** the collapse all button. */
JButton jbCollapseAll;
/** Current selection. */
TreePath[] paths;
/** Resursive items selection. */
Set<Item> selectedRecursively = new HashSet<Item>(100);
/** Items selection. */
List<Item> alSelected = new ArrayList<Item>(100);
/** Top tree node. */
DefaultMutableTreeNode top;
javax.swing.JPopupMenu jmenu;
JMenuItem jmiPlay;
JMenuItem jmiPush;
JMenuItem jmiFrontPush;
JMenuItem jmiPlayShuffle;
JMenuItem jmiPlayRepeat;
JMenuItem jmiCut;
JMenuItem jmiCopy;
JMenuItem jmiPaste;
JMenuItem jmiRename;
JMenuItem jmiDelete;
JMenuItem jmiNewFolder;
JMenuItem jmiAddFavorite;
JMenuItem jmiReport;
JMenuItem jmiProperties;
JMenuItem jmiCDDBWizard;
JMenuItem jmiCopyURL;
/** Jtree scroller position*. */
private int pos;
/** Preference menu. */
PreferencesJMenu pjmTracks;
/** Used to differentiate user action tree collapse from code tree collapse. */
boolean bManualAction = true;
/** Used to differentiate tree/table sync due to internal events from users's ones. */
boolean bInternalAction = false;
/** Flag used to set tree is refreshing. */
boolean refreshing = false;
/*
* (non-Javadoc)
*
* @see org.jajuk.events.Observer#getRegistrationKeys()
*/
@Override
public Set<JajukEvents> getRegistrationKeys() {
Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>();
eventSubjectSet.add(JajukEvents.FILE_LAUNCHED);
eventSubjectSet.add(JajukEvents.DEVICE_MOUNT);
eventSubjectSet.add(JajukEvents.DEVICE_UNMOUNT);
eventSubjectSet.add(JajukEvents.DEVICE_REFRESH);
eventSubjectSet.add(JajukEvents.CDDB_WIZARD);
eventSubjectSet.add(JajukEvents.PARAMETERS_CHANGE);
eventSubjectSet.add(JajukEvents.TABLE_SELECTION_CHANGED);
eventSubjectSet.add(JajukEvents.RATE_CHANGED);
return eventSubjectSet;
}
/**
* Creates the tree.
*
* @param bLazy
*
* @return the j tree
*/
protected JTree createTree(boolean bLazy) {
jtree = new JXTree(top);
jtree.putClientProperty("JTree.lineStyle", "Angled");
jtree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
setKeystrokes();
// set the special controller for doing lazy loading if used for this View
if (bLazy) {
final LazyLoadingTreeExpander controller = new LazyLoadingTreeExpander(
(DefaultTreeModel) jtree.getModel());
jtree.addTreeWillExpandListener(controller);
}
return jtree;
}
/*
* (non-Javadoc)
*
* @see org.jajuk.ui.views.IView#initUI()
*/
@Override
public void initUI() {
jmiPlay = new JMenuItem(ActionManager.getAction(JajukActions.PLAY_SELECTION));
jmiPlay.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiFrontPush = new JMenuItem(ActionManager.getAction(JajukActions.PUSH_FRONT_SELECTION));
jmiFrontPush.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiPush = new JMenuItem(ActionManager.getAction(JajukActions.PUSH_SELECTION));
jmiPush.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiPlayShuffle = new JMenuItem(ActionManager.getAction(JajukActions.PLAY_SHUFFLE_SELECTION));
jmiPlayShuffle.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiPlayRepeat = new JMenuItem(ActionManager.getAction(JajukActions.PLAY_REPEAT_SELECTION));
jmiPlayRepeat.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiCut = new JMenuItem(ActionManager.getAction(JajukActions.CUT));
jmiCut.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiCopy = new JMenuItem(ActionManager.getAction(JajukActions.COPY));
jmiCopy.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiPaste = new JMenuItem(ActionManager.getAction(JajukActions.PASTE));
jmiPaste.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiRename = new JMenuItem(ActionManager.getAction(JajukActions.RENAME));
jmiRename.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiDelete = new JMenuItem(ActionManager.getAction(JajukActions.DELETE));
jmiDelete.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiNewFolder = new JMenuItem(ActionManager.getAction(JajukActions.NEW_FOLDER));
jmiNewFolder.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiAddFavorite = new JMenuItem(ActionManager.getAction(JajukActions.BOOKMARK_SELECTION));
jmiAddFavorite.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiCDDBWizard = new JMenuItem(ActionManager.getAction(JajukActions.CDDB_SELECTION));
jmiCDDBWizard.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiReport = new JMenuItem(ActionManager.getAction(JajukActions.CREATE_REPORT));
// Add custom data to this component in order to allow the ReportAction
// to be able to get it
jmiReport.putClientProperty(Const.DETAIL_ORIGIN, XML_GENRE);
jmiReport.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiProperties = new JMenuItem(ActionManager.getAction(JajukActions.SHOW_PROPERTIES));
jmiProperties.putClientProperty(Const.DETAIL_SELECTION, alSelected);
jmiCopyURL = new JMenuItem(ActionManager.getAction(JajukActions.COPY_TO_CLIPBOARD));
jmiCopyURL.putClientProperty(Const.DETAIL_CONTENT, alSelected);
pjmTracks = new PreferencesJMenu(alSelected);
// Create the sync toggle button and restore its state
jtbSync = new JajukToggleButton(ActionManager.getAction(JajukActions.SYNC_TREE_TABLE));
jtbSync.putClientProperty(Const.DETAIL_VIEW, getID());
jtbSync.setSelected(Conf.getBoolean(Const.CONF_SYNC_TABLE_TREE + "." + getID()));
// Create the collapse all button, no need to a dedicated Action here as it
// is used only in this class
jbCollapseAll = new JButton(IconLoader.getIcon(JajukIcons.REMOVE));
jbCollapseAll.setToolTipText(Messages.getString("AbstractTreeView.0"));
jbCollapseAll.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
jtree.collapseAll();
// better to show at least the first level of items
jtree.expandRow(0);
jtree.setSelectionInterval(0, 0);
}
});
}
/**
* Populate tree.
*/
abstract void populateTree();
/**
* Expand.
*/
abstract void expand();
/**
* Expand a given item.
*
* @param item
*/
abstract void scrollTo(Item item);
/**
* Select a set of items
* @param items items to select
*/
abstract void selectNodes(List<Item> items);
/**
* Add keystroke support on the tree.
*/
private void setKeystrokes() {
jtree.putClientProperty(Const.DETAIL_SELECTION, alSelected);
InputMap inputMap = jtree.getInputMap(JComponent.WHEN_FOCUSED);
ActionMap actionMap = jtree.getActionMap();
// Delete
Action action = ActionManager.getAction(JajukActions.DELETE);
inputMap.put(KeyStroke.getKeyStroke("DELETE"), "delete");
actionMap.put("delete", action);
// Ctrl C
action = ActionManager.getAction(JajukActions.COPY);
inputMap.put(KeyStroke.getKeyStroke("ctrl C"), "copy");
actionMap.put("copy", action);
// Ctrl X
action = ActionManager.getAction(JajukActions.CUT);
inputMap.put(KeyStroke.getKeyStroke("ctrl X"), "cut");
actionMap.put("cut", action);
// Ctrl V
action = ActionManager.getAction(JajukActions.PASTE);
inputMap.put(KeyStroke.getKeyStroke("ctrl V"), "paste");
actionMap.put("paste", action);
// Properties ALT/ENTER
action = ActionManager.getAction(JajukActions.SHOW_PROPERTIES);
inputMap.put(KeyStroke.getKeyStroke("alt ENTER"), "properties");
actionMap.put("properties", action);
// Rename / F2
action = ActionManager.getAction(JajukActions.RENAME);
inputMap.put(KeyStroke.getKeyStroke("F2"), "rename");
actionMap.put("rename", action);
}
/*
* (non-Javadoc)
*
* @see org.jajuk.events.Observer#update(org.jajuk.events.JajukEvent)
*/
@Override
public void update(JajukEvent event) {
final JajukEvents subject = event.getSubject();
if (JajukEvents.RATE_CHANGED.equals(subject)) {
// Make sure that preference menu icon is refreshed
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
pjmTracks.resetUI(alSelected);
}
});
} else if (subject.equals(JajukEvents.DEVICE_MOUNT)
|| subject.equals(JajukEvents.DEVICE_UNMOUNT) || subject.equals(JajukEvents.DEVICE_REFRESH)
|| subject.equals(JajukEvents.PARAMETERS_CHANGE)) {
SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>() {
@Override
public Void doInBackground() {
if (jspTree != null) {
pos = jspTree.getVerticalScrollBar().getValue();
}
populateTree();
return null;
}
@Override
public void done() {
SwingUtilities.updateComponentTreeUI(jtree);
bManualAction = false;
expand();
bManualAction = true;
// Make sure that preference menu icon is refreshed
pjmTracks.resetUI(alSelected);
// Reset last position in tree
// The scrollbar must be set after current EDT work to be effective,
// so queue it
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (jspTree != null) {
jspTree.getVerticalScrollBar().setValue(pos);
}
}
});
}
};
sw.execute();
} else if (JajukEvents.TABLE_SELECTION_CHANGED.equals(subject)) {
// Check if the sync tree table option is set for this tree
if (Conf.getBoolean(Const.CONF_SYNC_TABLE_TREE + "." + getID())) {
// Consume only events from the same perspective and different view
// (for table/tree synchronization)
Properties details = event.getDetails();
if (details != null) {
String sourcePerspective = details.getProperty(Const.DETAIL_PERSPECTIVE);
IView sourceView = (IView) details.get(Const.DETAIL_VIEW);
if (!(sourcePerspective.equals(getPerspective().getID()))
//source view is null if the table is outside a view like CDDB dialog
|| sourceView == null
// Same view ? ignore...
|| sourceView.getID().equals(getID())) {
return;
}
@SuppressWarnings("unchecked")
final List<Item> selection = (List<Item>) details.get(Const.DETAIL_SELECTION);
if (selection.size() == 0) {
return;
}
final Item item = selection.get(0);
// Do not scroll if a webradio has been launched
if (item instanceof org.jajuk.base.File | item instanceof org.jajuk.base.Track) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
selectNodes(selection);
scrollTo(item);
}
});
}
}
}
}
}
}