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