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   import java.text.MessageFormat;
8   import java.util.Collection;
9   import java.util.HashMap;
10  import java.util.Iterator;
11  import java.util.Random;
12  
13  import javax.swing.JComponent;
14  import javax.swing.JTextArea;
15  import javax.swing.JToolBar;
16  import javax.swing.ProgressMonitor;
17  import javax.swing.Timer;
18  import javax.swing.UIManager;
19  import javax.swing.text.JTextComponent;
20  
21  import org.apache.commons.lang.StringEscapeUtils;
22  import org.apache.commons.lang.StringUtils;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  import com.jgoodies.forms.builder.PanelBuilder;
27  import com.jgoodies.forms.layout.CellConstraints;
28  import com.jgoodies.forms.layout.FormLayout;
29  import com.jgoodies.uif.builder.ToolBarBuilder;
30  import com.jgoodies.uif.component.ToolBarButton;
31  import com.jgoodies.uif.util.ResourceUtils;
32  import com.jgoodies.validation.view.ValidationComponentUtils;
33  import com.melloware.jukes.db.HibernateDao;
34  import com.melloware.jukes.db.HibernateUtil;
35  import com.melloware.jukes.db.orm.Artist;
36  import com.melloware.jukes.db.orm.Disc;
37  import com.melloware.jukes.exception.InfrastructureException;
38  import com.melloware.jukes.file.image.ImageFactory;
39  import com.melloware.jukes.gui.tool.Actions;
40  import com.melloware.jukes.gui.tool.Resources;
41  import com.melloware.jukes.gui.view.component.AlbumImage;
42  import com.melloware.jukes.gui.view.component.ComponentFactory;
43  import com.melloware.jukes.gui.view.tasks.TimerListener;
44  import com.melloware.jukes.gui.view.tasks.UpdateTagsTask;
45  import com.melloware.jukes.gui.view.validation.ArtistValidationModel;
46  import com.melloware.jukes.gui.view.validation.IconFeedbackPanel;
47  import com.melloware.jukes.util.MessageUtil;
48  
49  /**
50   * An implementation of {@link Editor} that displays a {@link Artist}.<p>
51   *
52   * This container uses a <code>FormLayout</code> and the panel building
53   * is done with the <code>PanelBuilder</code> class.
54   * Columns and rows are specified before the panel is filled with components.
55   * <p>
56   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
57   * @author Emil A. Lefkof III <info@melloware.com>
58   * @version 4.0
59   * AZ - some modifications made 2009
60   */
61  @SuppressWarnings("unchecked")
62  public final class ArtistEditor
63      extends AbstractEditor {
64  
65      private static final Log LOG = LogFactory.getLog(ArtistEditor.class);
66      private AlbumImage image1;
67      private AlbumImage image2;
68      private AlbumImage image3;
69      private AlbumImage image4;
70      private AlbumImage image5;
71      private AlbumImage image6;
72      private JComponent artistPanel;
73      private JTextArea notesField;
74      private JTextComponent nameField;
75      private JToolBar headerToolbar;
76  
77      /**
78       * Constructs a <code>ArtistEditor</code>.
79       */
80      public ArtistEditor() {
81          super(Resources.ARTIST_TREE_ICON);
82      }
83  
84      /**
85       * Gets the domain class associated with this editor.
86       */
87      public Class getDomainClass() {
88          return Artist.class;
89      }
90  
91      /* (non-Javadoc)
92       * @see com.melloware.jukes.gui.view.editor.AbstractEditor#getHeaderToolBar()
93       */
94      public JToolBar getHeaderToolBar() {
95          return headerToolbar;
96      }
97  
98      /**
99       * Builds the content pane.
100      */
101     public void build() {
102         initComponents();
103         initComponentAnnotations();
104         initEventHandling();
105 
106         artistPanel = buildArtistPanel();
107 
108         FormLayout layout = new FormLayout("fill:pref:grow",
109                                            "max(14dlu;pref), 3dlu, p, 4px, p, 4px, p, 4px, p, 4px, p, 4px, p");
110 
111         setLayout(layout);
112         PanelBuilder builder = new PanelBuilder(layout, this);
113         builder.setDefaultDialogBorder();
114         CellConstraints cc = new CellConstraints();
115 
116         builder.add(buildHintAreaPane(), cc.xy(1, 1));
117         builder.addSeparator(Resources.getString("label.artist"), cc.xy(1, 3));
118         builder.add(artistPanel, cc.xy(1, 5));
119         builder.addSeparator(Resources.getString("label.featured.discs"), cc.xy(1, 7));
120         builder.add(buildThumbnailPanel(), cc.xy(1, 9));
121         JComponent audit = buildAuditInfoPanel();
122         if (this.getSettings().isAuditInfo()) {
123             builder.addSeparator(Resources.getString("label.auditinfo"), cc.xy(1, 11));
124             builder.add(audit, cc.xy(1, 13));
125         }
126 
127     }
128 
129     /**
130      * Commits with the update tags flag on meaning it will write the ID3 tags
131      * to all tracks.
132      * AZ - commit with update flag as specified in Settings
133      */
134     public void commit() {
135         commit(this.getSettings().isUpdateTags());
136     }
137 
138     /* (non-Javadoc)
139      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#delete()
140      */
141     public void delete() {
142         super.delete();
143         try {
144             if (!MessageUtil.confirmDelete(this)) {
145                 return;
146             }
147             setBusyCursor(true);
148             // try to delete from database
149             Artist artist = getArtist();
150             HibernateUtil.beginTransaction();
151             //AZ: no refreshing to speed-up processing
152             //HibernateDao.refresh(artist);
153             HibernateDao.delete(artist);
154             HibernateUtil.commitTransaction();
155             
156             // AZ : If transaction is committed and copies of images are used
157             //      then delete the image copy for all discs
158             if (this.getSettings().isCopyImagesToDirectory()) {
159               for (Iterator iter = artist.getDiscs().iterator(); iter.hasNext();) {
160                 Disc disc = (Disc)iter.next();
161                	final String oldImageName = ImageFactory.standardImageFileName(artist.getName(), disc.getName(), disc.getYear());
162                  File oldImageFile = new File (oldImageName); 
163                   if ( oldImageFile.exists() ) {
164                       if (!oldImageFile.delete()) {
165                     	  LOG.debug("Error deleting file: " + oldImageFile.getAbsolutePath()); 
166                       }
167                   }
168               }
169             }
170 
171             // reset dirty flag since we are deleting
172             getValidationModel().setDirty(false);
173             // tell the tree to select the parent node
174             this.getMainModule().refreshSelection(null, Resources.NODE_DELETED);
175         } catch (Exception ex) {
176            	final String errorMessage = ResourceUtils.getString("messages.ErrorDeletingArtist"); 
177             MessageUtil.showError(this, errorMessage); //AZ
178             LOG.error(errorMessage, ex);
179             HibernateUtil.rollbackTransaction();
180         } finally {
181             setBusyCursor(false);
182         }
183     }
184 
185     /* (non-Javadoc)
186      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#rollback()
187      */
188     public void rollback() {
189         super.rollback();
190         try {
191             setBusyCursor(true);
192             // try to reload from database
193             Artist artist = getArtist();
194             HibernateDao.refresh(artist);
195             updateView();
196             super.rollback();
197         } catch (Exception ex) {
198            	final String errorMessage = ResourceUtils.getString("messages.ErrorRefreshingArtist"); 
199             MessageUtil.showError(this, errorMessage); //AZ
200             LOG.error(errorMessage, ex);
201         } finally {
202             setBusyCursor(false);
203         }
204     }
205 
206     /**
207      * Gets the title for the title bar.
208      * <p>
209      * @return the title to put on the title bar
210      */
211     protected String getTitleSuffix() {
212         return getArtist().getName();
213     }
214 
215     /**
216      * Writes view contents to the underlying model.
217      */
218     protected void updateModel() {
219         Artist artist = getArtist();
220         // compare any fields that may have changed.
221         if (!StringUtils.equals(artist.getName(), nameField.getText())) {
222             artist.setName(nameField.getText());
223         }
224         if (!StringUtils.equalsIgnoreCase(artist.getNotes(), notesField.getText())) {
225             artist.setNotes(notesField.getText());
226         }
227     }
228 
229     /**
230      * Reads view contents from the underlying model.
231      */
232     protected void updateView() {
233         Artist artist = getArtist();
234         nameField.setText(artist.getName());
235         notesField.setText(artist.getNotes());
236         createdDateLabel.setText(DATE_FORMAT.format(artist.getCreatedDate()));
237         createdByLabel.setText(artist.getCreatedUser());
238         modifiedDateLabel.setText(DATE_FORMAT.format(artist.getModifiedDate()));
239         modifiedByLabel.setText(artist.getModifiedUser());
240 
241         image1.setDisc(null);
242         image2.setDisc(null);
243         image3.setDisc(null);
244         image4.setDisc(null);
245         image5.setDisc(null);
246         image6.setDisc(null);
247 
248         // create random number generator to randomly pick 5 covers
249         Random random = new Random();
250 
251         Collection discs = this.getDiscCollection();
252         // grab till we have 5 random numbers
253         HashMap map = new HashMap();
254         while ((map.size() < 6) && (map.size() <= (discs.size() - 1))) {
255             Integer rand = Integer.valueOf(random.nextInt(discs.size()));
256             map.put(rand, rand);
257         }
258         int count = 0;
259         Object[] array = discs.toArray();
260         Iterator iterator = map.values().iterator();
261         IMAGE_LOOP:
262         while (iterator.hasNext()) {
263             Integer element = (Integer)iterator.next();
264             Disc disc = (Disc)array[element.intValue()];
265             AlbumImage preview = null;
266             switch (count) {
267                 case 0: {
268                     preview = image1;
269                     break;
270                 }
271                 case 1: {
272                     preview = image2;
273                     break;
274                 }
275                 case 2: {
276                     preview = image3;
277                     break;
278                 }
279                 case 3: {
280                     preview = image4;
281                     break;
282                 }
283                 case 4: {
284                     preview = image5;
285                     break;
286                 }
287                 case 5: {
288                     preview = image6;
289                     break;
290                 }
291                 default: {
292                     break IMAGE_LOOP;
293                 }
294             }
295             int dimension = this.getSettings().getCoverSizeSmall();
296             final String currentCoverUrl;
297             if (this.getSettings().isCopyImagesToDirectory()) {
298             	currentCoverUrl = ImageFactory.standardImageFileName(disc.getArtist().getName(), disc.getName(), disc.getYear());
299             }
300             else {
301             	currentCoverUrl = disc.getCoverUrl();
302             }
303             preview.setImage(ImageFactory.getScaledImage(currentCoverUrl, dimension, dimension).getImage());
304             preview.setDisc(disc);
305             count++;
306         }
307     }
308 
309     /**
310      * Gets the domain object associated with this editor.
311      * <p>
312      * @return an Artist instance associated with this editor
313      */
314     private Artist getArtist() {
315         return (Artist)getModel();
316     }
317 
318     /**
319      * Gets the discs for this artist.  If filtering then apply the filter
320      * else just return all of the artists discs.
321      * <p>
322      * @return the Collection containing the discs
323      */
324     private Collection getDiscCollection() {
325         Collection results = null;
326         final String filter = this.getSettings().getFilter();
327 
328         // if a filter was applied the use the filter HQL
329         if (StringUtils.isNotBlank(filter)) {
330             final String resource = ResourceUtils.getString("hql.filter.disc");
331             final String hql = MessageFormat.format(resource, new Object[] { getArtist().getId(), filter });
332             LOG.debug(hql);
333             results = HibernateDao.findByQuery(hql);
334         } else {
335             // else just get all discs for this artist
336             results = getArtist().getDiscs();
337         }
338 
339         return results;
340     }
341 
342     /**
343      * Builds the Artist editor panel.
344      * <p>
345      * @return the panel to edit artist info.
346      */
347     private JComponent buildArtistPanel() {
348         FormLayout layout = new FormLayout("right:max(14dlu;pref), 4dlu, left:min(80dlu;pref):grow, pref, 4dlu, 25px",
349                                            "p, 4px, p, 7px");
350 
351         PanelBuilder builder = new PanelBuilder(layout);
352         CellConstraints cc = new CellConstraints();
353 
354         builder.addLabel(Resources.getString("label.name") + ": ", cc.xy(1, 1));
355         builder.add(nameField, cc.xyw(3, 1, 3));
356         builder.add(ComponentFactory.createTitleCaseButton(nameField), cc.xy(6, 1));
357         builder.addLabel(Resources.getString("label.notes") + ": ", cc.xy(1, 3, "left,top"));
358         builder.add(notesField, cc.xyw(3, 3, 3));
359 
360         return new IconFeedbackPanel(getValidationModel().getValidationResultModel(), builder.getPanel());
361     }
362 
363     /**
364      * Builds the artist album cover thumbnail panel.
365      * <p>
366      * @return the artist cover thumbnail panel
367      */
368     private JComponent buildThumbnailPanel() {
369         FormLayout layout = new FormLayout("4px, pref, pref, pref, pref, pref, pref, pref, 4px", "p, 7px");
370 
371         PanelBuilder builder = new PanelBuilder(layout);
372         CellConstraints cc = new CellConstraints();
373 
374         builder.add(image1, cc.xyw(2, 1, 1));
375         builder.add(image2, cc.xyw(3, 1, 1));
376         builder.add(image3, cc.xyw(4, 1, 1));
377         builder.add(image4, cc.xyw(5, 1, 1));
378         builder.add(image5, cc.xyw(6, 1, 1));
379         builder.add(image5, cc.xyw(7, 1, 1));
380         builder.add(image6, cc.xyw(8, 1, 1));
381 
382         return builder.getPanel();
383     }
384 
385     private JToolBar buildToolBar() {
386         final ToolBarBuilder bar = new ToolBarBuilder("Artist Toolbar");
387         ToolBarButton button = null;
388         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.UNLOCK_ID);
389         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
390         bar.add(button);
391         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.COMMIT_ID);
392         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
393         bar.add(button);
394         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.ROLLBACK_ID);
395         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
396         bar.add(button);
397         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.DELETE_ID);
398         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
399         bar.add(button);
400         return bar.getToolBar();
401     }
402 
403     /**
404      * Commits to the database and if updateTags flag is set to true it updates
405      * the ID3 tags as well.
406      * <p>
407      * @param aUpdateTags true to update tags, false to not update tags
408      */
409     private void commit(boolean aUpdateTags) {
410         super.commit();
411         Artist artist = getArtist();
412         Artist foundArtist = null;
413         String oldArtistName = artist.getName();
414         boolean hasErrors = false;
415         boolean artistChanged = (!StringUtils.equals(artist.getName(), nameField.getText()));
416         /** AZ Commit Notes Changes **/
417         boolean notesChanged = (!StringUtils.equalsIgnoreCase(artist.getNotes(), notesField.getText()));
418         
419         if (notesChanged) {
420             artist.setNotes(notesField.getText());
421             }
422         if (artistChanged) {   
423         // try to persist
424         try {
425             setBusyCursor(true);
426             // see if this artist exists, if so move all the discs
427             final String resource = ResourceUtils.getString("hql.artist.findCaseSensitive");
428             final String hql = MessageFormat.format(resource,
429                                                     new Object[] { StringEscapeUtils.escapeSql(nameField.getText()) });
430             foundArtist = (Artist)HibernateDao.findUniqueByQuery(hql);
431 
432             if (foundArtist == null) {
433               updateModel();
434             }
435 
436             // check for validation errors, if any then do no changes
437             hasErrors = hasErrors();
438             if (hasErrors) {
439             	MessageUtil.showError(this, Resources.getString("messages.editorerrors")); //AZ
440                 LOG.error(Resources.getString("messages.editorerrors"));
441                 return;
442             }
443 
444             HibernateUtil.beginTransaction();
445             if (foundArtist != null) {
446                 // if artist was found add all discs under the new artist
447                 for (Iterator iter = artist.getDiscs().iterator(); iter.hasNext();) {
448                     Disc disc = (Disc)iter.next();
449                     foundArtist.addDisc(disc);
450                 }
451                 HibernateDao.persist(foundArtist);
452             }
453             HibernateDao.saveOrUpdate(artist);
454             HibernateUtil.commitTransaction();
455             // AZ : If transaction is committed and copies of images are used
456             //      then change the name of the image copy for all discs
457             if (this.getSettings().isCopyImagesToDirectory()) {
458               for (Iterator iter = artist.getDiscs().iterator(); iter.hasNext();) {
459                 Disc disc = (Disc)iter.next();
460                	final String oldImageName = ImageFactory.standardImageFileName(oldArtistName, disc.getName(), disc.getYear());
461                  File oldImageFile = new File (oldImageName); 
462                   if ( oldImageFile.exists() ) {
463                       final String newImageName = ImageFactory.standardImageFileName(nameField.getText(), disc.getName(), disc.getYear());
464                       final File newImageFile = new File (newImageName);
465                       oldImageFile.renameTo(newImageFile); 
466                   }
467               }
468             }
469  
470             // now update this editor and the treeview
471             updateView();
472         } catch (InfrastructureException ex) {
473             HibernateUtil.rollbackTransaction();
474        	    final String errorMessage = ResourceUtils.getString("messages.ArtistNameUnique"); 
475             MessageUtil.showError(this, errorMessage); //AZ 
476             LOG.error(errorMessage);
477             HibernateDao.refresh(artist);
478             hasErrors = true;
479         } catch (Exception ex) {
480             HibernateUtil.rollbackTransaction();
481        	    final String errorMessage = ResourceUtils.getString("messages.ErrorUpdatingArtist"); 
482             MessageUtil.showError(this, errorMessage); //AZ 
483             LOG.error(errorMessage, ex);
484             HibernateDao.refresh(artist);
485             hasErrors = true;
486         } finally {
487             setBusyCursor(false);
488         }
489 
490         if (!hasErrors) {
491             if (aUpdateTags && artistChanged) {
492                 // now update the ID3 tags
493                 final Artist whichArtist = (foundArtist == null) ? artist : foundArtist;
494                 task = new UpdateTagsTask(whichArtist);
495                 //AZ: Put the Title of Progress Monitor Dialog Box and Cancel button
496                 UIManager.put("ProgressMonitor.progressText", Resources.getString("label.ProgressTitle"));
497                 UIManager.put("OptionPane.cancelButtonText", Resources.getString("label.Cancel"));
498                 
499                 progressMonitor = new ProgressMonitor(getMainFrame(), Resources.getString("messages.updatetracks"), "",
500                                                       0, (int)task.getLengthOfTask());
501                 progressMonitor.setProgress(0);
502                 progressMonitor.setMillisToDecideToPopup(10);
503                 task.go();
504                 timer = new Timer(50, null);
505                 timer.addActionListener(new TimerListener(progressMonitor, task, timer));
506                 timer.start();
507             } else {
508                 MessageUtil.showSuccess(this);
509             }
510             super.commit();
511         }
512 
513         if (foundArtist == null) {
514             this.getMainModule().refreshSelection(artist, Resources.NODE_CHANGED);
515         } else {
516             this.getMainModule().refreshTree();
517         }
518         }  //If Artist changed
519     }
520 
521     /**
522      * Initializes validation annotations.
523      */
524     private void initComponentAnnotations() {
525         ValidationComponentUtils.setInputHint(nameField, Resources.getString("messages.NameIsMandatory"));
526         ValidationComponentUtils.setMandatory(nameField, true);
527         ValidationComponentUtils.setMessageKey(nameField, "Artist.Name");
528         ValidationComponentUtils.setInputHint(notesField, Resources.getString("messages.NotesLength"));
529         ValidationComponentUtils.setMessageKey(notesField, "Artist.Notes");
530     }
531 
532     /**
533      *  Creates and configures the UI components;
534      */
535     private void initComponents() {
536         validationModel = new ArtistValidationModel(new Artist());
537         headerToolbar = buildToolBar();
538         final Dimension dimension = new Dimension(this.getSettings().getCoverSizeSmall(),
539                                             this.getSettings().getCoverSizeSmall());
540         image1 = new AlbumImage(dimension);
541         image2 = new AlbumImage(dimension);
542         image3 = new AlbumImage(dimension);
543         image4 = new AlbumImage(dimension);
544         image5 = new AlbumImage(dimension);
545         image6 = new AlbumImage(dimension);
546         nameField = ComponentFactory.createTextField(getValidationModel().getModel(Artist.PROPERTYNAME_NAME), false);
547         notesField = ComponentFactory.createTextArea(getValidationModel().getModel(Artist.PROPERTYNAME_NOTES), false);
548         notesField.setLineWrap(true);
549         notesField.setWrapStyleWord(true);
550         notesField.setRows(3);
551 
552         ActionListener actionListener = new ActionListener() {
553             public void actionPerformed(ActionEvent event) {
554                 AlbumImage preview = (AlbumImage)event.getSource();
555                 if (preview.getDisc() != null) {
556                     setBusyCursor(true);
557                     getMainModule().selectNodeInTree(preview.getDisc());
558                     setBusyCursor(false);
559                 }
560             }
561         };
562         image1.addActionListener(actionListener);
563         image2.addActionListener(actionListener);
564         image3.addActionListener(actionListener);
565         image4.addActionListener(actionListener);
566         image5.addActionListener(actionListener);
567         image6.addActionListener(actionListener);
568 
569     }
570 
571 }