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