View Javadoc

1   package com.melloware.jukes.gui.tool;
2   
3   import java.awt.Component;
4   import java.awt.Desktop;
5   import java.awt.EventQueue;
6   import java.awt.Frame;
7   import java.awt.event.ActionEvent;
8   import java.io.File;
9   import java.io.FileInputStream;
10  import java.io.FileOutputStream;
11  import java.io.IOException;
12  import java.net.URI;
13  import java.net.URL;
14  import java.util.ArrayList;
15  import java.util.Collection;
16  import java.util.HashMap;
17  import java.util.Iterator;
18  import java.util.prefs.Preferences;
19  
20  import javax.swing.JComponent;
21  import javax.swing.JFileChooser;
22  import javax.swing.JList;
23  import javax.swing.JOptionPane;
24  import javax.swing.JTable;
25  import javax.swing.JTextArea;
26  import javax.swing.JToggleButton;
27  import javax.swing.JTree;
28  import javax.swing.UIManager;
29  import javax.swing.filechooser.FileFilter;
30  import javax.swing.text.JTextComponent;
31  import javax.swing.tree.TreePath;
32  
33  import javazoom.jlgui.basicplayer.BasicPlayer;
34  import net.sf.jasperreports.engine.JRException;
35  import net.sf.jasperreports.engine.JasperFillManager;
36  import net.sf.jasperreports.engine.JasperPrint;
37  import net.sf.jasperreports.view.JasperViewer;
38  
39  import org.apache.commons.io.FileUtils;
40  import org.apache.commons.lang.StringUtils;
41  import org.apache.commons.lang.SystemUtils;
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  import org.hibernate.HibernateException;
45  import org.hibernate.exception.JDBCConnectionException;
46  
47  import com.jgoodies.uif.application.Application;
48  import com.jgoodies.uifextras.convenience.DefaultAboutDialog;
49  import com.jgoodies.uifextras.convenience.SetupManager;
50  import com.jgoodies.uifextras.convenience.TipOfTheDayDialog;
51  import com.l2fprod.common.swing.JDirectoryChooser;
52  import com.melloware.jukes.db.Database;
53  import com.melloware.jukes.db.HibernateDao;
54  import com.melloware.jukes.db.HibernateUtil;
55  import com.melloware.jukes.db.orm.Track;
56  import com.melloware.jukes.exception.InfrastructureException;
57  import com.melloware.jukes.file.FileUtil;
58  import com.melloware.jukes.file.Playlist;
59  import com.melloware.jukes.file.filter.FilterFactory;
60  import com.melloware.jukes.gui.view.FilterPanel;
61  import com.melloware.jukes.gui.view.MainFrame;
62  import com.melloware.jukes.gui.view.PlaylistPanel;
63  import com.melloware.jukes.gui.view.component.AlbumImage;
64  import com.melloware.jukes.gui.view.dialogs.DifferenceToolDialog;
65  import com.melloware.jukes.gui.view.dialogs.DiscAddDialog;
66  import com.melloware.jukes.gui.view.dialogs.DiscFindDialog;
67  import com.melloware.jukes.gui.view.dialogs.DiscRemoveDialog;
68  import com.melloware.jukes.gui.view.dialogs.LocationChangeDialog;
69  import com.melloware.jukes.gui.view.dialogs.MemoryDialog;
70  import com.melloware.jukes.gui.view.dialogs.SearchDialog;
71  import com.melloware.jukes.gui.view.dialogs.SearchTableModel;
72  import com.melloware.jukes.gui.view.dialogs.StatisticsDialog;
73  import com.melloware.jukes.gui.view.editor.AbstractEditor;
74  import com.melloware.jukes.gui.view.node.AbstractTreeNode;
75  import com.melloware.jukes.gui.view.preferences.PreferencesDialog;
76  import com.melloware.jukes.util.GuiUtil;
77  import com.melloware.jukes.util.JukesValidationMessage;
78  import com.melloware.jukes.util.MessageUtil;
79  
80  /**
81   * Provides all application-level behavior. Most of the methods in this class
82   * will be invoked by <code>AbstractActions</code> as defined in the
83   * <code>Actions</code> class.
84   * <p>
85   * Copyright (c) 2006 Melloware, Inc. <http://www.melloware.com>
86   * @author Emil A. Lefkof III <info@melloware.com>
87   * @version 4.0
88   */
89  public final class MainController {
90  
91     private static final Log LOG = LogFactory.getLog(MainController.class);
92     private static final String LINE_BREAK = "\n\n";
93     private static final String ERROR_WRITING_FILE = "Error writing file: ";
94     private static final String ERROR_URL = " Enter this url in your browser: ";
95  
96     /**
97      * Refers to the module that provides all high-level models. Used to modify
98      * the project and access the domain object tree.
99      * @see #getMainModule()
100     */
101    private final MainModule mainModule;
102 
103    // Instance Creation ******************************************************
104 
105    /**
106     * Constructs the <code>MainController</code> for the given main module.
107     * Many methods require that the default parent frame is set once it is
108     * available.
109     * @param mainModule provides bound properties and high-level models
110     * @see #setDefaultParentFrame(Frame)
111     */
112    public MainController(final MainModule mainModule) {
113       this.mainModule = mainModule;
114 
115    }
116 
117    /**
118     * Backs up the database to zip file.
119     * <p>
120     * @param aEvent the Actionevent fired
121     */
122    @SuppressWarnings("deprecation")
123    public void backupTool(final ActionEvent aEvent) {
124       LOG.debug("Backup Database");
125       if (MessageUtil.confirmBackup(getDefaultParentFrame())) {
126          final File zip = MainModule.SETTINGS.getFileBackup();
127 
128          Database.backupDatabase(HibernateUtil.getSession().connection(), zip.getAbsolutePath());
129 
130          MessageUtil.showTaskCompleted(getDefaultParentFrame());
131       }
132 
133    }
134 
135    /**
136     * Checks if we shall show a tip of the day: asks the TipOfTheDayDialog
137     * whether it is enabled, and the SetupManager, if we are not running for the
138     * first time. We don't want to disturb the user the first time, where we
139     * already have opened some extra panels from the setup process.
140     * <p>
141     * Opens the tip of the day dialog in the event dispatch thread.
142     */
143    public void checkForOpenTipOfTheDayDialog() {
144       if ((SetupManager.usageCount() > 1) && (TipOfTheDayDialog.isShowingTips())) {
145          EventQueue.invokeLater(new Runnable() {
146             public void run() {
147                openTipOfTheDayDialog();
148             }
149          });
150       }
151    }
152 
153    /**
154     * Opens the Directory chooser dialog.
155     * <p>
156     * @param aEvent the Action Event fired for this button
157     */
158    public void chooseDirectory(final ActionEvent aEvent) {
159       final JComponent button = (JComponent) aEvent.getSource();
160       final JTextComponent text = (JTextComponent) button.getClientProperty(Resources.TEXT_COMPONENT);
161       final JDirectoryChooser chooser = new JDirectoryChooser();
162       final JTextArea accessory = new JTextArea("Select Directory");
163       chooser.setSelectedFile(new File(text.getText()));
164       accessory.setLineWrap(true);
165       accessory.setWrapStyleWord(true);
166       accessory.setEditable(false);
167       accessory.setOpaque(false);
168       accessory.setFont(UIManager.getFont("Tree.font"));
169       chooser.setAccessory(accessory);
170       chooser.setMultiSelectionEnabled(false);
171 
172       final int choice = chooser.showOpenDialog(button);
173       if (choice == JDirectoryChooser.APPROVE_OPTION) {
174          final File dir = chooser.getSelectedFile();
175          LOG.debug("Directory selected: " + dir.getAbsolutePath());
176          text.setText(dir.getAbsolutePath());
177       }
178    }
179 
180    /**
181     * Chooses a file and puts it in the text component listed.
182     * <p>
183     * @param aEvent the Action Event fired for this button
184     */
185    public void chooseFile(final ActionEvent aEvent) {
186       final JComponent button = (JComponent) aEvent.getSource();
187       final JTextComponent text = (JTextComponent) button.getClientProperty(Resources.TEXT_COMPONENT);
188       final JFileChooser chooser = new JFileChooser();
189       chooser.setDialogTitle((String) button.getClientProperty(Resources.FILE_CHOOSER_TITLE));
190       chooser.setFileFilter((FileFilter) button.getClientProperty(Resources.FILE_CHOOSER_FILTER));
191       chooser.setMultiSelectionEnabled(false);
192       chooser.setFileHidingEnabled(true);
193       final int returnVal = chooser.showOpenDialog(this.getDefaultParentFrame());
194       if (returnVal != JFileChooser.APPROVE_OPTION) {
195          return;
196       }
197       final File file = chooser.getSelectedFile();
198       text.setText(file.getAbsolutePath());
199       if (LOG.isDebugEnabled()) {
200          LOG.debug(file.getAbsolutePath());
201 
202       }
203    }
204 
205    /**
206     * Commits the object to the database and updates tags if necessary.
207     * <p>
208     * @param aEvent the Action Event fired for this button
209     */
210    public void commit(final ActionEvent aEvent) {
211       LOG.debug("Commit Changes");
212       final JComponent button = (JComponent) aEvent.getSource();
213       final AbstractEditor editor = (AbstractEditor) button.getClientProperty(Resources.EDITOR_COMPONENT);
214       editor.commit();
215    }
216 
217    /**
218     * Connects to the database and refreshes the whole application.
219     * <p>
220     * @param aEvent the Action Event fired for this button
221     */
222    @SuppressWarnings("deprecation")
223    public void connect(final ActionEvent aEvent) {
224       LOG.debug("Connecting to database.");
225       try {
226          // first shut the database and Hibernate down.
227          HibernateUtil.shutdown();
228          Database.shutdown();
229 
230          // now try and connect to the database listed in the preferences
231          String dbLocation = MainModule.SETTINGS.getDatabaseLocation().getAbsolutePath();
232          dbLocation = dbLocation + SystemUtils.FILE_SEPARATOR + Resources.APPLICATION_LOCATION;
233 
234          // start database
235          Database.startup(dbLocation, Resources.APPLICATION_LOCATION);
236 
237          // initializes Hibernate
238          HibernateUtil.initialize();
239          HibernateUtil.getSession().clear();
240 
241          // set the write delay on the HSQL database so writes are immediate
242          Database.setWriteDelay(HibernateUtil.getSession().connection(), "FALSE");
243 
244          // now refresh the tree
245          getMainModule().refreshTree();
246       } catch (JDBCConnectionException ex) {
247          LOG.error("Not a valid connection URL. Please try another URL.");
248       } catch (Exception ex) {
249          LOG.error("Error trying to connect to the database.", ex);
250          System.exit(1);
251       }
252    }
253 
254    /**
255     * Redirects to the www.melloware.com homepage.
256     * <p>
257     * @param aEvent the Action Event fired for this button
258     */
259    public void contactUs(final ActionEvent aEvent) {
260       LOG.debug("Contact Us");
261       try {
262          if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
263             Desktop.getDesktop().browse(new URI(Resources.APPLICATION_URL));
264          }
265       } catch (UnsupportedOperationException ex) {
266          LOG.warn(ex.getMessage() + LINE_BREAK + ERROR_URL + Resources.APPLICATION_URL);
267       } catch (Exception ex) {
268          LOG.warn(ex.getMessage() + LINE_BREAK + ERROR_URL + Resources.APPLICATION_URL);
269       }
270    }
271 
272    /**
273     * Deletes the object and all its descendants but not any files they may
274     * point to.
275     * <p>
276     * @param aEvent the Action Event fired for this button
277     */
278    public void delete(final ActionEvent aEvent) {
279       LOG.debug("Delete Item");
280       final JComponent source = (JComponent) aEvent.getSource();
281       final Object editor = (Object) source.getClientProperty(Resources.EDITOR_COMPONENT);
282       if (editor == null) {
283          return;
284       }
285 
286       if (editor instanceof AbstractEditor) {
287          ((AbstractEditor) editor).delete();
288       } else if (editor instanceof JTree) {
289          final JTree tree = (JTree) editor;
290          final TreePath path = tree.getSelectionPath();
291 
292          // if it's not a AbstractTreeNode, don't do anything
293          if (path.getLastPathComponent() instanceof AbstractTreeNode) {
294             LOG.debug("Tree Node Delete");
295             ((AbstractTreeNode) path.getLastPathComponent()).delete();
296          }
297       }
298 
299    }
300 
301    /**
302     * Displays the difference tool dialog.
303     * <p>
304     * @param aEvent the Action Event fired for this button
305     */
306    public void differenceTool(final ActionEvent aEvent) {
307       LOG.debug("Difference Tool Dialog");
308       new DifferenceToolDialog(getDefaultParentFrame(), MainModule.SETTINGS).open();
309 
310    }
311 
312    /**
313     * Adds a single disc to the catalog by making the user select a directory.
314     * <p>
315     * @param aEvent the Action Event fired for this button
316     */
317    public void discAdd(final ActionEvent aEvent) {
318       LOG.debug("Add New Disc");
319       final JFileChooser openDialog = new JFileChooser();
320       openDialog.setDialogTitle("Add New Disc");
321       final File currentDir = MainModule.SETTINGS.getStartInDirectory();
322       openDialog.setCurrentDirectory(currentDir);
323       openDialog.setFileFilter(FilterFactory.musicFileFilter());
324       openDialog.setMultiSelectionEnabled(false);
325       openDialog.setSelectedFile(null);
326       final int returnVal = openDialog.showOpenDialog(this.getDefaultParentFrame());
327       if (returnVal != JFileChooser.APPROVE_OPTION) {
328          return;
329       }
330       final File file = openDialog.getSelectedFile();
331 
332       LOG.debug(file.getAbsolutePath());
333 
334       new DiscAddDialog(getDefaultParentFrame(), MainModule.SETTINGS, file).open();
335    }
336 
337    /**
338     * Updates all of the comments at once.
339     * <p>
340     * @param aEvent the ActionEvent fired
341     */
342    public void discAddComments(final ActionEvent aEvent) {
343       LOG.debug("Update comments all at once.");
344       final JComponent button = (JComponent) aEvent.getSource();
345       final Object editor = (Object) button.getClientProperty(Resources.EDITOR_COMPONENT);
346       if ((editor != null) && (editor instanceof DiscAddDialog)) {
347          ((DiscAddDialog) editor).updateComments();
348       }
349    }
350 
351    /**
352     * Resets all tracks titles based on filename.
353     * <p>
354     * @param aEvent the ActionEvent fired
355     */
356    public void discAddResetFromFilename(final ActionEvent aEvent) {
357       LOG.debug("Reset Titles from filenames.");
358       final JComponent button = (JComponent) aEvent.getSource();
359       final Object editor = (Object) button.getClientProperty(Resources.EDITOR_COMPONENT);
360       if ((editor != null) && (editor instanceof DiscAddDialog)) {
361          ((DiscAddDialog) editor).resetFromFilenames();
362       }
363    }
364 
365    /**
366     * Resets all track numbers in a disc if they are really screwed up.
367     * <p>
368     * @param aEvent the ActionEvent fired
369     */
370    public void discAddResetTrackNumbers(final ActionEvent aEvent) {
371       LOG.debug("Reset Track Numbers.");
372       final JComponent button = (JComponent) aEvent.getSource();
373       final Object editor = (Object) button.getClientProperty(Resources.EDITOR_COMPONENT);
374       if ((editor != null) && (editor instanceof DiscAddDialog)) {
375          ((DiscAddDialog) editor).resetTrackNumbers();
376       }
377    }
378 
379    /**
380     * Title cases all tracks in a disc.
381     * <p>
382     * @param aEvent the ActionEvent fired
383     */
384    public void discAddTitleCase(final ActionEvent aEvent) {
385       LOG.debug("Title case all tracks.");
386       final JComponent button = (JComponent) aEvent.getSource();
387       final Object editor = (Object) button.getClientProperty(Resources.EDITOR_COMPONENT);
388       if ((editor != null) && (editor instanceof DiscAddDialog)) {
389          ((DiscAddDialog) editor).titleCase();
390       }
391    }
392 
393    /**
394     * Uses a file chooser to let the user select another cover image.
395     * <p>
396     * @param aEvent the Action Event fired for this button
397     */
398    public void discCoverImage(final ActionEvent aEvent) {
399       LOG.debug("Finding new cover image");
400       final JComponent button = (JComponent) aEvent.getSource();
401 
402       final Object editor = (Object) button.getClientProperty(Resources.EDITOR_COMPONENT);
403       LOG.debug(editor);
404       if (editor != null) {
405          if (editor instanceof AbstractEditor) {
406             ((AbstractEditor) editor).findCover();
407          } else if (editor instanceof DiscAddDialog) {
408             ((DiscAddDialog) editor).findCover();
409          }
410       }
411    }
412 
413    /**
414     * Opens the Disc Finder dialog.
415     * <p>
416     * @param aEvent the Action Event fired for this button
417     */
418    public void discFinder(final ActionEvent aEvent) {
419       LOG.debug("Disc Finder Dialog");
420       new DiscFindDialog(getDefaultParentFrame(), MainModule.SETTINGS).open();
421    }
422 
423    /**
424     * Opens the Disc Remover dialog.
425     * <p>
426     * @param aEvent the Action Event fired for this button
427     */
428    public void discRemover(final ActionEvent aEvent) {
429       LOG.debug("Disc Remover Dialog");
430       new DiscRemoveDialog(getDefaultParentFrame()).open();
431    }
432 
433    /**
434     * Uses the Amazon.com web service to find album information and covers.
435     * <p>
436     * @param aEvent the Action Event fired for this button
437     */
438    public void discWebSearch(final ActionEvent aEvent) {
439       LOG.debug("Web Search");
440       final JComponent button = (JComponent) aEvent.getSource();
441       final Object editor = (Object) button.getClientProperty(Resources.EDITOR_COMPONENT);
442       if (editor != null) {
443          if (editor instanceof AbstractEditor) {
444             ((AbstractEditor) editor).webSearch();
445          } else if (editor instanceof DiscAddDialog) {
446             ((DiscAddDialog) editor).webSearch();
447          }
448       }
449    }
450 
451    /**
452     * Launches a browser and send to the PayPal website
453     */
454    public void donate() {
455       try {
456          if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
457             Desktop.getDesktop().browse(new URL(Resources.APPLICATION_DONATE_URL).toURI());
458          }
459       } catch (UnsupportedOperationException ex) {
460          LOG.warn(ex.getMessage() + LINE_BREAK + ERROR_URL + Resources.APPLICATION_DONATE_URL);
461       } catch (Exception ex) {
462          LOG.warn(ex.getMessage() + LINE_BREAK + ERROR_URL + Resources.APPLICATION_DONATE_URL);
463       }
464    }
465 
466    /**
467     * Exports a catalog to a text file the user selects with a chooser.
468     * <p>
469     * @param aEvent the Action Event fired for this button
470     */
471    @SuppressWarnings("unchecked")
472    public void exportCatalog(final ActionEvent aEvent) {
473       LOG.debug("Export Catalog");
474       final JFileChooser chooser = new JFileChooser();
475       chooser.setDialogTitle("Export Catalog");
476       chooser.setFileFilter(FilterFactory.textFileFilter());
477       chooser.setMultiSelectionEnabled(false);
478       chooser.setFileHidingEnabled(true);
479       final int returnVal = chooser.showSaveDialog(this.getDefaultParentFrame());
480       if (returnVal != JFileChooser.APPROVE_OPTION) {
481          return;
482       }
483       File file = chooser.getSelectedFile();
484       if (LOG.isDebugEnabled()) {
485          LOG.debug(file.getAbsolutePath());
486       }
487 
488       // add the extension if missing
489       file = FilterFactory.forceTextExtension(file);
490 
491       try {
492          // now query the catalog and save the results to the file
493          final ArrayList results = new ArrayList();
494          final Collection discs = HibernateDao.findByQuery(Resources.getString("hql.export.catalog"));
495 
496          for (final Iterator iter = discs.iterator(); iter.hasNext();) {
497             final Object[] disc = (Object[]) iter.next();
498 
499             results.add(disc[0] + Resources.TAB + disc[1]);
500          }
501 
502          FileUtils.writeLines(file, null, results);
503 
504          MessageUtil.showInformation(getDefaultParentFrame(), "Catalog exported successfully.");
505       } catch (IOException ex) {
506          LOG.error(ERROR_WRITING_FILE + LINE_BREAK + ex.getMessage(), ex);
507       } catch (InfrastructureException ex) {
508          LOG.error(ERROR_WRITING_FILE + LINE_BREAK + ex.getMessage(), ex);
509       } catch (Exception ex) {
510          LOG.error("Unexpected error writing file.", ex);
511       }
512    }
513 
514    /**
515     * Renames the file to track number - title.mp3.
516     * <p>
517     * @param aEvent the Action Event fired for this button
518     */
519    public void fileRename(final ActionEvent aEvent) {
520       LOG.debug("Renaming File");
521       final JComponent button = (JComponent) aEvent.getSource();
522       final Object editor = (Object) button.getClientProperty(Resources.EDITOR_COMPONENT);
523       if (editor != null) {
524          if (editor instanceof AbstractEditor) {
525             ((AbstractEditor) editor).renameFiles();
526          } else if (editor instanceof DiscAddDialog) {
527             ((DiscAddDialog) editor).renameFiles();
528          }
529       }
530    }
531 
532    /**
533     * Applies the current filter to the tree.
534     * <p>
535     * @param aEvent the Action Event fired for this button
536     */
537    public void filter(final ActionEvent aEvent) {
538       LOG.debug("Filter applied.");
539       final JToggleButton button = (JToggleButton) aEvent.getSource();
540       final FilterPanel editor = (FilterPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
541       if (button.isSelected()) {
542          LOG.debug("Button Selected");
543          editor.applyFilter();
544       } else {
545          LOG.debug("Button Deselected");
546          editor.removeFilter();
547       }
548    }
549 
550    /**
551     * Clears the current filter.
552     * <p>
553     * @param aEvent the Action Event fired for this button
554     */
555    public void filterClear(final ActionEvent aEvent) {
556       LOG.debug("Filter cleared.");
557       final JComponent button = (JComponent) aEvent.getSource();
558       final FilterPanel editor = (FilterPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
559       editor.clearFilter();
560 
561    }
562 
563    /**
564     * Closes the filter window.
565     * <p>
566     * @param aEvent the Action Event fired for this button
567     */
568    public void filterClose(final ActionEvent aEvent) {
569       LOG.debug("Close Filter.");
570       getMainFrame().getMainPageBuilder().setFilterVisible(false);
571    }
572 
573    /**
574     * Displays or hides the filter window.
575     * <p>
576     * @param aEvent the Action Event fired for this button
577     */
578    public void filterDisplay(final ActionEvent aEvent) {
579       LOG.debug("Filter displayed/hidden.");
580       final boolean visible = !getMainFrame().getMainPageBuilder().isFilterVisible();
581       getMainFrame().getMainPageBuilder().setFilterVisible(visible);
582    }
583 
584    /**
585     * Launches the browser to the forums website.
586     */
587    public void forums() {
588       try {
589          if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
590             Desktop.getDesktop().browse(new URI(Resources.APPLICATION_FORUMS_URL));
591          }
592       } catch (UnsupportedOperationException ex) {
593          LOG.warn(ex.getMessage() + LINE_BREAK + ERROR_URL + Resources.APPLICATION_FORUMS_URL);
594       } catch (Exception ex) {
595          LOG.warn(ex.getMessage() + LINE_BREAK + ERROR_URL + Resources.APPLICATION_FORUMS_URL);
596       }
597    }
598 
599    /**
600     * Hides the main window.
601     */
602    public void hideMainWindow() {
603       if (SystemUtils.IS_OS_WINDOWS) {
604          final MainFrame mainframe = (MainFrame) getDefaultParentFrame();
605          mainframe.getTrayIcon().hideWindow();
606       }
607    }
608 
609    /**
610     * Updates the language of the application to aLanguage.
611     * <p>
612     * @param aEvent the ActionEvent fired
613     * @param aLanguage the language to change to
614     */
615    public void language(final ActionEvent aEvent, final String aLanguage) {
616       LOG.debug("Language change to " + aLanguage);
617       // MessageUtil.showwarn(getDefaultParentFrame(), "Language support not
618       // implemented yet");
619       MainModule.SETTINGS.setLocale(aLanguage);
620       getMainModule().storeState();
621       MessageUtil.showInformation(getDefaultParentFrame(), Resources.getString("messages.update.language"));
622    }
623 
624    /**
625     * Opens the global location change tool.
626     * <p>
627     * @param aEvent the Action Event fired for this button
628     */
629    public void locationTool(final ActionEvent aEvent) {
630       LOG.debug("Location Tool Dialog");
631       new LocationChangeDialog(getDefaultParentFrame(), MainModule.SETTINGS).open();
632    }
633 
634    /**
635     * Displays the memory usage and provide garbage collection.
636     * <p>
637     * @param aEvent the Action Event fired for this button
638     */
639    public void memory(final ActionEvent aEvent) {
640       LOG.debug("Memory Dialog");
641       new MemoryDialog(getDefaultParentFrame()).open();
642 
643    }
644 
645    /**
646     * Plays the next track in the playlist.
647     * <p>
648     * @param aEvent the actionevent fired
649     */
650    public void playerNext(final ActionEvent aEvent) {
651       LOG.debug("Player Next");
652       final Runnable update = new Runnable() {
653          public void run() {
654             final Playlist playlist = getMainFrame().getPlaylist();
655             if (playlist.hasNext()) {
656                final Track track = (Track) playlist.getNext();
657                if (track.isValid()) {
658                   getMainFrame().getPlayer().play(track.getTrackUrl());
659                }
660 
661             }
662          }
663       };
664       EventQueue.invokeLater(update);
665    }
666 
667    /**
668     * Pauses the media player.
669     * <p>
670     * @param aEvent the actionevent fired
671     */
672    public void playerPause(final ActionEvent aEvent) {
673       LOG.debug("Player Pause/Resume");
674       final Runnable update = new Runnable() {
675          public void run() {
676             final MainFrame mainFrame = (MainFrame) getDefaultParentFrame();
677             mainFrame.getPlayer().pause();
678          }
679       };
680       EventQueue.invokeLater(update);
681    }
682 
683    /**
684     * Plays the media player.
685     * <p>
686     * @param aEvent the actionevent fired
687     */
688    public void playerPlay(final ActionEvent aEvent) {
689       LOG.debug("Player Play");
690       final Runnable update = new Runnable() {
691          public void run() {
692             final MainFrame mainFrame = (MainFrame) getDefaultParentFrame();
693             LOG.debug("Status: " + mainFrame.getPlayer().getStatus());
694             if (mainFrame.getPlayer().getStatus() == -1) {
695                playerNext(null);
696             } else {
697                mainFrame.getPlayer().play();
698             }
699          }
700       };
701       EventQueue.invokeLater(update);
702 
703    }
704 
705    /**
706     * Plays the previous track in the playlist.
707     * <p>
708     * @param aEvent the actionevent fired
709     */
710    public void playerPrevious(final ActionEvent aEvent) {
711       LOG.debug("Player Previous");
712       final Runnable update = new Runnable() {
713          public void run() {
714             final Playlist playlist = getMainFrame().getPlaylist();
715             if (playlist.hasPrevious()) {
716                final Track trackCurrent = (Track) playlist.getPrevious();
717 
718                // if elapsed time is greater than 2 then replay this song
719                if (getMainFrame().getPlayer().getElapsedTime() > 2000) {
720                   LOG.debug("Player Previous: REPLAY current song");
721                   playerNext(null);
722                } else {
723                   final Track trackPrevious = (Track) playlist.getPrevious();
724                   if ((trackPrevious != null) && (trackPrevious.isValid())) {
725                      LOG.debug("Player Previous: PLAY previous song in playlist");
726                      playerNext(null);
727                   } else if ((trackCurrent != null) && (trackCurrent.isValid())) {
728                      playerNext(null);
729                   }
730                }
731             }
732          }
733       };
734       EventQueue.invokeLater(update);
735 
736    }
737 
738    /**
739     * Stops the media player.
740     * <p>
741     * @param aEvent the actionevent fired
742     */
743    public void playerStop(final ActionEvent aEvent) {
744       LOG.debug("Player Stop");
745       final Runnable update = new Runnable() {
746          public void run() {
747             MainFrame mainFrame = (MainFrame) getDefaultParentFrame();
748             mainFrame.getPlayer().stop();
749          }
750       };
751       EventQueue.invokeLater(update);
752    }
753 
754    /**
755     * Plays the selected track immediately.
756     * <p>
757     * @param aEvent the Action Event fired for this button
758     */
759    public void playImmediately(final ActionEvent aEvent) {
760       LOG.debug("Play Immediately");
761       final JComponent source = (JComponent) aEvent.getSource();
762       final Object editor = (Object) source.getClientProperty(Resources.EDITOR_COMPONENT);
763       final Runnable update = new Runnable() {
764          public void run() {
765 
766             if (editor != null) {
767                final Player player = getMainFrame().getPlayer();
768                final Playlist playlist = getMainFrame().getPlaylist();
769                if (editor instanceof JTree) {
770                   final JTree tree = (JTree) editor;
771                   for (int i = 0; i < tree.getSelectionPaths().length; i++) {
772                      final TreePath path = tree.getSelectionPaths()[i];
773                      if (path.getLastPathComponent() instanceof AbstractTreeNode) {
774                         final AbstractTreeNode node = (AbstractTreeNode) path.getLastPathComponent();
775                         playlist.addNext(node.getModel());
776                      }
777                   }
778                }
779                if (editor instanceof JList) {
780                   final JList list = (JList) editor;
781                   final Object[] selections = list.getSelectedValues();
782                   for (int i = selections.length - 1; i >= 0; i--) {
783                      Track track = null;
784                      if (selections[i] instanceof JukesValidationMessage) {
785                         final JukesValidationMessage message = (JukesValidationMessage) selections[i];
786                         track = (Track) message.getDomainObject();
787                         playlist.addNext(track);
788                      } else if (selections[i] instanceof Track) {
789                         track = (Track) selections[i];
790                         playlist.addNext(track);
791                      }
792                   }
793                }
794                if (editor instanceof JTable) {
795                   final JTable table = (JTable) editor;
796                   final int[] selections = table.getSelectedRows();
797                   final SearchTableModel model = (SearchTableModel) table.getModel();
798                   for (int i = 0; i < selections.length; i++) {
799                      int selectedRow = selections[i];
800                      selectedRow = table.getRowSorter().convertRowIndexToModel(selectedRow);
801                      Track track = (Track) model.getData()[selectedRow];
802                      playlist.addNext(track);
803                   }
804                }
805                if (editor instanceof AlbumImage) {
806                   final AlbumImage image = (AlbumImage) editor;
807                   if (image.getDisc() != null) {
808                      playlist.addNext(image.getDisc());
809                   }
810                }
811                final Track next = (Track) playlist.getNextImmediate();
812                if (next != null) {
813                   player.play(next.getTrackUrl());
814                }
815 
816             }
817          }
818       };
819       EventQueue.invokeLater(update);
820    }
821 
822    /**
823     * Closes the playlist.
824     * <p>
825     * @param aEvent the Actionevent fired
826     */
827    public void playlistClose(final ActionEvent aEvent) {
828       LOG.debug("Close Playlist.");
829       getMainFrame().getMainPageBuilder().setPlaylistVisible(false);
830 
831    }
832 
833    /**
834     * Shows or hides the playlist.
835     * <p>
836     * @param aEvent the Actionevent fired
837     */
838    public void playlistDisplay(final ActionEvent aEvent) {
839       LOG.debug("Playlist displayed/hidden.");
840       final boolean visible = !getMainFrame().getMainPageBuilder().isPlaylistVisible();
841       getMainFrame().getMainPageBuilder().setPlaylistVisible(visible);
842 
843    }
844 
845    /**
846     * Go to the track in the navigator.
847     * <p>
848     * @param aEvent the Actionevent fired
849     */
850    public void playlistGoto(final ActionEvent aEvent) {
851       LOG.debug("Playlist goto.");
852       final JComponent button = (JComponent) aEvent.getSource();
853       final JList editor = (JList) button.getClientProperty(Resources.EDITOR_COMPONENT);
854       try {
855          GuiUtil.setBusyCursor(getDefaultParentFrame(), true);
856          if (editor.getSelectedValue() != null) {
857             getMainModule().selectNodeInTree(editor.getSelectedValue());
858          }
859       } finally {
860          GuiUtil.setBusyCursor(getDefaultParentFrame(), false);
861       }
862    }
863 
864    /**
865     * Loads a playlist from a file.
866     * <p>
867     * @param aEvent the Action Event fired for this button
868     */
869    public void playlistLoad(final ActionEvent aEvent) {
870       LOG.debug("Load Playlist");
871       final JComponent button = (JComponent) aEvent.getSource();
872       final PlaylistPanel editor = (PlaylistPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
873       editor.load();
874    }
875 
876    /**
877     * Moves a track down on the playlist.
878     * <p>
879     * @param aEvent the Actionevent fired
880     */
881    public void playlistMoveDown(final ActionEvent aEvent) {
882       LOG.debug("Move down playlist");
883       final JComponent button = (JComponent) aEvent.getSource();
884       final PlaylistPanel editor = (PlaylistPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
885       editor.moveDown();
886 
887    }
888 
889    /**
890     * Moves the selected tracks over to the other list.
891     * <p>
892     * @param aEvent the Action Event fired for this button
893     */
894    public void playlistMoveOver(final ActionEvent aEvent) {
895       LOG.debug("Move over playlist");
896       final JComponent button = (JComponent) aEvent.getSource();
897       final PlaylistPanel editor = (PlaylistPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
898       editor.moveOver();
899 
900    }
901 
902    /**
903     * Moves a track up on the playlist.
904     * <p>
905     * @param aEvent the Actionevent fired
906     */
907    public void playlistMoveUp(final ActionEvent aEvent) {
908       LOG.debug("Move up playlist");
909       final JComponent button = (JComponent) aEvent.getSource();
910       final PlaylistPanel editor = (PlaylistPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
911       editor.moveUp();
912    }
913 
914    /**
915     * Removes tracks from the playlist.
916     * <p>
917     * @param aEvent the Actionevent fired
918     */
919    public void playlistRemoveTracks(final ActionEvent aEvent) {
920       LOG.debug("Remove tracks from playlist");
921       final JComponent button = (JComponent) aEvent.getSource();
922       final PlaylistPanel editor = (PlaylistPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
923       editor.removeTracks();
924       getMainFrame().getMainPageBuilder().refreshUI();
925    }
926 
927    /**
928     * Clears all tracks from the playlist.
929     * <p>
930     * @param aEvent the Actionevent fired
931     */
932    public void playlistClear(ActionEvent aEvent) {
933       LOG.debug("Clear tracks from playlist");
934       final JComponent button = (JComponent) aEvent.getSource();
935       final PlaylistPanel editor = (PlaylistPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
936       editor.removeAllTracks();
937       getMainFrame().getMainPageBuilder().refreshUI();
938 
939    }
940 
941    /**
942     * Saves a playlist to disk.
943     * <p>
944     * @param aEvent the Action Event fired for this button
945     */
946    public void playlistSave(final ActionEvent aEvent) {
947       LOG.debug("Save playlist.");
948       final JComponent button = (JComponent) aEvent.getSource();
949       final PlaylistPanel editor = (PlaylistPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
950       editor.save();
951    }
952 
953    /**
954     * Toggles between shuffling the catalog or not.
955     * <p>
956     * @param aEvent the Action Event fired for this button
957     */
958    public void playlistShuffleCatalog(final ActionEvent aEvent) {
959       LOG.debug("Shuffle catalog.");
960       final JToggleButton button = (JToggleButton) aEvent.getSource();
961       final PlaylistPanel editor = (PlaylistPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
962       editor.shuffleCatalog(button.isSelected());
963 
964       if (button.isSelected()) {
965          final Player player = getMainFrame().getPlayer();
966          // if the player was stopped then play the next song
967          if ((player.getStatus() == BasicPlayer.STOPPED) || (player.getStatus() == BasicPlayer.UNKNOWN)) {
968             playerNext(null);
969          }
970       }
971    }
972 
973    /**
974     * Toggles between shuffling the playlist or not.
975     * <p>
976     * @param aEvent the Action Event fired for this button
977     */
978    public void playlistShuffleList(final ActionEvent aEvent) {
979       LOG.debug("Shuffle playlist.");
980       final JToggleButton button = (JToggleButton) aEvent.getSource();
981       final PlaylistPanel editor = (PlaylistPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
982       editor.shufflePlaylist(button.isSelected());
983    }
984 
985    /**
986     * Toggles between history and current playlist.
987     * <p>
988     * @param aEvent the Action Event fired for this button
989     */
990    public void playlistToggle(final ActionEvent aEvent) {
991       LOG.debug("Toggle playlist.");
992       final JToggleButton button = (JToggleButton) aEvent.getSource();
993       final PlaylistPanel editor = (PlaylistPanel) button.getClientProperty(Resources.EDITOR_COMPONENT);
994       editor.toggle(!button.isSelected());
995    }
996 
997    /**
998     * Export preferences to an XML file.
999     * <p>
1000     * @param aEvent the Action Event fired for this button
1001     */
1002    public void preferencesExport(final ActionEvent aEvent) {
1003       LOG.debug("Export Preferences");
1004       final JFileChooser chooser = new JFileChooser();
1005       chooser.setDialogTitle("Export Preferences");
1006       chooser.setFileFilter(FilterFactory.xmlFileFilter());
1007       chooser.setMultiSelectionEnabled(false);
1008       chooser.setFileHidingEnabled(true);
1009       final int returnVal = chooser.showSaveDialog(this.getDefaultParentFrame());
1010       if (returnVal != JFileChooser.APPROVE_OPTION) {
1011          return;
1012       }
1013       File file = chooser.getSelectedFile();
1014       if (LOG.isDebugEnabled()) {
1015          LOG.debug(file.getAbsolutePath());
1016       }
1017 
1018       // add the extension if missing
1019       file = FilterFactory.forceXmlExtension(file);
1020 
1021       // now try and save the prefernces to a file
1022       try {
1023          final Preferences prefs = Application.getUserPreferences();
1024          final FileOutputStream stream = new FileOutputStream(file);
1025          prefs.exportSubtree(stream);
1026 
1027          MessageUtil.showInformation(getDefaultParentFrame(), "Preferences exported successfully.");
1028       } catch (IOException ex) {
1029          LOG.error(ERROR_WRITING_FILE + LINE_BREAK + ex, ex);
1030       } catch (Exception ex) {
1031          LOG.error("Unexpected error writing file.", ex);
1032       }
1033    }
1034 
1035    /**
1036     * Import preferences from an XML file.
1037     * <p>
1038     * @param aEvent the Action Event fired for this button
1039     */
1040    public void preferencesImport(final ActionEvent aEvent) {
1041       LOG.debug("Import Preferences");
1042       final JFileChooser chooser = new JFileChooser();
1043       chooser.setDialogTitle("Import Preferences");
1044       chooser.setFileFilter(FilterFactory.xmlFileFilter());
1045       chooser.setMultiSelectionEnabled(false);
1046       chooser.setFileHidingEnabled(true);
1047       final int returnVal = chooser.showOpenDialog(this.getDefaultParentFrame());
1048       if (returnVal != JFileChooser.APPROVE_OPTION) {
1049          return;
1050       }
1051       final File file = chooser.getSelectedFile();
1052       if (LOG.isDebugEnabled()) {
1053          LOG.debug(file.getAbsolutePath());
1054       }
1055 
1056       // now try and import the preferences to a file
1057       try {
1058          final FileInputStream stream = new FileInputStream(file);
1059          Preferences.importPreferences(stream);
1060          MainModule.SETTINGS.restoreFrom(Application.getUserPreferences());
1061 
1062          MessageUtil.showInformation(getDefaultParentFrame(), "Preferences imported successfully.");
1063       } catch (IOException ex) {
1064          LOG.error(ERROR_WRITING_FILE + LINE_BREAK + ex.getMessage(), ex);
1065       } catch (Exception ex) {
1066          LOG.error("Unexpected error writing file.", ex);
1067       }
1068    }
1069 
1070    /**
1071     * Adds track(s) to the bottom of the queue.
1072     * <p>
1073     * @param aEvent the Action Event fired for this button
1074     */
1075    public void queue(final ActionEvent aEvent) {
1076       LOG.debug("Queue");
1077       final JComponent source = (JComponent) aEvent.getSource();
1078       final Component editor = (Component) source.getClientProperty(Resources.EDITOR_COMPONENT);
1079       if (editor != null) {
1080          final Player player = getMainFrame().getPlayer();
1081          final Playlist playlist = getMainFrame().getPlaylist();
1082 
1083          // check if the playlist is empty. If it is then set a flag to
1084          // start playing the next song immediately
1085          final boolean startPlaying = (playlist.sizeNext() == 0);
1086 
1087          if (editor instanceof JTree) {
1088             final JTree tree = (JTree) editor;
1089             for (int i = 0; i < tree.getSelectionPaths().length; i++) {
1090                final TreePath path = tree.getSelectionPaths()[i];
1091                if (path.getLastPathComponent() instanceof AbstractTreeNode) {
1092                   final AbstractTreeNode node = (AbstractTreeNode) path.getLastPathComponent();
1093                   playlist.add(node.getModel());
1094                }
1095             }
1096          }
1097          if (editor instanceof JList) {
1098             final JList list = (JList) editor;
1099             final Object[] selections = list.getSelectedValues();
1100             for (int i = 0; i < selections.length; i++) {
1101                final JukesValidationMessage message = (JukesValidationMessage) selections[i];
1102                playlist.add(message.getDomainObject());
1103             }
1104          }
1105          if (editor instanceof JTable) {
1106             final JTable table = (JTable) editor;
1107             final int[] selections = table.getSelectedRows();
1108             final SearchTableModel model = (SearchTableModel) table.getModel();
1109             for (int i = 0; i < selections.length; i++) {
1110                int selectedRow = selections[i];
1111                selectedRow = table.getRowSorter().convertRowIndexToModel(selectedRow);
1112                playlist.add(model.getData()[selectedRow]);
1113             }
1114          }
1115          if (editor instanceof AlbumImage) {
1116             final AlbumImage image = (AlbumImage) editor;
1117             if (image.getDisc() != null) {
1118                playlist.add(image.getDisc());
1119             }
1120          }
1121 
1122          // if player was stopped and playlist empty then play the next song
1123          if (((player.getStatus() == BasicPlayer.STOPPED) || (player.getStatus() == BasicPlayer.UNKNOWN))
1124                   && (startPlaying)) {
1125             playerNext(null);
1126          }
1127          getMainFrame().getMainPageBuilder().refreshUI();
1128       }
1129    }
1130 
1131    /**
1132     * Adds track(s) to the top of the queue.
1133     * <p>
1134     * @param aEvent the Action Event fired for this button
1135     */
1136    public void queueNext(final ActionEvent aEvent) {
1137       LOG.debug("Queue Next");
1138       final JComponent source = (JComponent) aEvent.getSource();
1139       final Component editor = (Component) source.getClientProperty(Resources.EDITOR_COMPONENT);
1140       if (editor != null) {
1141          final Player player = getMainFrame().getPlayer();
1142          final Playlist playlist = getMainFrame().getPlaylist();
1143 
1144          // check if the playlist is empty. If it is then set a flag to
1145          // start playing the next song immediately
1146          final boolean startPlaying = (playlist.sizeNext() == 0);
1147 
1148          if (editor instanceof JTree) {
1149             final JTree tree = (JTree) editor;
1150             for (int i = tree.getSelectionPaths().length - 1; i >= 0; i--) {
1151                final TreePath path = tree.getSelectionPaths()[i];
1152                if (path.getLastPathComponent() instanceof AbstractTreeNode) {
1153                   final AbstractTreeNode node = (AbstractTreeNode) path.getLastPathComponent();
1154                   playlist.addNext(node.getModel());
1155                }
1156             }
1157          }
1158          if (editor instanceof JList) {
1159             final JList list = (JList) editor;
1160             final Object[] selections = list.getSelectedValues();
1161             for (int i = selections.length - 1; i >= 0; i--) {
1162                final JukesValidationMessage message = (JukesValidationMessage) selections[i];
1163                playlist.addNext(message.getDomainObject());
1164             }
1165          }
1166          if (editor instanceof JTable) {
1167             final JTable table = (JTable) editor;
1168             final int[] selections = table.getSelectedRows();
1169             final SearchTableModel model = (SearchTableModel) table.getModel();
1170             for (int i = 0; i < selections.length; i++) {
1171                int selectedRow = selections[i];
1172                selectedRow = table.getRowSorter().convertRowIndexToModel(selectedRow);
1173                playlist.addNext(model.getData()[selectedRow]);
1174             }
1175          }
1176          if (editor instanceof AlbumImage) {
1177             final AlbumImage image = (AlbumImage) editor;
1178             if (image.getDisc() != null) {
1179                playlist.addNext(image.getDisc());
1180             }
1181          }
1182 
1183          // if player was stopped and playlist empty then play the next song
1184          if (((player.getStatus() == BasicPlayer.STOPPED) || (player.getStatus() == BasicPlayer.UNKNOWN))
1185                   && (startPlaying)) {
1186             playerNext(null);
1187          }
1188 
1189          getMainFrame().getMainPageBuilder().refreshUI();
1190       }
1191    }
1192 
1193    /**
1194     * Refreshes the data from the database and reloads the tree
1195     * <p>
1196     * @param aEvent the Action Event fired for this button
1197     */
1198    public void refresh(final ActionEvent aEvent) {
1199       LOG.debug("Refreshing Data.");
1200       GuiUtil.setBusyCursor(getDefaultParentFrame(), true);
1201       // completely evict the session and clear all loaded objects.
1202       // HibernateUtil.getSession().clear();
1203 
1204       // refresh the tree
1205       getMainModule().refreshTree();
1206       GuiUtil.setBusyCursor(getDefaultParentFrame(), false);
1207    }
1208 
1209    /**
1210     * Rolls changes back to orginal form.
1211     * <p>
1212     * @param aEvent the Action Event fired for this button
1213     */
1214    public void rollback(final ActionEvent aEvent) {
1215       LOG.debug(