View Javadoc

1   package com.melloware.jukes.gui.view.editor;
2   
3   import java.awt.Dimension;
4   import java.awt.event.ActionEvent;
5   import java.awt.event.ActionListener;
6   
7   import javax.swing.JComponent;
8   import javax.swing.JLabel;
9   import javax.swing.JTextField;
10  import javax.swing.JToolBar;
11  import javax.swing.ProgressMonitor;
12  import javax.swing.Timer;
13  import javax.swing.text.JTextComponent;
14  
15  import org.apache.commons.io.FileUtils;
16  import org.apache.commons.lang.StringUtils;
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  
20  import com.jgoodies.forms.builder.PanelBuilder;
21  import com.jgoodies.forms.layout.CellConstraints;
22  import com.jgoodies.forms.layout.FormLayout;
23  import com.jgoodies.uif.builder.ToolBarBuilder;
24  import com.jgoodies.uif.component.ToolBarButton;
25  import com.jgoodies.uifextras.util.UIFactory;
26  import com.jgoodies.validation.view.ValidationComponentUtils;
27  import com.melloware.jukes.db.HibernateDao;
28  import com.melloware.jukes.db.HibernateUtil;
29  import com.melloware.jukes.db.orm.Disc;
30  import com.melloware.jukes.db.orm.Track;
31  import com.melloware.jukes.exception.InfrastructureException;
32  import com.melloware.jukes.exception.MusicTagException;
33  import com.melloware.jukes.file.image.ImageFactory;
34  import com.melloware.jukes.file.tag.MusicTag;
35  import com.melloware.jukes.file.tag.TagFactory;
36  import com.melloware.jukes.gui.tool.Actions;
37  import com.melloware.jukes.gui.tool.Resources;
38  import com.melloware.jukes.gui.view.component.AlbumImage;
39  import com.melloware.jukes.gui.view.component.ComponentFactory;
40  import com.melloware.jukes.gui.view.tasks.TimerListener;
41  import com.melloware.jukes.gui.view.tasks.UpdateTagsTask;
42  import com.melloware.jukes.gui.view.validation.IconFeedbackPanel;
43  import com.melloware.jukes.gui.view.validation.TrackValidationModel;
44  import com.melloware.jukes.util.MessageUtil;
45  
46  /**
47   * An implementation of {@link Editor} that displays a {@link Track}.<p>
48   *
49   * This container uses a <code>FormLayout</code> and the panel building
50   * is done with the <code>PanelBuilder</code> class.
51   * Columns and rows are specified before the panel is filled with components.
52   * <p>
53   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
54   * @author Emil A. Lefkof III <info@melloware.com>
55   * @version 4.0
56   */
57  public final class TrackEditor
58      extends AbstractEditor {
59  
60      private static final Log LOG = LogFactory.getLog(TrackEditor.class);
61      private AlbumImage albumImage;
62      private JComponent trackPanel;
63      private JLabel bitRate;
64      private JLabel copyrighted;
65      private JLabel duration;
66      private JLabel emphasis;
67      private JLabel fileSize;
68      private JLabel frequency;
69      private JLabel layer;
70      private JLabel location;
71      private JLabel mode;
72      private JLabel version;
73      private JTextComponent comment;
74      private JTextComponent titleField;
75      private JTextComponent trackNumber;
76      private JToolBar headerToolbar;
77      private transient MusicTag musicTag;
78  
79      /**
80       * Constructs a <code>TrackEditor</code>.
81       */
82      public TrackEditor() {
83          super(Resources.TRACK_TREE_ICON);
84      }
85  
86      /**
87       * Gets the domain class associated with this editor.
88       */
89      public Class getDomainClass() {
90          return Track.class;
91      }
92  
93      /* (non-Javadoc)
94       * @see com.melloware.jukes.gui.view.editor.AbstractEditor#getHeaderToolBar()
95       */
96      public JToolBar getHeaderToolBar() {
97          return headerToolbar;
98      }
99  
100     /**
101      * Builds the content pane.
102      */
103     public void build() {
104         initComponents();
105         initComponentAnnotations();
106         initEventHandling();
107 
108         trackPanel = buildTrackPanel();
109 
110         FormLayout layout = new FormLayout("fill:pref:grow",
111                                            "max(14dlu;pref), p, p, p, 12px, p, 7px, p, 12px, p, 7px, p");
112 
113         setLayout(layout);
114         PanelBuilder builder = new PanelBuilder(layout, this);
115         builder.setDefaultDialogBorder();
116         CellConstraints cc = new CellConstraints();
117 
118         builder.add(buildHintAreaPane(), cc.xy(1, 1));
119         builder.addSeparator(Resources.getString("label.track"), cc.xy(1, 3));
120         builder.add(trackPanel, cc.xy(1, 4));
121         builder.addSeparator(Resources.getString("label.taginfo"), cc.xy(1, 6));
122         builder.add(buildMusicPanel(), cc.xy(1, 8));
123         JComponent audit = buildAuditInfoPanel();
124         if (this.getSettings().isAuditInfo()) {
125             builder.addSeparator(Resources.getString("label.auditinfo"), cc.xy(1, 10));
126             builder.add(audit, cc.xy(1, 12));
127         }
128 
129     }
130 
131     /* (non-Javadoc)
132      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#commit()
133      */
134     public void commit() {
135         super.commit();
136         Track track = getTrack();
137         updateModel();
138 
139         // check for validation errors, if any then do no changes
140         boolean hasErrors = hasErrors();
141         if (hasErrors) {
142             LOG.error(Resources.getString("messages.editorerrors"));
143             return;
144         }
145 
146         // try to persist
147         try {
148             setBusyCursor(true);
149             HibernateUtil.beginTransaction();
150             HibernateDao.saveOrUpdate(track);
151             HibernateUtil.commitTransaction();
152 
153             // now update this editor and the treeview
154             updateView();
155             this.getMainModule().refreshSelection(track, Resources.NODE_CHANGED);
156         } catch (InfrastructureException ex) {
157             HibernateUtil.rollbackTransaction();
158             LOG.error("Track must be unique within an album. \n\nMust have different Title and Track Number than all other tracks. ");
159             HibernateDao.refresh(track);
160             hasErrors = true;
161         } catch (Exception ex) {
162             HibernateUtil.rollbackTransaction();
163             LOG.error("Error updating track.", ex);
164             HibernateDao.refresh(track);
165             hasErrors = true;
166         } finally {
167             setBusyCursor(false);
168         }
169 
170         if (!hasErrors) {
171             // now update the ID3 tags
172             task = new UpdateTagsTask(track);
173             progressMonitor = new ProgressMonitor(getMainFrame(), Resources.getString("messages.updatetracks"), "", 0,
174                                                   (int)task.getLengthOfTask());
175             progressMonitor.setProgress(0);
176             progressMonitor.setMillisToDecideToPopup(1);
177             task.go();
178             timer = new Timer(50, null);
179             timer.addActionListener(new TimerListener(progressMonitor, task, timer));
180             timer.start();
181 
182             super.commit();
183         }
184     }
185 
186     /* (non-Javadoc)
187      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#delete()
188      */
189     public void delete() {
190         super.delete();
191         try {
192             if (!MessageUtil.confirmDelete(this)) {
193                 return;
194             }
195             // try to delete from database
196             setBusyCursor(true);
197             HibernateUtil.beginTransaction();
198             final Track track = getTrack();
199             final Disc disc = track.getDisc();
200             HibernateDao.refresh(disc);
201             disc.getTracks().remove(track);
202             HibernateDao.refresh(track);
203             HibernateDao.delete(track);
204             HibernateUtil.commitTransaction();
205 
206             // reset dirty flag since we are deleting
207             getValidationModel().setDirty(false);
208             // tell the tree to select the parent node
209             this.getMainModule().refreshSelection(disc, Resources.NODE_DELETED);
210         } catch (Exception ex) {
211             LOG.error("Error deleting track.", ex);
212             HibernateUtil.rollbackTransaction();
213         } finally {
214             setBusyCursor(false);
215         }
216     }
217 
218     /**
219      * Rename the file to a good format.
220      */
221     public void renameFiles() {
222         updateModel();
223 
224         // check for validation errors, if any then do no changes
225         if (hasErrors()) {
226             LOG.error(Resources.getString("messages.editorerrors"));
227             return;
228         }
229 
230         if (musicTag == null) {
231             LOG.error(Resources.getString("messages.filenotexists"));
232             return;
233         }
234 
235         try {
236             setBusyCursor(true);
237             if (this.musicTag.renameFile(this.getSettings().getFileFormatMusic())) {
238                 getTrack().setTrackUrl(this.musicTag.getAbsolutePath());
239                 commit();
240             }
241         } catch (InfrastructureException ex) {
242             LOG.error(ex.getMessage());
243         } catch (Exception ex) {
244             LOG.error("Error renaming file.", ex);
245         } finally {
246             setBusyCursor(false);
247         }
248     }
249 
250     /* (non-Javadoc)
251      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#rollback()
252      */
253     public void rollback() {
254         super.rollback();
255         try {
256             setBusyCursor(true);
257             // try to reload from database
258             Track track = getTrack();
259             HibernateDao.refresh(track);
260             updateView();
261             super.rollback();
262         } catch (Exception ex) {
263             LOG.error("Error refreshing track.", ex);
264         } finally {
265             setBusyCursor(false);
266         }
267     }
268 
269     /**
270      * Gets the title for the title bar.
271      * <p>
272      * @return the title to put on the title bar
273      */
274     protected String getTitleSuffix() {
275         return getTrack().getDisplayText(getSettings().getDisplayFormatTrack());
276     }
277 
278     /**
279      * Writes view contents to the underlying model.
280      */
281     protected void updateModel() {
282         Track track = getTrack();
283 
284         // compare any fields that may have changed.
285         if (!StringUtils.equals(track.getName(), titleField.getText())) {
286             track.setName(titleField.getText());
287         }
288         if (!StringUtils.equalsIgnoreCase(track.getTrackNumber(), trackNumber.getText())) {
289             track.setTrackNumber(trackNumber.getText());
290         }
291         if (!StringUtils.equalsIgnoreCase(track.getComment(), comment.getText())) {
292             track.setComment(comment.getText());
293         }
294 
295         if (musicTag != null) {
296             musicTag.setTitle(titleField.getText());
297             musicTag.setTrack(trackNumber.getText());
298             musicTag.setComment(comment.getText());
299         }
300     }
301 
302     /**
303      * Reads view contents from the underlying model.
304      */
305     protected void updateView() {
306         Track track = getTrack();
307         titleField.setText(track.getName());
308         trackNumber.setText(track.getTrackNumber());
309         comment.setText(track.getComment());
310         location.setText(track.getTrackUrl());
311         location.setToolTipText(track.getTrackUrl());
312         createdDateLabel.setText(DATE_FORMAT.format(track.getCreatedDate()));
313         createdByLabel.setText(track.getCreatedUser());
314         modifiedDateLabel.setText(DATE_FORMAT.format(track.getModifiedDate()));
315         modifiedByLabel.setText(track.getModifiedUser());
316         albumImage.setDisc(track.getDisc());
317         if (StringUtils.isNotBlank(track.getDisc().getCoverUrl())) {
318             int dimension = this.getSettings().getCoverSizeSmall();
319             albumImage.setImage(ImageFactory.getScaledImage(track.getDisc().getCoverUrl(), dimension, dimension).getImage());
320         } else {
321             albumImage.setImage(null);
322         }
323 
324         // try and open the tag, but fail safely
325         try {
326             musicTag = TagFactory.getTag(track.getTrackUrl());
327             duration.setText(musicTag.getTrackLengthAsString());
328             layer.setText(musicTag.getLayer());
329             version.setText(musicTag.getVersion());
330             bitRate.setText(musicTag.getBitRateAsString());
331             frequency.setText(musicTag.getFrequency() + " Hz");
332             mode.setText(musicTag.getMode());
333             fileSize.setText(FileUtils.byteCountToDisplaySize(musicTag.getFile().length()));
334             emphasis.setText(musicTag.getEmphasis());
335             copyrighted.setText(musicTag.getCopyrighted());
336         } catch (MusicTagException ex) {
337             LOG.info(ex.getMessage(), ex);
338             duration.setText("");
339             layer.setText("");
340             version.setText("");
341             bitRate.setText("");
342             frequency.setText("");
343             mode.setText("");
344             fileSize.setText("");
345             emphasis.setText("");
346             copyrighted.setText("");
347             musicTag = null;
348         }
349     }
350 
351     /**
352      * Gets the domain object associated with this editor.
353      * <p>
354      * @return an Track instance associated with this editor
355      */
356     private Track getTrack() {
357         return (Track)getModel();
358     }
359 
360     /**
361      * Builds the Music information panel.
362      * <p>
363      * @return the panel to display the music info
364      */
365     private JComponent buildMusicPanel() {
366         FormLayout layout = new FormLayout("right:max(14dlu;pref), 4dlu, left:min(80dlu;pref), 100px, right:max(14dlu;pref),pref:grow, 4px",
367                                            "p, 4px, p, 4px, p, 4px, p, 4px, p, 4px");
368 
369         layout.setRowGroups(new int[][] {
370                                 { 1, 3 }
371                             });
372         PanelBuilder builder = new PanelBuilder(layout);
373         CellConstraints cc = new CellConstraints();
374         builder.addLabel(Resources.getString("label.file") + ": ", cc.xy(1, 1));
375         builder.add(location, cc.xyw(3, 1, 5));
376         builder.addLabel(Resources.getString("label.duration") + ": ", cc.xy(1, 3));
377         builder.add(duration, cc.xy(3, 3));
378         builder.addLabel(Resources.getString("label.layer") + ": ", cc.xy(5, 3));
379         builder.add(layer, cc.xy(6, 3));
380         builder.addLabel(Resources.getString("label.bitrate") + ": ", cc.xy(1, 5));
381         builder.add(bitRate, cc.xyw(3, 5, 4));
382         builder.addLabel(Resources.getString("label.version") + ": ", cc.xy(5, 5));
383         builder.add(version, cc.xy(6, 5));
384         builder.addLabel(Resources.getString("label.frequency") + ": ", cc.xy(1, 7));
385         builder.add(frequency, cc.xy(3, 7));
386         builder.addLabel(Resources.getString("label.mode") + ": ", cc.xy(5, 7));
387         builder.add(mode, cc.xy(6, 7));
388         builder.addLabel(Resources.getString("label.filesize") + ": ", cc.xy(1, 9));
389         builder.add(fileSize, cc.xy(3, 9));
390         builder.addLabel(Resources.getString("label.copyright") + ": ", cc.xy(5, 9));
391         builder.add(copyrighted, cc.xy(6, 9));
392 
393         return builder.getPanel();
394     }
395 
396     private JToolBar buildToolBar() {
397         final ToolBarBuilder bar = new ToolBarBuilder("Track Toolbar");
398         ToolBarButton button = null;
399         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.UNLOCK_ID);
400         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
401         bar.add(button);
402         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.COMMIT_ID);
403         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
404         bar.add(button);
405         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.ROLLBACK_ID);
406         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
407         bar.add(button);
408         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.DELETE_ID);
409         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
410         bar.add(button);
411         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.FILE_RENAME_ID);
412         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
413         bar.add(button);
414         return bar.getToolBar();
415     }
416 
417     /**
418      * Builds the Track editor panel.
419      * <p>
420      * @return the panel to edit track info.
421      */
422     private JComponent buildTrackPanel() {
423         FormLayout layout = new FormLayout("right:max(14dlu;pref), 4dlu, left:min(80dlu;pref):grow, pref, 40dlu, pref, right:pref:grow",
424                                            "4px, p, 4px, p, 4px, p, " + this.getSettings().getCoverSizeSmall() + "px");
425 
426         PanelBuilder builder = new PanelBuilder(layout);
427         CellConstraints cc = new CellConstraints();
428 
429         builder.addLabel(Resources.getString("label.tracknumber") + ": ", cc.xy(1, 2));
430         builder.add(trackNumber, cc.xy(3, 2));
431         builder.add(albumImage, cc.xywh(7, 2, 1, 6, "right,top"));
432         builder.addLabel(Resources.getString("label.title") + ": ", cc.xy(1, 4));
433         builder.add(titleField, cc.xyw(3, 4, 3));
434         builder.add(ComponentFactory.createTitleCaseButton(titleField), cc.xy(6, 4));
435         builder.addLabel(Resources.getString("label.comment") + ": ", cc.xy(1, 6));
436         builder.add(comment, cc.xyw(3, 6, 3));
437         return new IconFeedbackPanel(getValidationModel().getValidationResultModel(), builder.getPanel());
438     }
439 
440     /**
441      * Initializes validation annotations.
442      */
443     private void initComponentAnnotations() {
444         ValidationComponentUtils.setInputHint(titleField, "Track Title is Mandatory, Length <= 100");
445         ValidationComponentUtils.setMandatory(titleField, true);
446         ValidationComponentUtils.setMessageKey(titleField, "Track.Title");
447         ValidationComponentUtils.setInputHint(trackNumber, "Track Number is Mandatory, must be at least 2 digits");
448         ValidationComponentUtils.setMandatory(trackNumber, true);
449         ValidationComponentUtils.setMessageKey(trackNumber, "Track.Track Number");
450         ValidationComponentUtils.setInputHint(comment, "Comment Length <= 254");
451         ValidationComponentUtils.setMessageKey(comment, "Track.Comment");
452     }
453 
454     /**
455      *  Creates and configures the UI components;
456      */
457     private void initComponents() {
458         validationModel = new TrackValidationModel(new Track());
459 
460         headerToolbar = buildToolBar();
461         titleField = ComponentFactory.createTextField(validationModel.getModel(Track.PROPERTYNAME_NAME), false);
462         trackNumber = ComponentFactory.createTextField(validationModel.getModel(Track.PROPERTYNAME_TRACK_NUMBER),
463                                                        false);
464         ((JTextField)trackNumber).setColumns(4);
465         comment = ComponentFactory.createTextField(validationModel.getModel(Track.PROPERTYNAME_COMMENT), false);
466         location = ComponentFactory.createLabel(getValidationModel().getModel(Track.PROPERTYNAME_TRACK_URL));
467         duration = UIFactory.createBoldLabel("");
468         layer = UIFactory.createBoldLabel("");
469         version = UIFactory.createBoldLabel("");
470         bitRate = UIFactory.createBoldLabel("");
471         frequency = UIFactory.createBoldLabel("");
472         mode = UIFactory.createBoldLabel("");
473         fileSize = UIFactory.createBoldLabel("");
474         emphasis = UIFactory.createBoldLabel("");
475         copyrighted = UIFactory.createBoldLabel("");
476 
477         albumImage = new AlbumImage(new Dimension(this.getSettings().getCoverSizeSmall(),
478                                                   this.getSettings().getCoverSizeSmall()));
479         ActionListener actionListener = new ActionListener() {
480             public void actionPerformed(ActionEvent event) {
481                 AlbumImage preview = (AlbumImage)event.getSource();
482                 if (preview.getDisc() != null) {
483                     setBusyCursor(true);
484                     getMainModule().selectNodeInTree(preview.getDisc());
485                     setBusyCursor(false);
486                 }
487             }
488         };
489         albumImage.addActionListener(actionListener);
490     }
491 
492 }