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