View Javadoc

1   package com.melloware.jukes.gui.view.dialogs;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Dimension;
5   import java.awt.Frame;
6   import java.io.File;
7   import java.io.IOException;
8   import java.util.ArrayList;
9   import java.util.Collection;
10  import java.util.Iterator;
11  
12  import javax.swing.DefaultCellEditor;
13  import javax.swing.JButton;
14  import javax.swing.JComboBox;
15  import javax.swing.JComponent;
16  import javax.swing.JFileChooser;
17  import javax.swing.JOptionPane;
18  import javax.swing.JPanel;
19  import javax.swing.JSplitPane;
20  import javax.swing.JTable;
21  import javax.swing.JTextField;
22  import javax.swing.ListSelectionModel;
23  import javax.swing.RowSorter;
24  import javax.swing.event.ListSelectionEvent;
25  import javax.swing.event.ListSelectionListener;
26  import javax.swing.table.TableModel;
27  import javax.swing.table.TableRowSorter;
28  
29  import org.apache.commons.io.FilenameUtils;
30  import org.apache.commons.lang.StringUtils;
31  import org.apache.commons.lang.math.NumberUtils;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  
35  import com.jgoodies.forms.builder.PanelBuilder;
36  import com.jgoodies.forms.factories.Borders;
37  import com.jgoodies.forms.factories.ButtonBarFactory;
38  import com.jgoodies.forms.layout.CellConstraints;
39  import com.jgoodies.forms.layout.FormLayout;
40  import com.jgoodies.uif.AbstractDialog;
41  import com.jgoodies.uif.action.ActionManager;
42  import com.jgoodies.uif.application.Application;
43  import com.jgoodies.uifextras.util.UIFactory;
44  import com.jgoodies.validation.Severity;
45  import com.melloware.jukes.exception.MusicTagException;
46  import com.melloware.jukes.file.FileUtil;
47  import com.melloware.jukes.file.MusicDirectory;
48  import com.melloware.jukes.file.filter.FilterFactory;
49  import com.melloware.jukes.file.image.ChooserImagePreview;
50  import com.melloware.jukes.file.image.ImageFactory;
51  import com.melloware.jukes.file.image.ImageFileView;
52  import com.melloware.jukes.file.tag.MusicTag;
53  import com.melloware.jukes.file.tag.TagFactory;
54  import com.melloware.jukes.gui.tool.Actions;
55  import com.melloware.jukes.gui.tool.Resources;
56  import com.melloware.jukes.gui.tool.Settings;
57  import com.melloware.jukes.gui.view.component.AlbumImage;
58  import com.melloware.jukes.gui.view.component.ComponentFactory;
59  import com.melloware.jukes.gui.view.component.EnhancedTableHeader;
60  import com.melloware.jukes.util.GuiUtil;
61  import com.melloware.jukes.util.JukesValidationMessage;
62  import com.melloware.jukes.util.MessageUtil;
63  
64  /**
65   * Adds a single disc to the catalog.  This disc can be mainipulated before it
66   * is added to the catalog for correctness.  Amazon may be used to get its cover
67   * and track titles.
68   * <p>
69   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
70   * @author Emil A. Lefkof III <info@melloware.com>
71   * @version 4.0
72   */
73  @SuppressWarnings("unchecked")
74  public final class DiscAddDialog
75      extends AbstractDialog {
76  
77      private static final Log LOG = LogFactory.getLog(DiscAddDialog.class);
78      private static final String TRACK_00 = "00";
79      private static final String TRACK_32 = "32";
80      private AlbumImage webImagePreview;
81      private EnhancedTableHeader header;
82      private File coverImage;
83      private JButton buttonCancel;
84      private JButton buttonSave;
85      private JComboBox genreField;
86      private JComponent buttonBar;
87      private JComponent splitPane;
88      private JTable tagTable;
89      private JTextField artistField;
90      private JTextField discField;
91      private JTextField yearField;
92      private MusicTagTableModel tableModel;
93      private Object[] tags;
94      private Settings settings;
95      private String directory;
96  
97      /**
98       * Constructs a default about dialog using the given owner.
99       *
100      * @param owner   the dialog's owner
101      */
102     public DiscAddDialog(Frame owner, Settings settings, File aFile) {
103         super(owner);
104         LOG.debug("Disc Add Dialog created.");
105         try {
106             this.settings = settings;
107             final MusicTag musicTag = TagFactory.getTag(aFile);
108             if (LOG.isDebugEnabled()) {
109                 LOG.debug(musicTag.getHeaderInfo());
110             }
111 
112             artistField = new JTextField(musicTag.getArtist());
113             artistField.setColumns(45);
114             discField = new JTextField(musicTag.getDisc());
115             discField.setColumns(45);
116             genreField = new JComboBox(MusicTag.getGenreTypes().toArray());
117             genreField.setSelectedItem(musicTag.getGenre());
118             if (genreField.getSelectedItem() == null) {
119                 genreField.setSelectedItem("Other");
120             }
121             yearField = new JTextField(musicTag.getYear());
122             ((JTextField)yearField).setColumns(5);
123             tagTable = new JTable();
124             header = new EnhancedTableHeader(tagTable.getColumnModel(), tagTable);
125             tagTable.setTableHeader(header);
126 
127             // try and get the best image from the directory
128             directory = FilenameUtils.getFullPath(aFile.getAbsolutePath());
129             webImagePreview = new AlbumImage();
130             final File dir = new File(directory);
131             coverImage = MusicDirectory.findLargestImageFile(dir);
132             updateCoverImage();
133 
134             // try and get music files and load table
135             final Collection files = MusicDirectory.findMusicFiles(dir);
136             final ArrayList musicTags = new ArrayList();
137             
138             if (files != null) {
139                 final int padding = ((files.size() >= 100) ? 3 : 2);
140                 int counter = 0;
141                 for (Iterator iter = files.iterator(); iter.hasNext();) {
142                     counter++;
143                     final File element = (File)iter.next();
144                     final MusicTag tag = TagFactory.getTag(element);
145                     if ((StringUtils.equals(TRACK_00, tag.getTrack())) || (StringUtils.equals(TRACK_32, tag.getTrack()))) {
146                         tag.setTrack(Integer.toString(counter), padding);
147                     } else {
148                        tag.setTrack(tag.getTrack(), padding);
149                     }
150                     musicTags.add(tag);
151                 }
152                 this.tags = musicTags.toArray();
153                 loadTable();
154             }
155 
156             // this.setPreferredSize(new Dimension(700, 576));
157         } catch (MusicTagException ex) {
158             LOG.error("MusicTagException", ex);
159         }
160     }
161 
162     /* (non-Javadoc)
163      * @see com.jgoodies.swing.AbstractDialog#doApply()
164      */
165     public void doApply() {
166         LOG.debug("Save pressed.");
167 
168         GuiUtil.setBusyCursor(this, true);
169 
170         // update tags from dialog values
171         fillTags();
172 
173         // now try and save the database record and update tags
174         JukesValidationMessage result = MusicDirectory.createNewDisc(this.tags, this.coverImage,
175                                                                      new File(this.directory), null, true);
176         GuiUtil.setBusyCursor(this, false);
177 
178         if (result.getSeverity() == Severity.OK) {
179             // refresh the tree
180             ActionManager.get(Actions.REFRESH_ID).actionPerformed(null);
181             super.doClose();
182         } else {
183             MessageUtil.showError(this, result.getMessage());
184         }
185     }
186 
187     /* (non-Javadoc)
188      * @see com.jgoodies.swing.AbstractDialog#doCancel()
189      */
190     public void doCancel() {
191         LOG.debug("Cancel Pressed.");
192         super.doCancel();
193     }
194 
195     /**
196      * Finds a new disc cover.
197      */
198     public void findCover() {
199         final File currentDir = new File(this.directory);
200         JFileChooser chooser = new JFileChooser();
201         chooser.setApproveButtonText("Select");
202         chooser.setDialogTitle("Find Cover Image");
203         chooser.setCurrentDirectory(currentDir);
204         chooser.addChoosableFileFilter(FilterFactory.imageFileFilter());
205         chooser.setAcceptAllFileFilterUsed(false);
206         chooser.setFileView(new ImageFileView());
207         chooser.setAccessory(new ChooserImagePreview(chooser));
208         chooser.setMultiSelectionEnabled(false);
209         int returnVal = chooser.showOpenDialog(Application.getDefaultParentFrame());
210         if (returnVal != JFileChooser.APPROVE_OPTION) {
211             return;
212         }
213 
214         File file = chooser.getSelectedFile();
215         this.coverImage = file;
216         updateCoverImage();
217     }
218 
219     /**
220      * Renames all the music files
221      */
222     public void renameFiles() {
223         LOG.debug("Renaming Files");
224         updateTable();
225         MusicTag musicTag = null;
226         try {
227             GuiUtil.setBusyCursor(this, true);
228             // update tags from dialog values
229             fillTags();
230 
231             for (int i = 0; i < tags.length; i++) {
232                 musicTag = (MusicTag)tags[i];
233                 if (musicTag.renameFile(this.settings.getFileFormatMusic())) {
234                     LOG.debug("Renamed " + musicTag.getAbsolutePath());
235                 }
236             }
237         } catch (Exception ex) {
238             LOG.error("Error renaming file: " + ex.getMessage(), ex);
239         } finally {
240             GuiUtil.setBusyCursor(this, false);
241         }
242         updateTable();
243     }
244 
245     /**
246      * If track titles are all messed up and no amazon search found, this will
247      * attempt to use the filename to construct a valid title.
248      */
249     public void resetFromFilenames() {
250         LOG.debug("Constructing titles from filenames");
251         for (int i = 0; i < tags.length; i++) {
252             MusicTag tag = (MusicTag)tags[i];
253             tag.setTitle(tag.extractTitleFromFilename());
254         }
255         updateTable();
256     }
257 
258     /**
259      * If track numbers are all screwed up, then loop and make them 1 to N.
260      */
261     public void resetTrackNumbers() {
262         LOG.debug("Resetting track numbers.");
263         final int padding = ((tags.length >= 100) ? 3 : 2);
264         for (int i = 0; i < tags.length; i++) {
265             MusicTag tag = (MusicTag)tags[i];
266             tag.setTrack(String.valueOf(i + 1), padding);
267         }
268         updateTable();
269     }
270 
271     /**
272      * Apply title case to all tracks in the disc.
273      */
274     public void titleCase() {
275         LOG.debug("Title casing all tracks");
276         for (int i = 0; i < tags.length; i++) {
277             MusicTag tag = (MusicTag)tags[i];
278             tag.setTitle(FileUtil.capitalize(tag.getTitle()));
279         }
280         updateTable();
281     }
282 
283     /**
284      * Updates all of the comments at once
285      */
286     public void updateComments() {
287         LOG.debug("Updating comments");
288         updateTable();
289         final String inputValue = StringUtils.defaultIfEmpty(JOptionPane.showInputDialog("Enter a comment: "), "");
290         for (int i = 0; i < tags.length; i++) {
291             final MusicTag tag = (MusicTag)tags[i];
292             tag.setComment(inputValue);
293         }
294         updateTable();
295     }
296 
297     /**
298      * Perform the web search.
299      */
300     public void webSearch() {
301         LOG.debug("Web Search");
302         WebSearchDialog dialog = new WebSearchDialog((Frame)this.getParent(), this.settings);
303         dialog.setSelectedArtist(artistField.getText());
304         dialog.setSelectedDisc(discField.getText());
305         dialog.open();
306 
307         // if the user did not select anything
308         if (dialog.hasBeenCanceled()) {
309             return;
310         }
311 
312         if (StringUtils.isNotBlank(dialog.getSelectedArtist())) {
313             artistField.setText(dialog.getSelectedArtist());
314         }
315         if (StringUtils.isNotBlank(dialog.getSelectedDisc())) {
316             String end = StringUtils.substringAfterLast(discField.getText(), " -");
317             if (StringUtils.isNotBlank(end)) {
318                 discField.setText(dialog.getSelectedDisc() + " -" + end);
319             } else {
320                 discField.setText(dialog.getSelectedDisc());
321             }
322         }
323         if (StringUtils.isNotBlank(dialog.getSelectedYear())) {    // NOPMD
324             if ((NumberUtils.isNumber(yearField.getText())) && (NumberUtils.isNumber(dialog.getSelectedYear()))) {    // NOPMD
325                 if (Integer.valueOf(dialog.getSelectedYear()).intValue() < Integer.valueOf(yearField.getText()).intValue()) {    // NOPMD
326                     yearField.setText(dialog.getSelectedYear());
327                 }
328             }
329         }
330 
331         // if the track counts match exactly then rename tracks too
332         Collection amazonTracks = dialog.getSelectedTracks();
333         if (amazonTracks != null) {
334             if (LOG.isDebugEnabled()) {
335                 LOG.debug("Amazon Count = " + amazonTracks.size());
336                 LOG.debug("Tag Count = " + tags.length);
337             }
338 
339             if (amazonTracks.size() == tags.length) {
340                 Object[] tracks = amazonTracks.toArray();
341                 for (int i = 0; i < tags.length; i++) {
342                     MusicTag musicTag = (MusicTag)tags[i];
343                     musicTag.setTitle((String)tracks[i]);
344                 }
345             }
346         }
347 
348         // either overwrite the old cover or create a new one
349         if (dialog.getSelectedImage() != null) {
350             try {
351                 if (this.coverImage == null) {
352                     LOG.debug(this.directory);
353                     String imageLocation = ImageFactory.saveImageWithFileFormat(dialog.getSelectedImage(),
354                                                                                 this.settings.getFileFormatImage(),
355                                                                                 FilenameUtils.getFullPath(this.directory),
356                                                                                 artistField.getText(),
357                                                                                 discField.getText());
358 
359                     this.coverImage = new File(imageLocation);
360                 } else {
361                     ImageFactory.saveImage(dialog.getSelectedImage(), this.coverImage.getAbsolutePath());
362                 }
363                 updateCoverImage();
364             } catch (IOException ex) {
365                 LOG.error("Error saving cover image.", ex);
366             }
367         }
368 
369         updateTable();
370     }
371 
372     /**
373      * Builds and answers the dialog's content.
374      *
375      * @return the dialog's content with tabbed pane and button bar
376      */
377     protected JComponent buildContent() {
378         JPanel content = new JPanel(new BorderLayout());
379         JButton[] buttons = new JButton[2];
380         JButton button = createApplyButton();
381         button.setText("Save");
382         button.setEnabled(true);
383         buttonSave = button;
384         buttonCancel = createCancelButton();
385         buttons[0] = buttonSave;
386         buttons[1] = buttonCancel;
387         buttonBar = ButtonBarFactory.buildRightAlignedBar(buttons);
388         splitPane = buildSplitPane();
389         content.add(splitPane, BorderLayout.CENTER);
390         content.add(buttonBar, BorderLayout.SOUTH);
391         return content;
392     }
393 
394     /**
395      * Builds and returns the dialog's header.
396      *
397      * @return the dialog's header component
398      */
399     protected JComponent buildHeader() {
400         final DiscAddHeaderPanel header = new DiscAddHeaderPanel(this, "Add New Disc ",
401                                                            "Adds a new disc to the catalog. Any updates should be made here before adding to catalog.",
402                                                            Resources.DISC_ADD_ICON);
403 
404         return header;
405     }
406 
407     /**
408      * Builds and returns the dialog's pane.
409      *
410      * @return the dialog's  pane component
411      */
412     protected JComponent buildMainPanel() {
413         FormLayout layout = new FormLayout("fill:pref:grow", "p, p, p");
414         PanelBuilder builder = new PanelBuilder(layout);
415         builder.setDefaultDialogBorder();
416         CellConstraints cc = new CellConstraints();
417         builder.add(buildDiscPanel(), cc.xy(1, 1));
418         builder.add(buildTagTablePanel(), cc.xy(1, 3));
419         return builder.getPanel();
420     }
421 
422     /**
423      * Resizes the given component to give it a quadratic aspect ratio.
424      *
425      * @param component   the component to be resized
426      */
427     protected void resizeHook(JComponent component) {
428         // Resizer.ONE2ONE.resizeDialogContent(component);
429     }
430 
431     /**
432      * Builds the search criteria panel.
433      * <p>
434      * @return the panel used to specify criteria
435      */
436     private JComponent buildDiscPanel() {
437         FormLayout layout = new FormLayout("right:max(14dlu;pref), 400px, pref, pref, pref, pref ,pref, fill:pref:grow",
438                                            "p, 4px, p, 4px, p, 4px, 4px");
439 
440         PanelBuilder builder = new PanelBuilder(layout);
441         CellConstraints cc = new CellConstraints();
442 
443         builder.addLabel("Artist: ", cc.xy(1, 1));
444         builder.add(artistField, cc.xyw(2, 1, 4));
445         builder.add(webImagePreview, cc.xywh(7, 1, 1, 7));
446         builder.add(ComponentFactory.createTitleCaseButton(artistField), cc.xy(6, 1));
447         builder.addLabel("Disc: ", cc.xy(1, 3));
448         builder.add(discField, cc.xyw(2, 3, 4));
449         builder.add(ComponentFactory.createTitleCaseButton(discField), cc.xy(6, 3));
450         builder.addLabel("Genre: ", cc.xy(1, 5));
451         builder.add(genreField, cc.xy(2, 5));
452         builder.addLabel("Year: ", cc.xy(4, 5));
453         builder.add(yearField, cc.xy(5, 5));
454         return builder.getPanel();
455     }
456 
457     /**
458      * Builds the <code>Search Criteria</code>, the <code>Results</code>
459      * and answers them wrapped by a stripped <code>JSplitPane</code>.
460      */
461     private JComponent buildSplitPane() {
462         splitPane = UIFactory.createStrippedSplitPane(JSplitPane.VERTICAL_SPLIT, buildDiscPanel(), buildTagTablePanel(),
463                                                       0.25);
464         splitPane.setBorder(Borders.DIALOG_BORDER);
465         return splitPane;
466     }
467 
468     /**
469      * Builds the panel with the JTable tags in it.
470      * <p>
471      * @return the panel used to display messages
472      */
473     private JComponent buildTagTablePanel() {
474 
475         // build the table and model
476         tagTable.setShowGrid(false);
477         tagTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
478         tagTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
479         // Ask to be notified of selection changes.
480         ListSelectionModel rowSM = tagTable.getSelectionModel();
481         rowSM.addListSelectionListener(new ListSelectionListener() {
482                 public void valueChanged(ListSelectionEvent e) {
483                     // Ignore extra messages.
484                     if (e.getValueIsAdjusting()) {
485                         return;
486                     }
487                 }
488             });
489 
490         JComponent resultsPane = UIFactory.createTablePanel(tagTable);
491         resultsPane.setPreferredSize(new Dimension(300, 275));
492 
493         // build the form
494         FormLayout layout = new FormLayout("fill:pref:grow", "p");
495         PanelBuilder builder = new PanelBuilder(layout);
496         CellConstraints cc = new CellConstraints();
497         builder.add(resultsPane, cc.xy(1, 1));
498         return builder.getPanel();
499     }
500 
501     /**
502      * Fill each tag from the screen.
503      */
504     private void fillTags() {
505         // loop through and set the disc settings into each tag
506         for (int i = 0; i < tags.length; i++) {
507             MusicTag tag = (MusicTag)tags[i];
508             tag.setArtist(artistField.getText());
509             tag.setDisc(discField.getText());
510             tag.setYear(yearField.getText());
511             tag.setGenre((String)genreField.getSelectedItem());
512         }
513     }
514 
515     /**
516      * Loads the JTable with data.
517      */
518     private void loadTable() {
519         if (LOG.isDebugEnabled()) {
520             LOG.debug("Loading table.");
521         }
522         tableModel = new MusicTagTableModel(tags);
523         RowSorter<TableModel> sorter = new TableRowSorter<TableModel>(tableModel);
524         tagTable.setModel(tableModel);
525         tagTable.setRowSorter(sorter);
526         header.autoSizeColumns();
527 
528         // one click to edit the text cell
529         ((DefaultCellEditor)tagTable.getDefaultEditor(String.class)).setClickCountToStart(1);
530         tagTable.updateUI();
531     }
532 
533     /**
534      * Updates the cover thumbnail.
535      */
536     private void updateCoverImage() {
537         if ((this.coverImage != null) && (this.coverImage.exists())) {
538             webImagePreview.setImage(ImageFactory.getScaledImage(coverImage.getAbsolutePath(), 90, 90).getImage());
539         }
540     }
541 
542     /**
543      * Closes any cell editors and fires datachanged event.
544      */
545     private void updateTable() {
546         GuiUtil.stopTableEditing(tagTable);
547         tableModel.fireTableDataChanged();
548     }
549 
550 }