View Javadoc

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