View Javadoc

1   package com.melloware.jukes.gui.tool;
2   
3   import java.util.Enumeration;
4   import java.util.prefs.Preferences;
5   
6   import javax.swing.event.TreeSelectionEvent;
7   import javax.swing.event.TreeSelectionListener;
8   import javax.swing.tree.DefaultMutableTreeNode;
9   import javax.swing.tree.DefaultTreeModel;
10  import javax.swing.tree.DefaultTreeSelectionModel;
11  import javax.swing.tree.TreeModel;
12  import javax.swing.tree.TreeNode;
13  import javax.swing.tree.TreePath;
14  import javax.swing.tree.TreeSelectionModel;
15  
16  import org.apache.commons.lang.StringUtils;
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  
20  import com.jgoodies.binding.beans.Model;
21  import com.jgoodies.uif.application.Application;
22  import com.jgoodies.uifextras.convenience.SetupManager;
23  import com.melloware.jukes.db.orm.AbstractJukesObject;
24  import com.melloware.jukes.db.orm.Artist;
25  import com.melloware.jukes.db.orm.Catalog;
26  import com.melloware.jukes.db.orm.Disc;
27  import com.melloware.jukes.db.orm.Track;
28  import com.melloware.jukes.gui.view.node.AbstractTreeNode;
29  import com.melloware.jukes.gui.view.node.ArtistNode;
30  import com.melloware.jukes.gui.view.node.DiscNode;
31  import com.melloware.jukes.gui.view.node.NavigationNode;
32  import com.melloware.jukes.gui.view.node.RootNode;
33  import com.melloware.jukes.gui.view.node.TrackNode;
34  
35  /**
36   * Provides bound bean properties for the catalog, navigation tree,
37   * navigation tree selection, selection type. Refers to the
38   * presentation settings and a submodule for the dynamic help.
39   * <p>
40   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
41   * @author Emil A. Lefkof III <info@melloware.com>
42   * @version 4.0
43   *
44   * @see Settings
45   * @see DynamicHelpModule
46   */
47  public final class MainModule
48      extends Model {
49  
50      private static final Log LOG = LogFactory.getLog(MainModule.class);
51  
52      public static final String PROPERTYNAME_CATALOG = "catalog";
53      public static final String PROPERTYNAME_HIGHLIGHT = "highlight";
54      public static final String PROPERTYNAME_SELECTION = "selection";
55      public static final String PROPERTYNAME_SELECTION_TYPE = "selectionType";
56      public static final String PROPERTYNAME_NAVIGATION_TREE_MODEL = "navigationTreeModel";
57  
58      /**
59       * Refers to the submodule for the UI-related settings.
60       *
61       * @see #getSettings()
62       */
63      public static final Settings SETTINGS = new Settings();
64  
65      /**
66       * Holds the current catalog.
67       *
68       * @see #getCatalog()
69       * @see #setCatalog(Catalog)
70       */
71      private Catalog catalog;
72  
73      /**
74       * Holds the class of the current selection.
75       *
76       * @see #getSelection()
77       * @see #getSelectionType()
78       */
79      private Class selectionType;
80  
81      /**
82       * Holds the node that refers to the current selection.
83       * Used to report a potential change to its hosting tree model
84       * when the selection changes. This is because the node's label
85       * may have changed as part of the editor change.<p>
86       *
87       * This is a poor workaround that is based on the weak assumumption
88       * that this class's tree selection listener is the first to know
89       * about the node change.<p>
90       */
91      private NavigationNode selectedNode;
92  
93      /**
94       * Holds the current selection, an instance of Model.
95       *
96       * @see #getSelection()
97       * @see #getSelectionType()
98       */
99      private Object selection;
100 
101     /**
102      * Holds the model for the navigation tree.
103      * It changes everytime this module's catalog changes.
104      *
105      * @see #getNavigationTreeModel()
106      * @see #getNavigationTreeSelectionModel()
107      */
108     private TreeModel navigationTreeModel;
109 
110     /**
111      * Refers to the single selection model for the navigation tree.
112      * The selection is reset if the navigation tree model changes.
113      *
114      * @see #getNavigationTreeSelectionModel()
115      * @see #getNavigationTreeModel()
116      */
117     private final TreeSelectionModel navigationTreeSelectionModel = new DefaultTreeSelectionModel();
118 
119     /**
120      * Constructs a <code>MainModule</code> that has no catalog set,
121      * no selection and no tree model.
122      */
123     public MainModule() {
124         LOG.debug("MainModule created.");
125         restoreState();
126     }
127 
128     /**
129      * Returns the current catalog.
130      *
131      * @return the current catalog.
132      */
133     public Catalog getCatalog() {
134         return catalog;
135     }
136 
137     /**
138      * Returns the tree model for the navigation tree.
139      *
140      * @return the tree model for the navigation tree.
141      */
142     public TreeModel getNavigationTreeModel() {
143         return navigationTreeModel;
144     }
145 
146     /**
147      * Returns the fixed selection model for the navigation tree.
148      *
149      * @return the fixed selection model for the navigation tree.
150      */
151     public TreeSelectionModel getNavigationTreeSelectionModel() {
152         return navigationTreeSelectionModel;
153     }
154 
155     /**
156      * Gets the selectedNode.
157      * <p>
158      * @return Returns the selectedNode.
159      */
160     public NavigationNode getSelectedNode() {
161         return this.selectedNode;
162     }
163 
164     /**
165      * Returns the current selection, a domain object. This selection
166      * will be updated if the selection in the navigation tree changes.
167      * During the transition from one tree selection to another this
168      * value may hold the old or new selection.
169      *
170      * @return the domain object selected in the navigation tree.
171      *
172      * @see #getSelectionType()
173      */
174     public Object getSelection() {
175         return selection;
176     }
177 
178     /**
179      * Returns the class of the selected domain object.
180      *
181      * @return the class of the selected domain object.
182      *
183      * @see #getSelection()
184      */
185     public Class getSelectionType() {
186         return selectionType;
187     }
188 
189     /**
190      * Sets a new catalog.
191      *
192      * @param newCatalog   the catalog to set
193      *
194      * @throws NullPointerException if the new catalog is null
195      */
196     public void setCatalog(final Catalog newCatalog) {
197         if (newCatalog == null) {
198             throw new IllegalArgumentException("The catalog must not be null.");
199         }
200 
201         final Catalog oldCatalog = getCatalog();
202         catalog = newCatalog;
203         firePropertyChange(PROPERTYNAME_CATALOG, oldCatalog, newCatalog);
204         setNavigationTreeModel(createNavigationTreeModel(newCatalog));
205 
206     }
207 
208     /**
209      * Checks and answers whether the catalog's file path is valid.
210      *
211      * @return true if the catalog's file path is valid.
212      */
213     public boolean isCatalogFilePathValid() {
214         return false;
215     }
216 
217     /**
218      * Checks and answers if a catalog is loaded.
219      *
220      * @return true if a catalog is loaded.
221      */
222     public boolean hasCatalog() {
223         return getCatalog() != null;
224     }
225 
226     /**
227      * Refreshes the currently selected tree node.
228      * <p>
229      * @param domainObject the domain object may be useful
230      * @param operation NODE_INSERTED, NODE_DELETED, NODE_CHANGED
231      */
232     public void refreshSelection(final Object domainObject, final String operation) {
233         if (selectedNode == null) {
234             return;
235         }
236         final DefaultTreeModel treeModel = ((DefaultTreeModel)getNavigationTreeModel());
237         if (Resources.NODE_CHANGED.equals(operation)) {
238             treeModel.nodeChanged(selectedNode);
239             LOG.debug("Tree Node changed.");
240         } else if (Resources.NODE_DELETED.equals(operation)) {
241         	final TreeNode parent = selectedNode.getParent();
242             if (parent != null) {
243                 LOG.debug("Tree Node Deleted");
244                 treeModel.removeNodeFromParent((DefaultMutableTreeNode)selectedNode);
245                 setSelectedNode((AbstractTreeNode)parent);
246             }
247         }
248     }
249 
250     /**
251      * Refreshes the tree node from the database.
252      * <p>
253      * @throws NullPointerException if the new catalog is null
254      */
255     public void refreshTree() {
256         setCatalog(new Catalog(SETTINGS.getFilter()));
257         firePropertyChange(PROPERTYNAME_SELECTION, selection, null);
258     }
259 
260     /**
261      * Restores the application state from the user preferences.
262      */
263     public void restoreState() {
264         SETTINGS.restoreFrom(Application.getUserPreferences());
265     }
266 
267     /**
268      * Finds the selection in the tree and expands the node and sets the editor.
269      *
270      * @param newSelection   the selection to set
271      *
272      * @see #getSelection()
273      */
274     public void selectNodeInTree(final Object newSelection) {
275         final AbstractJukesObject selection = (AbstractJukesObject)newSelection;
276         AbstractTreeNode node = null;
277         String artistName = null;
278         String discName = null;
279         String trackName = null;
280 
281         if (selection instanceof Artist) {
282             artistName = selection.getName();
283         } else if (selection instanceof Disc) {
284             final Disc disc = (Disc)selection;
285             artistName = disc.getArtist().getName();
286             discName = selection.getName();
287         } else if (selection instanceof Track) {
288             final Track track = (Track)selection;
289             artistName = track.getDisc().getArtist().getName();
290             discName = track.getDisc().getName();
291             trackName = selection.getName();
292         }
293 
294         final DefaultTreeModel treeModel = ((DefaultTreeModel)navigationTreeModel);
295         final AbstractTreeNode root = (AbstractTreeNode)treeModel.getRoot();
296 
297         // loop through the artists
298         ArtistNode artistNode = null;
299         if (StringUtils.isNotBlank(artistName)) {
300             final Enumeration artistEnum = root.children();
301             while (artistEnum.hasMoreElements()) {
302                 artistNode = (ArtistNode)artistEnum.nextElement();
303                 if (StringUtils.equalsIgnoreCase(artistNode.toString(), artistName)) {
304                     node = artistNode;
305                     break;    // found it
306                 }
307             }
308         }
309 
310         // now get the disc
311         DiscNode discNode = null;
312         if ((StringUtils.isNotBlank(discName)) && (artistNode != null)) {
313             final Enumeration discEnum = artistNode.children();
314             while (discEnum.hasMoreElements()) {
315                 discNode = (DiscNode)discEnum.nextElement();
316                 if (StringUtils.equalsIgnoreCase(discNode.toString(), discName)) {
317                     node = discNode;
318                     break;    // found it
319                 }
320             }
321         }
322 
323         // now get the track
324         TrackNode trackNode = null;
325         if ((StringUtils.isNotBlank(trackName)) && (discNode != null)) {
326             final Enumeration trackEnum = discNode.children();
327             while (trackEnum.hasMoreElements()) {
328                 trackNode = (TrackNode)trackEnum.nextElement();
329                 if (StringUtils.equalsIgnoreCase(trackNode.toString(), trackName)) {
330                     node = trackNode;
331                     break;    // found it
332                 }
333             }
334         }
335 
336         if (node != null) {
337             // tree node found
338             setSelectedNode(node);
339 
340             // fire this property change to tell the tree to select the node
341             firePropertyChange(PROPERTYNAME_HIGHLIGHT, null, "Highlight");
342         }
343     }
344 
345     /**
346      * Stores the application state to the user preferences.
347      */
348     public void storeState() {
349         final Preferences prefs = Application.getUserPreferences();
350         SETTINGS.storeIn(prefs);
351         SetupManager.incrementUsageCounter();
352     }
353 
354     /**
355      * Sets a new tree model for the navigation tree.<p>
356      *
357      * @param newModel  the new tree model to set
358      */
359     private void setNavigationTreeModel(final TreeModel newModel) {
360     	final TreeModel oldModel = getNavigationTreeModel();
361         navigationTreeModel = newModel;
362         firePropertyChange(PROPERTYNAME_NAVIGATION_TREE_MODEL, oldModel, newModel);
363     }
364 
365     /**
366      * Sets the selection and reports the previously selected node as changed.<p>
367      *
368      * <strong>Note:</strong> This code requires a special order. It assumes
369      * that domain changes happen when the selection changes.<p>
370      *
371      * @param newSelectedNode   the new node selection
372      */
373     private void setSelectedNode(final NavigationNode newSelectedNode) {
374     	final NavigationNode oldSelectedNode = selectedNode;
375         selectedNode = newSelectedNode;
376         setSelection(newSelectedNode.getModel());
377         ((DefaultTreeModel)getNavigationTreeModel()).nodeChanged(oldSelectedNode);
378     }
379 
380     /**
381      * Sets a new domain object as selection. This method is invoked
382      * by the NavigationTreeSelectionChangeHandler that observes changes in
383      * the selection of the navigation tree.
384      *
385      * @param newSelection   the selection to set
386      *
387      * @see #getSelection()
388      */
389     private void setSelection(final Object newSelection) {
390     	final Object oldSelection = getSelection();
391         selection = newSelection;
392         setSelectionType(newSelection.getClass());
393         firePropertyChange(PROPERTYNAME_SELECTION, oldSelection, newSelection);
394     }
395 
396     /**
397      * Sets a new selection type. This method is invoked in
398      * <code>#setSelection</code> with the class of the new selection.
399      *
400      * @param newSelectionType   the selection type to set
401      *
402      * @see #getSelectionType()
403      */
404     private void setSelectionType(final Class newSelectionType) {
405     	final Class oldSelectionType = getSelectionType();
406         selectionType = newSelectionType;
407         if (equals(oldSelectionType, newSelectionType)) {
408             return;
409         }
410 
411         firePropertyChange(PROPERTYNAME_SELECTION_TYPE, oldSelectionType, newSelectionType);
412     }
413 
414     /**
415      * Creates and returns a tree model for the given catalog.
416      * Constructs a <code>RootNode</code> with the catalog and
417      * returns a <code>DefaultTreeModel</code> with this root.
418      *
419      * @return a TreeModel for the given catalog
420      */
421     private TreeModel createNavigationTreeModel(final Catalog catalog) {
422     	final RootNode rootNode = new RootNode(catalog);
423         rootNode.setSettings(SETTINGS);
424         final DefaultTreeModel model = new DefaultTreeModel(rootNode);
425         navigationTreeSelectionModel.setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
426         navigationTreeSelectionModel.addTreeSelectionListener(new NavigationTreeSelectionChangeHandler());
427         return model;
428     }
429 
430     // Listens to changes in the navigation tree node selection
431     // and updates the selection.
432     private class NavigationTreeSelectionChangeHandler
433         implements TreeSelectionListener {
434 
435         /**
436          * The selected node in the navidation tree has changed.
437          * Updates this module's selected domain object and in turn
438          * the selection type.<p>
439          *
440          * Does nothing if the new tree selection is <code>null</code>.
441          *
442          * @param evt    the event that describes the change
443          */
444         public void valueChanged(final TreeSelectionEvent evt) {
445         	final TreePath path = evt.getPath();
446         	final NavigationNode aSelectedNode = (NavigationNode)path.getLastPathComponent();
447             if (aSelectedNode != null) {
448                 setSelectedNode(aSelectedNode);
449             }
450         }
451 
452     }
453 
454 }