View Javadoc

1   package com.melloware.jukes.gui.view;
2   
3   import java.awt.Component;
4   import java.awt.Rectangle;
5   import java.awt.event.ActionEvent;
6   import java.awt.event.MouseAdapter;
7   import java.awt.event.MouseEvent;
8   import java.awt.event.MouseListener;
9   import java.beans.PropertyChangeEvent;
10  import java.beans.PropertyChangeListener;
11  
12  import javax.swing.Action;
13  import javax.swing.JComponent;
14  import javax.swing.JPopupMenu;
15  import javax.swing.JTree;
16  import javax.swing.ToolTipManager;
17  import javax.swing.event.TreeExpansionEvent;
18  import javax.swing.event.TreeExpansionListener;
19  import javax.swing.event.TreeWillExpandListener;
20  import javax.swing.tree.DefaultTreeCellRenderer;
21  import javax.swing.tree.DefaultTreeModel;
22  import javax.swing.tree.ExpandVetoException;
23  import javax.swing.tree.TreeModel;
24  import javax.swing.tree.TreeNode;
25  import javax.swing.tree.TreePath;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  import skt.swing.SwingUtil;
31  import skt.swing.search.IncrementalSearchKeyListener;
32  import skt.swing.search.TreeFindAction;
33  
34  import com.jgoodies.uif.action.ActionManager;
35  import com.jgoodies.uif.application.Application;
36  import com.jgoodies.uif.component.UIFTree;
37  import com.jgoodies.uifextras.util.UIFactory;
38  import com.melloware.jukes.gui.tool.Actions;
39  import com.melloware.jukes.gui.tool.MainModule;
40  import com.melloware.jukes.gui.tool.Resources;
41  import com.melloware.jukes.gui.view.node.AbstractTreeNode;
42  import com.melloware.jukes.gui.view.node.NavigationNode;
43  import com.melloware.jukes.gui.view.node.TrackNode;
44  import com.melloware.jukes.util.GuiUtil;
45  
46  /**
47   * Builds the navigation panel that is primarily intended to display
48   * a tree of <code>NavigationNode</code> instances. Since Skeleton Pro
49   * has no tree data at application startup time, there's an empty white panel
50   * that is displayed as long as there's no data.
51   * The first time the tree gets a tree model, the empty panel is replaced
52   * by the tree. The empty panel and the tree are switched in a CardPanel.<p>
53   *
54   * This panel is embedded in a stripped scrollpane that in turn is contained
55   * in a <code>SimpleInternalFrame</code> that in turn is contained
56   * in the main page.
57   * <p>
58   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
59   * @author Emil A. Lefkof III <info@melloware.com>
60   * @version 4.0
61   */
62  final class NavigationPanelBuilder {
63  
64      private static final Log LOG = LogFactory.getLog(NavigationPanelBuilder.class);
65  
66      /**
67       * Refers to the tree that displays the navigation nodes.
68       *
69       * @see #initComponents()
70       */
71      private UIFTree tree;
72  
73      /**
74       * Refers to the module that holds the tree model and tree selection model.
75       */
76      private final MainModule module;
77  
78      /**
79       * Constructs a <code>NavigationPanelBuilder</code> for the given module.
80       *
81       * @param mainModule  provides the tree model and tree selection model
82       */
83      NavigationPanelBuilder(MainModule mainModule) {
84          this.module = mainModule;
85          initComponents();
86          mainModule.addPropertyChangeListener(MainModule.PROPERTYNAME_NAVIGATION_TREE_MODEL,
87                                               new TreeModelChangeHandler());
88          SelectionChangeHandler selectionHandler = new SelectionChangeHandler();
89          mainModule.addPropertyChangeListener(MainModule.PROPERTYNAME_SELECTION, selectionHandler);
90          mainModule.addPropertyChangeListener(MainModule.PROPERTYNAME_HIGHLIGHT, selectionHandler);
91      }
92  
93      /**
94       * Builds and returns a CardPanel with two cards: one for the
95       * navigation tree, another for the empty card..
96       */
97      JComponent build() {
98          return UIFactory.createStrippedScrollPane(tree);
99      }
100 
101     /**
102      * Creates, binds and configures the navigation tree
103      * used to display the <code>NavigationNodes</code>.
104      * Requests the tree model and the tree selection model
105      * from the MainModule.
106      */
107     private void initComponents() {
108         tree = new RefreshedTree(module.getNavigationTreeModel());
109         tree.setSelectionModel(module.getNavigationTreeSelectionModel());
110         final NavigationTreeExpandListener expander = new NavigationTreeExpandListener();
111         tree.addTreeWillExpandListener(expander);
112         tree.addTreeExpansionListener(expander);
113         tree.putClientProperty("JTree.lineStyle", "None");
114         tree.setRootVisible(false);
115         tree.setShowsRootHandles(true);
116         tree.setCellRenderer(new TreeCellRenderer());
117 
118         // add the incremental searching to the tree
119         final TreeFindAction findAction = new TreeFindAction(true);
120         SwingUtil.installActions(tree, new Action[] { new TreeFindAction(true), findAction });
121         tree.addKeyListener(new IncrementalSearchKeyListener(findAction));
122 
123         // Add listener to components that can bring up popup menus.
124         final JPopupMenu popup = MainMenuBuilder.buildPlayerPopupMenu(tree);
125         MouseListener popupListener = new TreePopupListener(popup);
126         tree.addMouseListener(popupListener);
127 
128         tree.setEditable(false);
129         tree.setScrollsOnExpand(true);
130 
131         // Enable tool tips.
132         ToolTipManager.sharedInstance().registerComponent(tree);
133     }
134 
135     /**
136      * Updates the expansion state of the selected node;
137      * honors the auto expand and collapse settings.
138      */
139     private void updateExpansionState() {
140         MainFrame mf = (MainFrame)Application.getDefaultParentFrame();
141         AbstractTreeNode node = (AbstractTreeNode)mf.getMainModule().getSelectedNode();
142 
143         if (node != null) {
144             final DefaultTreeModel treeModel = ((DefaultTreeModel)tree.getModel());
145             final TreeNode[] nodes = treeModel.getPathToRoot(node);
146             final TreePath path = new TreePath(nodes);
147             boolean expanded = tree.isExpanded(path);
148             if (expanded) {
149                 tree.expandPath(path);
150             }
151         }
152     }
153 
154     /**
155      * Sets a new tree model.
156      *
157      * @param newModel   the new tree model
158      */
159     private void updateModel(TreeModel newModel) {
160         tree.setModel(newModel);
161         // tree.setSelectionRow(0);
162     }
163 
164     /**
165      * Updates the selection state of the node.
166      */
167     private void updateSelectedState() {
168         MainFrame mf = (MainFrame)Application.getDefaultParentFrame();
169         AbstractTreeNode node = (AbstractTreeNode)mf.getMainModule().getSelectedNode();
170 
171         if (node != null) {
172             final DefaultTreeModel treeModel = ((DefaultTreeModel)tree.getModel());
173             final TreeNode[] nodes = treeModel.getPathToRoot(node);
174             final TreePath path = new TreePath(nodes);
175 
176             // tree.setSelectionPath(path);
177             tree.scrollPathToVisible(path);
178             tree.makeVisible(path);
179             tree.setSelectionPath(path);
180         }
181     }
182 
183     // Renders icons and labels for the NavigationNodes.
184     private static class TreeCellRenderer
185         extends DefaultTreeCellRenderer {
186 
187         public Component getTreeCellRendererComponent(JTree tree,
188                                                       Object value,
189                                                       boolean sel,
190                                                       boolean expanded,
191                                                       boolean leaf,
192                                                       int row,
193                                                       boolean focus) {
194             super.getTreeCellRendererComponent(tree, value, selected, expanded, false, row, focus);
195             NavigationNode node = (NavigationNode)value;
196             if (node.getFontColor() == null) {
197                 this.setForeground(sel ? getTextSelectionColor() : getTextNonSelectionColor());
198             } else {
199                 this.setForeground(sel ? getTextSelectionColor() : node.getFontColor());
200             }
201 
202             selected = sel;
203             UIFTree extTree = (UIFTree)tree;
204             this.setFont((node.getFont() == null) ? extTree.getFont() : node.getFont());
205             this.setIcon(node.getNodeIcon(sel));
206             this.setText(node.getName());
207             this.setToolTipText(node.getName());
208             return this;
209         }
210     }
211 
212     // Listens to tree expand collapse events
213     private static class NavigationTreeExpandListener
214         implements TreeWillExpandListener,
215                    TreeExpansionListener {
216 
217         /**
218          * The tree has just finished collapsing.
219          */
220         public void treeCollapsed(TreeExpansionEvent aEvent) {
221             GuiUtil.setBusyCursor(Application.getDefaultParentFrame(), false);
222         }
223 
224         /**
225          * The tree has just finished expanding.
226          */
227         public void treeExpanded(TreeExpansionEvent aEvent) {
228             GuiUtil.setBusyCursor(Application.getDefaultParentFrame(), false);
229         }
230 
231         /**
232          * The tree is about to be collapsed.
233          */
234         public void treeWillCollapse(TreeExpansionEvent aEvent)
235                               throws ExpandVetoException {
236             GuiUtil.setBusyCursor(Application.getDefaultParentFrame(), true);
237         }
238 
239         /**
240          * The tree is about to be expanded.
241          */
242         public void treeWillExpand(TreeExpansionEvent aEvent)
243                             throws ExpandVetoException {
244             AbstractTreeNode node = (AbstractTreeNode)aEvent.getPath().getLastPathComponent();
245             GuiUtil.setBusyCursor(Application.getDefaultParentFrame(), true);
246             node.loadChildren();
247         }
248 
249     }
250 
251     // A tree that refreshes the cell renderer when the
252     private static class RefreshedTree
253         extends UIFTree {
254 
255         public RefreshedTree(TreeModel treeModel) {
256             super(treeModel);
257         }
258 
259         /* (non-Javadoc)
260          * @see javax.swing.JTree#getScrollableUnitIncrement(java.awt.Rectangle, int, int)
261          */
262         public int getScrollableUnitIncrement(Rectangle aVisibleRect, int aOrientation, int aDirection) {
263             // control the speed effect of the scrolling
264             return MainModule.SETTINGS.getCatalogScrollUnits();
265         }
266 
267         /**
268          * Updates the UI; updates the selection path and cell renderer.
269          */
270         public void updateUI() {
271             super.updateUI();
272             setSelectionPath(getSelectionPath());
273             setCellRenderer(new TreeCellRenderer());
274         }
275 
276     }
277 
278     // Updates the expansion state if the tree selection has changed.
279     private class SelectionChangeHandler
280         implements PropertyChangeListener {
281 
282         /**
283          * The module's selection has changed.
284          * Updates the expansion state.
285          *
286          * @param evt   describes the property change
287          */
288         public void propertyChange(PropertyChangeEvent evt) {
289 
290             if (evt.getPropertyName().equals(MainModule.PROPERTYNAME_SELECTION)) {
291                 LOG.debug("Selected");
292                 updateExpansionState();
293             } else if (evt.getPropertyName().equals(MainModule.PROPERTYNAME_HIGHLIGHT)) {
294                 LOG.debug("Highlighted");
295                 updateSelectedState();
296             }
297         }
298     }
299 
300     // Listens to changes in the module's navigation tree model and rebuilds the tree.
301     private class TreeModelChangeHandler
302         implements PropertyChangeListener {
303 
304         /**
305          * The module's navigation tree model has changed.
306          * Update this navigator's tree model switch to the tree card
307          * and expand the top-level nodes.
308          *
309          * @param evt   describes the property change
310          */
311         public void propertyChange(PropertyChangeEvent evt) {
312             TreeModel newModel = (TreeModel)evt.getNewValue();
313             updateModel(newModel);
314             // cardPanel.showCard(TREE_CARD);
315             // tree.setSelectionRow(0);
316         }
317     }
318 
319     // shows popups on right click of tree nodes
320     private static class TreePopupListener
321         extends MouseAdapter {
322 
323         final JPopupMenu popup;
324 
325         public TreePopupListener(JPopupMenu popup) {
326             this.popup = popup;
327         }
328 
329         public void mousePressed(MouseEvent evt) {
330             maybeShowPopup(evt);
331             boolean queue = false;
332 
333             JTree tree = (JTree)evt.getComponent();
334 
335             // get the node at location x, y
336             TreePath path = tree.getPathForLocation(evt.getX(), evt.getY());
337 
338             if (path == null) {
339                 return;
340             }
341 
342             // left mouse button pressed twice, queue in playlist if track
343             if (((evt.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) && (evt.getClickCount() == 2)) {
344                 LOG.debug("[Treenode left double clicked].");
345                 // if it's not a Track, don't do anything
346                 if (!(path.getLastPathComponent() instanceof TrackNode)) {
347                     return;
348                 }
349                 queue = true;
350             }
351 
352             // middle mouse button pressed once queue in playlist
353             if (((evt.getModifiers() & MouseEvent.BUTTON2_MASK) != 0) && (evt.getClickCount() == 1)) {
354                 LOG.debug("[Treenode mouse wheel clicked].");
355                 queue = true;
356             }
357 
358             if (queue) {
359                 JComponent source = (JComponent)evt.getSource();
360                 final ActionEvent event = new ActionEvent(source, 1, Actions.PLAYER_QUEUE_ID);
361                 source.putClientProperty(Resources.EDITOR_COMPONENT, source);
362                 ActionManager.get(Actions.PLAYER_QUEUE_ID).actionPerformed(event);
363             }
364         }
365 
366         public void mouseReleased(MouseEvent e) {
367             maybeShowPopup(e);
368         }
369 
370         private void maybeShowPopup(MouseEvent e) {
371             if ((e.isPopupTrigger() || ((e.getModifiers() & MouseEvent.BUTTON2_MASK) != 0))) {
372                 JTree tree = (JTree)e.getComponent();
373 
374                 // get the node at location x, y
375                 TreePath path = tree.getPathForLocation(e.getX(), e.getY());
376 
377                 if (path == null) {
378                     return;
379                 }
380 
381                 // if it's not a AbstractTreeNode, don't do anything
382                 if (!(path.getLastPathComponent() instanceof AbstractTreeNode)) {
383                     return;
384                 }
385                 AbstractTreeNode node = (AbstractTreeNode)path.getLastPathComponent();
386                 if (node instanceof TrackNode) {
387                     // append the track to the selection
388                     tree.addSelectionPath(path);
389                 } else {
390                     tree.setSelectionPath(path);
391                 }
392 
393                 // popup the menu
394                 if (e.isPopupTrigger()) {
395                     popup.show(e.getComponent(), e.getX(), e.getY());
396                 }
397             }
398         }
399     }
400 
401 }