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