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.MouseAdapter;
6   import java.awt.event.MouseEvent;
7   import java.awt.event.MouseListener;
8   import java.io.File;
9   import java.io.IOException;
10  import java.text.MessageFormat;
11  import java.util.ArrayList;
12  import java.util.Collections;
13  import java.util.Iterator;
14  import java.util.List;
15  
16  import javax.swing.DefaultListModel;
17  import javax.swing.JComboBox;
18  import javax.swing.JComponent;
19  import javax.swing.JFileChooser;
20  import javax.swing.JLabel;
21  import javax.swing.JList;
22  import javax.swing.JPopupMenu;
23  import javax.swing.JTextArea;
24  import javax.swing.JTextField;
25  import javax.swing.JToolBar;
26  import javax.swing.ListSelectionModel;
27  import javax.swing.ProgressMonitor;
28  import javax.swing.Timer;
29  import javax.swing.text.JTextComponent;
30  
31  import org.apache.commons.lang.StringEscapeUtils;
32  import org.apache.commons.lang.StringUtils;
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  
36  import com.jgoodies.binding.list.SelectionInList;
37  import com.jgoodies.binding.value.ValueModel;
38  import com.jgoodies.binding.adapter.ComboBoxAdapter; //AZ
39  import com.jgoodies.forms.builder.PanelBuilder;
40  import com.jgoodies.forms.layout.CellConstraints;
41  import com.jgoodies.forms.layout.FormLayout;
42  import com.jgoodies.uif.action.ActionManager;
43  import com.jgoodies.uif.application.Application;
44  import com.jgoodies.uif.builder.ToolBarBuilder;
45  import com.jgoodies.uif.component.ToolBarButton;
46  import com.jgoodies.uif.panel.SimpleInternalFrame;
47  import com.jgoodies.uif.util.ResourceUtils;
48  import com.jgoodies.uifextras.util.UIFactory;
49  import com.jgoodies.validation.util.ValidationUtils;
50  import com.jgoodies.validation.view.ValidationComponentUtils;
51  import com.melloware.jukes.db.HibernateDao;
52  import com.melloware.jukes.db.HibernateUtil;
53  import com.melloware.jukes.db.orm.Artist;
54  import com.melloware.jukes.db.orm.Catalog;
55  import com.melloware.jukes.db.orm.Disc;
56  import com.melloware.jukes.db.orm.Track;
57  import com.melloware.jukes.exception.InfrastructureException;
58  import com.melloware.jukes.file.FileUtil;
59  import com.melloware.jukes.file.filter.FilterFactory;
60  import com.melloware.jukes.file.image.ChooserImagePreview;
61  import com.melloware.jukes.file.image.ImageFactory;
62  import com.melloware.jukes.file.image.ImageFileView;
63  import com.melloware.jukes.file.tag.MusicTag;
64  import com.melloware.jukes.file.tag.TagFactory;
65  import com.melloware.jukes.gui.tool.Actions;
66  import com.melloware.jukes.gui.tool.Resources;
67  import com.melloware.jukes.gui.view.MainMenuBuilder;
68  import com.melloware.jukes.gui.view.component.AlbumImage;
69  import com.melloware.jukes.gui.view.component.ComponentFactory;
70  import com.melloware.jukes.gui.view.component.TrackListCellRenderer;
71  import com.melloware.jukes.gui.view.dialogs.WebSearchDialog;
72  import com.melloware.jukes.gui.view.tasks.TimerListener;
73  import com.melloware.jukes.gui.view.tasks.UpdateTagsTask;
74  import com.melloware.jukes.gui.view.validation.DiscValidationModel;
75  import com.melloware.jukes.gui.view.validation.ArtistValidationModel; //AZ
76  import com.melloware.jukes.gui.view.validation.IconFeedbackPanel;
77  import com.melloware.jukes.util.JukesValidationMessage;
78  import com.melloware.jukes.util.MessageUtil;
79  
80  /**
81   * An implementation of {@link Editor} that displays a {@link Disc}.<p>
82   *
83   * This container uses a <code>FormLayout</code> and the panel building
84   * is done with the <code>PanelBuilder</code> class.
85   * Columns and rows are specified before the panel is filled with components.
86   * <p>
87   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
88   * @author Emil A. Lefkof III <info@melloware.com>
89   * @version 4.0
90   * AZ - some modifications 2009
91   */
92  @SuppressWarnings("unchecked")
93  public final class DiscEditor
94      extends AbstractEditor {
95  
96      private static final Log LOG = LogFactory.getLog(DiscEditor.class);
97      private static final List GENRE_LIST = MusicTag.getGenreTypes();
98      private AlbumImage albumImage;
99      private DefaultListModel listModel;
100     private JComboBox genre;
101     private JComponent discPanel;
102     private JComponent genreEditor;
103     private JLabel genreLabel;
104     private JLabel location;
105     private JList trackList;
106     private JTextArea notesField;
107     private JTextComponent nameField;
108     private JTextComponent year;
109     private JTextComponent artistField; // AZ artistField 
110     private JToolBar headerToolbar;
111     private List tracks;
112     private SimpleInternalFrame listScrollPane;
113 
114     /**
115      * Constructs a <code>DiscEditor</code>.
116      */
117     public DiscEditor() {
118         super(Resources.DISC_TREE_ICON);
119     }
120 
121     /**
122      * Gets the domain class associated with this editor.
123      */
124     public Class getDomainClass() {
125         return Disc.class;
126     }
127 
128     /* (non-Javadoc)
129      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#getHeaderToolBar()
130      */
131     public JToolBar getHeaderToolBar() {
132         return headerToolbar;
133     }
134 
135     /**
136      * Builds the content pane.
137      */
138     public void build() {
139         initComponents();
140         initComponentAnnotations();
141         initEventHandling();
142 
143         discPanel = buildDiscPanel();
144 
145         FormLayout layout = new FormLayout("fill:pref:grow", "p, 12px, p, p, 12px, p, 12px, p, 12px, p");
146 
147         setLayout(layout);
148         PanelBuilder builder = new PanelBuilder(layout, this);
149         builder.setDefaultDialogBorder();
150         CellConstraints cc = new CellConstraints();
151 
152         builder.add(buildMusicPanel(), cc.xy(1, 1));
153         builder.add(buildHintAreaPane(), cc.xy(1, 3));
154         builder.addSeparator(Resources.getString("label.disc"), cc.xy(1, 4));
155         builder.add(discPanel, cc.xy(1, 6));
156         JComponent audit = buildAuditInfoPanel();
157         if (this.getSettings().isAuditInfo()) {
158         	builder.addSeparator(Resources.getString("label.auditinfo"), cc.xy(1, 8));
159             builder.add(audit, cc.xy(1, 10));
160 		}
161      }
162 
163     /**
164      * Commits with the update tags flag ON meaning it will write the ID3 tags
165      * to all tracks.
166      *  AZ - commit with update flag as specified in Settings
167      */
168     public void commit() {
169       /** AZ **/  commit(this.getSettings().isUpdateTags());
170     }
171 
172     /* (non-Javadoc)
173      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#delete()
174      */
175     public void delete() {
176         super.delete();
177         try {
178             if (!MessageUtil.confirmDelete(this)) {
179                 return;
180             }
181             // try to delete from database
182             setBusyCursor(true);
183             HibernateUtil.beginTransaction();
184             Disc disc = getDisc();
185             final Artist artist = disc.getArtist();
186             HibernateDao.refresh(artist);
187             artist.getDiscs().remove(disc);
188             HibernateDao.refresh(disc);
189             HibernateDao.delete(disc);
190             HibernateUtil.commitTransaction();
191             
192             // AZ : If transaction is committed and copies of images are used
193             //      then delete the image copy 
194             if (this.getSettings().isCopyImagesToDirectory()) {
195                	final String oldImageName = ImageFactory.standardImageFileName(artist.getName(), disc.getName(), disc.getYear());
196                  File oldImageFile = new File (oldImageName); 
197                   if ( oldImageFile.exists() ) {
198                       if (!oldImageFile.delete()) {
199                     	  LOG.debug("Error deleting file: " + oldImageFile.getAbsolutePath()); 
200                       }
201                   }
202             }
203 
204             // reset dirty flag since we are deleting
205             getValidationModel().setDirty(false);
206 
207             if (artist.getDiscs().size() == 0) {
208                 // refresh the tree
209                 ActionManager.get(Actions.REFRESH_ID).actionPerformed(null);
210             } else {
211                 // tell the tree to select the parent node
212                 this.getMainModule().refreshSelection(disc, Resources.NODE_DELETED);
213             }
214         } catch (Exception ex) {
215            	final String errorMessage = ResourceUtils.getString("messages.ErrorDeletingDisc"); 
216             MessageUtil.showError(this, errorMessage); //AZ
217             LOG.error(errorMessage, ex);          
218             HibernateUtil.rollbackTransaction();
219         } finally {
220             setBusyCursor(false);
221         }
222     }
223 
224     /**
225      * Let the user select another album cover.
226      */
227     public void findCover() {
228         Disc disc = getDisc();
229         if (disc.isNotValid()) {
230            	final String errorMessage = ResourceUtils.getString("messages.discnotexists");
231             LOG.error(errorMessage);
232             MessageUtil.showError(this, errorMessage); //AZ
233             return;
234         }
235 
236         updateModel();
237 
238         // check for validation errors, if any then do no changes
239         boolean hasErrors = hasErrors();
240         if (hasErrors) {
241             LOG.error(Resources.MESSAGE_EDITOR_ERRORS);
242             MessageUtil.showError(this, Resources.MESSAGE_EDITOR_ERRORS); //AZ
243             return;
244         }
245 
246         final File currentDir = new File(getDisc().getLocation());
247         JFileChooser chooser = new JFileChooser();
248         chooser.setApproveButtonText("Select");
249         chooser.setDialogTitle("Find Cover Image");
250         chooser.setCurrentDirectory(currentDir);
251         chooser.addChoosableFileFilter(FilterFactory.imageFileFilter());
252         chooser.setAcceptAllFileFilterUsed(false);
253         chooser.setFileView(new ImageFileView());
254         chooser.setAccessory(new ChooserImagePreview(chooser));
255         chooser.setMultiSelectionEnabled(false);
256         int returnVal = chooser.showOpenDialog(Application.getDefaultParentFrame());
257         if (returnVal != JFileChooser.APPROVE_OPTION) {
258             return;
259         }
260 
261         File file = chooser.getSelectedFile();
262         /** AZ **/
263         // now scale and copy cover image depending on settings
264         String imageLocation;
265 		try {
266 			imageLocation = ImageFactory.saveImageToUserDefinedDirectory(file, 
267 				disc.getArtist().getName(),
268 			    disc.getName().toString(),
269 			    disc.getYear().toString());
270 		} catch (IOException e) {
271 			// TODO Auto-generated catch block
272 			e.printStackTrace();
273 			imageLocation = file.getAbsolutePath();
274 		} 
275         // try to persist
276         try {
277             setBusyCursor(true);
278             HibernateUtil.beginTransaction();
279             disc.setCoverUrl(imageLocation);
280             HibernateDao.persist(disc);
281             HibernateUtil.commitTransaction();
282 
283             // now update this editor
284             updateView();
285         } catch (InfrastructureException ex) {
286            	final String errorMessage = ResourceUtils.getString("messages.DiscUnique"); 
287             MessageUtil.showError(this, errorMessage); //AZ
288             LOG.error(errorMessage, ex);
289             HibernateUtil.rollbackTransaction();
290             HibernateDao.refresh(disc);
291         } catch (Exception ex) {
292            	final String errorMessage = ResourceUtils.getString("messages.ErrorUpdatingCoverImage"); 
293             MessageUtil.showError(this, errorMessage); //AZ
294             LOG.error(errorMessage, ex);
295             HibernateUtil.rollbackTransaction();
296             HibernateDao.refresh(disc);
297         } finally {
298             setBusyCursor(false);
299         }
300     }
301 
302     /**
303      * Rename the file to a good format.
304      */
305     public void renameFiles() {
306         if (getDisc().isNotValid()) {
307            	final String errorMessage = ResourceUtils.getString("messages.discnotexists"); 
308             MessageUtil.showError(this, errorMessage); //AZ
309             LOG.error(errorMessage);
310             return;
311         }
312 
313         updateModel();
314 
315         // check for validation errors, if any then do no changes
316         boolean hasErrors = hasErrors();
317         if (hasErrors) {
318             LOG.error(Resources.MESSAGE_EDITOR_ERRORS);
319             MessageUtil.showError(this, Resources.MESSAGE_EDITOR_ERRORS); //AZ
320             return;
321         }
322 
323         MusicTag musicTag = null;
324         try {
325             setBusyCursor(true);
326             for (Iterator iter = getDisc().getTracks().iterator(); iter.hasNext();) {
327                 Track track = (Track)iter.next();
328                 final File file = new File(track.getTrackUrl());
329 
330                 if (file.exists()) {
331                     musicTag = TagFactory.getTag(file);
332                     if (musicTag.renameFile(this.getSettings().getFileFormatMusic())) {
333                         track.setTrackUrl(musicTag.getAbsolutePath());
334                     }
335                 }
336             }
337             commit(false);
338         } catch (InfrastructureException ex) {
339             LOG.error(ex.getMessage());
340             MessageUtil.showError(this, ex.getMessage()); //AZ
341         } catch (Exception ex) {
342            	final String errorMessage = ResourceUtils.getString("messages.ErrorRenamingFile"); 
343             MessageUtil.showError(this, errorMessage); //AZ
344             LOG.error(errorMessage, ex);
345         } finally {
346             setBusyCursor(false);
347         }
348     }
349 
350     /* (non-Javadoc)
351      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#rollback()
352      */
353     public void rollback() {
354         super.rollback();
355         try {
356             setBusyCursor(true);
357             // try to reload from database
358             Disc disc = getDisc();
359             HibernateDao.refresh(disc);
360             updateView();
361             super.rollback();
362         } catch (Exception ex) {
363            	final String errorMessage = ResourceUtils.getString("messages.ErrorRefreshingDisc"); 
364             MessageUtil.showError(this, errorMessage); //AZ
365             LOG.error(errorMessage, ex);
366         } finally {
367             setBusyCursor(false);
368         }
369     }
370 
371     /**
372      * Performs the Amazon.com web service search.
373      */
374     public void webSearch() {
375         super.webSearch();
376         final Disc disc = getDisc();
377         if (disc.isNotValid()) {
378            	final String errorMessage = ResourceUtils.getString("messages.discnotexists"); 
379             MessageUtil.showError(this, errorMessage); //AZ
380             LOG.error(errorMessage);
381             return;
382         }
383         // check for validation errors, if any then do no changes
384         if (hasErrors()) {
385             LOG.error(Resources.MESSAGE_EDITOR_ERRORS);
386             MessageUtil.showError(this, Resources.MESSAGE_EDITOR_ERRORS); //AZ
387             return;
388         }
389 
390         final WebSearchDialog dialog = new WebSearchDialog(getMainFrame(), getSettings());
391         dialog.setSelectedArtist(disc.getArtist().getName());
392         dialog.setSelectedDisc(disc.getName());
393         dialog.open();
394 
395         // if the user did not select anything
396         if (dialog.hasBeenCanceled()) {
397             return;
398         }
399 
400         // flag for if this year or name was modified
401         boolean modified = false;
402 
403         // if disc is not blank and does not contain a subtitle like - Disc 1
404         final String[] checkList = { "- Disc", "-Disc", "- disc" };
405         if (((StringUtils.isNotBlank(dialog.getSelectedDisc()))
406              && (StringUtils.indexOfAny(nameField.getText(), checkList) <= 0))) {
407             disc.setName(dialog.getSelectedDisc());
408             nameField.setText(dialog.getSelectedDisc());
409             modified = true;
410         }
411         if ((StringUtils.isNotBlank(dialog.getSelectedYear()))
412             && (Integer.valueOf(dialog.getSelectedYear()).intValue() < Integer.valueOf(year.getText()).intValue())) {
413             disc.setYear(dialog.getSelectedYear());
414             year.setText(dialog.getSelectedYear());
415             modified = true;
416         }
417         if (dialog.getSelectedImage() != null) {
418             try {
419                 if (StringUtils.isNotBlank(disc.getCoverUrl())) {
420                     ImageFactory.saveImage(dialog.getSelectedImage(), disc.getCoverUrl());
421                 } else {
422                     String filename = ImageFactory.saveImageWithFileFormat(dialog.getSelectedImage(),
423                                                                            this.getSettings().getFileFormatImage(),
424                                                                            disc.getLocation(),
425                                                                            disc.getArtist().getName(), 
426                                                                            disc.getName(),
427                                                                            disc.getYear());
428                     disc.setCoverUrl(filename);
429                 }
430                 albumImage.setImage(ImageFactory.getDiscImage(disc.getCoverUrl()).getImage());
431             } catch (IOException ex) {
432                	final String errorMessage = ResourceUtils.getString("messages.ErrorSavingCoverImage"); 
433                 MessageUtil.showError(this, errorMessage); //AZ
434                 LOG.error(errorMessage, ex);
435             }
436         }
437 
438         // commit the changes and update the cover URL
439         commit(modified);
440     }
441 
442     /**
443      * Gets the title for the title bar.
444      * <p>
445      * @return the title to put on the title bar
446      */
447     protected String getTitleSuffix() {
448         return getDisc().getDisplayText(getSettings().getDisplayFormatDisc());
449     }
450 
451     /**
452      * Writes view contents to the underlying model.
453      */
454     protected void updateModel() {
455         Disc disc = getDisc();
456         Artist foundArtist = null;
457         // compare any fields that may have changed.
458         if (!StringUtils.equals(disc.getName(), nameField.getText())) {
459             disc.setName(nameField.getText());
460         }
461 
462         if (!StringUtils.equalsIgnoreCase(disc.getYear(), year.getText())) {
463             disc.setYear(year.getText());
464         }
465         if (!StringUtils.equalsIgnoreCase(disc.getGenre(), (String)genre.getSelectedItem())) {
466             disc.setGenre((String)genre.getSelectedItem());
467         }
468 
469         if (!StringUtils.equalsIgnoreCase(disc.getNotes(), notesField.getText())) {
470             disc.setNotes(notesField.getText());
471         }     
472         /** AZ set Artist **/
473         //AZ - Check for empty Artist field. In addition to standard Validation method.
474         if ((artistField.getText()== null) ||  (ValidationUtils.isBlank(artistField.getText()))) {
475            	final String errorMessage = ResourceUtils.getString("messages.UpdateModel")+ Resources.MESSAGE_EDITOR_ERRORS
476            								+ "\n " + ResourceUtils.getString("messages.ArtistNameMandatory");
477             MessageUtil.showError(this, errorMessage); //AZ
478             LOG.error(errorMessage);
479         } else if (!StringUtils.equalsIgnoreCase(disc.getArtist().getName() , artistField.getText())) {
480          	// try to persist
481             try {
482                 setBusyCursor(true);
483                 // see if this artist exists
484                 final String resource = ResourceUtils.getString("hql.artist.findCaseSensitive");
485                 final String hql = MessageFormat.format(resource,
486                                                         new Object[] { StringEscapeUtils.escapeSql(artistField.getText()) });
487                 foundArtist = (Artist)HibernateDao.findUniqueByQuery(hql);
488                 // If Artists does not exist in database
489                 if (foundArtist == null) {
490                    	final String errorMessage = ResourceUtils.getString("messages.ErrorUpdatingArtistNotFound") + artistField.getText();
491                 	LOG.error(errorMessage);
492                     MessageUtil.showError(this, errorMessage); //AZ
493                 }
494                 if (foundArtist != null) {
495                 // if artist was found 
496                 disc.setArtist(foundArtist);
497                 }
498             } catch (InfrastructureException ex) {
499                	final String errorMessage = ResourceUtils.getString("messages.ArtistNameUnique");
500             	LOG.error(errorMessage);
501                 MessageUtil.showError(this, errorMessage); //AZ
502             } catch (Exception ex) {
503                	final String errorMessage = ResourceUtils.getString("messages.ErrorUpdatingArtist");
504             	LOG.error(errorMessage, ex);
505                 MessageUtil.showError(this, errorMessage); //AZ
506             } finally {
507                 setBusyCursor(false);
508             }	
509         	
510         } 
511     }
512 
513     /**
514      * Reads view contents from the underlying model.
515      */
516     protected void updateView() {
517         Disc disc = getDisc();
518         final String currentCoverUrl;
519         // reset any fields
520         genre.setSelectedItem(null);
521         // load new values
522         nameField.setText(disc.getName());
523         year.setText(disc.getYear());
524         genre.setSelectedItem(disc.getGenre());
525         genreLabel.setText(disc.getGenre());
526         location.setText(disc.getLocation());
527         location.setToolTipText(disc.getLocation());
528         notesField.setText(disc.getNotes());
529         artistField.setText(disc.getArtist().getName());  // AZ 
530         createdDateLabel.setText(DATE_FORMAT.format(disc.getCreatedDate()));
531         createdByLabel.setText(disc.getCreatedUser());
532         modifiedDateLabel.setText(DATE_FORMAT.format(disc.getModifiedDate()));
533         modifiedByLabel.setText(disc.getModifiedUser());
534         listModel.removeAllElements();
535         tracks = new ArrayList();
536         for (Iterator iter = disc.getTracks().iterator(); iter.hasNext();) {
537             final Track track = (Track)iter.next();
538             final String name = track.getDisplayText(getSettings().getDisplayFormatTrack());
539             final JukesValidationMessage message = new JukesValidationMessage(name, null, track);
540             tracks.add(message);
541         }
542         Collections.sort(tracks, TrackListCellRenderer.TRACK_COMPARATOR);
543         for (Iterator iter = tracks.iterator(); iter.hasNext();) {
544             JukesValidationMessage message = (JukesValidationMessage)iter.next();
545             listModel.addElement(message);
546         }
547 
548         // load the album cover
549         int dimension = this.getSettings().getCoverSizeLarge();
550         albumImage.setDisc(disc);
551         if (this.getSettings().isCopyImagesToDirectory()) {
552         	currentCoverUrl = ImageFactory.standardImageFileName(disc.getArtist().getName(), disc.getName(), disc.getYear());
553         }
554         else {
555         	currentCoverUrl = disc.getCoverUrl();
556         }
557         albumImage.setImage(ImageFactory.getScaledImage(currentCoverUrl, dimension, dimension).getImage());
558     }
559 
560     /**
561      * Gets the domain object associated with this editor.
562      * <p>
563      * @return an Disc instance associated with this editor
564      */
565     private Disc getDisc() {
566         return (Disc)getModel();
567     }
568 
569     /**
570      * Builds the Disc editor panel.
571      * <p>
572      * @return the panel to edit disc info.
573      */
574     private JComponent buildDiscPanel() {
575     	
576         FormLayout layout = new FormLayout("right:max(14dlu;pref), 4dlu, left:min(80dlu;pref):grow, pref, 4dlu, pref, 25px",
577                                            "p, 4px, p, 4px, p, 4px, p, 4px, p, 4px, p, 4px");
578 
579         layout.setRowGroups(new int[][] {
580                                 { 1, 3, 5 }
581                             });
582         PanelBuilder builder = new PanelBuilder(layout);
583         CellConstraints cc = new CellConstraints();
584 /** AZ - add artistField**/
585         builder.addLabel(Resources.getString("label.artist") + ": ", cc.xy(1, 1));
586         builder.add(artistField, cc.xyw(3, 1, 3));
587          
588         builder.addLabel(Resources.getString("label.name") + ": ", cc.xy(1, 3));
589         builder.add(nameField, cc.xyw(3, 3, 3));
590         builder.add(ComponentFactory.createTitleCaseButton(nameField), cc.xy(6, 3));
591         builder.addLabel(Resources.getString("label.genre") + ": ", cc.xy(1, 5));
592         builder.add(genreLabel, cc.xyw(2, 5, 2)); 
593         builder.add(genre, cc.xyw(3, 5, 3)); 
594         builder.addLabel(Resources.getString("label.year") + ": ", cc.xy(1, 7));
595         builder.add(year, cc.xy(3, 7));
596         builder.addLabel(Resources.getString("label.file") + ": ", cc.xy(1, 9));
597         builder.add(location, cc.xyw(3, 9, 5));
598         builder.addLabel(Resources.getString("label.notes") + ": ", cc.xy(1, 11, "left,top"));
599         builder.add(notesField, cc.xyw(3, 11, 3));
600       
601         return new IconFeedbackPanel(getValidationModel().getValidationResultModel(), builder.getPanel());
602     }
603 
604     /**
605      * Builds the Music information panel.
606      * <p>
607      * @return the panel to display the music info
608      */
609     private JComponent buildMusicPanel() {
610         FormLayout layout = new FormLayout("left:pref, 4dlu, right:pref", "p");
611 
612         layout.setRowGroups(new int[][] {
613                                 { 1 }
614                             });
615         PanelBuilder builder = new PanelBuilder(layout);
616         CellConstraints cc = new CellConstraints();
617         builder.add(listScrollPane, cc.xy(1, 1, "left,top"));
618         builder.add(albumImage, cc.xy(3, 1, "left,top"));
619         return builder.getPanel();
620     }
621 
622     private JToolBar buildToolBar() {
623     	final ToolBarBuilder bar = new ToolBarBuilder("Disc Toolbar");
624         ToolBarButton button = null;
625         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.UNLOCK_ID);
626         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
627         bar.add(button);
628         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.COMMIT_ID);
629         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
630         bar.add(button);
631         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.ROLLBACK_ID);
632         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
633         bar.add(button);
634         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.DELETE_ID);
635         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
636         bar.add(button);
637         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.FILE_RENAME_ID);
638         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
639         bar.add(button);
640         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.DISC_COVER_ID);
641         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
642         bar.add(button);
643         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.DISC_WEB_ID);
644         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
645         bar.add(button);
646         return bar.getToolBar();
647     }
648 
649     /**
650      * Commits to the database and if updateTags flag is set to true it updates
651      * the ID3 tags as well.
652      * <p>
653      * @param aUpdateTags true to update tags, false to not update tags
654      */
655     private void commit(boolean aUpdateTags) {
656         super.commit();
657         final Disc disc = getDisc();
658         final String temporaryDiscName = disc.getName(); //AZ Store disc to rollback
659         final String temporaryDiscYear = disc.getYear();
660         final String temporaryDiscGenre = disc.getGenre();
661         final String temporaryDiscNotes = disc.getNotes();
662         final String oldImageName = ImageFactory.standardImageFileName(disc.getArtist().getName(), disc.getName(), disc.getYear());
663         Artist foundArtist = null;
664         Artist artist = null;
665         //AZ - Check for empty Artist field. In addition to standard Validation method.
666         if ((artistField.getText()== null) ||  (ValidationUtils.isBlank(artistField.getText()))) {
667            	final String errorMessage = Resources.MESSAGE_EDITOR_ERRORS + "\n" + ResourceUtils.getString("messages.ArtistNameMandatory"); 
668             MessageUtil.showError(this, errorMessage); //AZ
669             LOG.error(errorMessage);
670             return;
671         }
672         //AZ - check Artist field length
673         if (artistField.getText().length() > 100) {
674            	final String errorMessage = Resources.MESSAGE_EDITOR_ERRORS + "\n" + ResourceUtils.getString("messages.ArtistNameLength") + " <= 100"; 
675             MessageUtil.showError(this, errorMessage); //AZ
676             LOG.error(errorMessage);
677             return;
678         }
679         boolean artistChanged = (!StringUtils.equalsIgnoreCase(disc.getArtist().getName() , artistField.getText()));
680 
681         // check for validation errors, if any then do no changes
682         boolean hasErrors = hasErrors();
683         if (hasErrors) {
684             LOG.error(Resources.MESSAGE_EDITOR_ERRORS);
685             MessageUtil.showError(this, Resources.MESSAGE_EDITOR_ERRORS); //AZ
686             return;
687         }
688         // AZ : trying to find Artist
689      	// try to persist
690         if (artistChanged) {
691         try {
692             setBusyCursor(true);
693             // see if this artist exists
694             final String resource = ResourceUtils.getString("hql.artist.findCaseSensitive");
695             final String hql = MessageFormat.format(resource,
696                                                     new Object[] { StringEscapeUtils.escapeSql(artistField.getText()) });
697             foundArtist = (Artist)HibernateDao.findUniqueByQuery(hql);
698             if (foundArtist == null) {
699            	// if artist was not found then add new Artist 
700             	artist = new Artist();
701                 artist.setName(artistField.getText());
702                 artist.setNotes("");
703                 HibernateUtil.beginTransaction();
704                 HibernateDao.saveOrUpdate(artist);
705                 HibernateUtil.commitTransaction();
706               }
707             if (foundArtist != null) {
708             // if artist was found 
709             	LOG.debug("DiscEditor - Change Artist, Artist was found in Database: " + artistField.getText());
710             }            
711         } catch (InfrastructureException ex) {
712            	final String errorMessage = ResourceUtils.getString("messages.ArtistNameUnique"); 
713             MessageUtil.showError(this, errorMessage); //AZ
714             LOG.error(errorMessage);          
715         } catch (Exception ex) {
716            	final String errorMessage = ResourceUtils.getString("messages.ErrorUpdatingArtist"); 
717             MessageUtil.showError(this, errorMessage); //AZ
718             LOG.error(errorMessage, ex);
719         } finally {
720             setBusyCursor(false);
721         }
722         } // if artistChanged
723         
724         updateModel();
725         
726         // try to persist
727         try {
728             setBusyCursor(true);
729             HibernateUtil.beginTransaction();
730             HibernateDao.saveOrUpdate(disc);
731             HibernateUtil.commitTransaction();
732             // AZ : If transaction is committed and copies of images are used
733             //      then change the name of the image copy
734             if (this.getSettings().isCopyImagesToDirectory()) {          
735              File oldImageFile = new File (oldImageName); 
736               if ( oldImageFile.exists() ) {
737                   final String newImageName = ImageFactory.standardImageFileName(artistField.getText(), disc.getName(), disc.getYear());
738                   final File newImageFile = new File (newImageName);
739                   oldImageFile.renameTo(newImageFile);                 
740               }
741             }
742             // now update this editor and the treeview
743             updateView();
744             this.getMainModule().refreshSelection(disc, Resources.NODE_CHANGED);
745             if (artistChanged) {
746             this.getMainModule().refreshTree();	
747             }
748          } catch (InfrastructureException ex) {
749            	final String errorMessage = ResourceUtils.getString("messages.DiscUnique"); 
750             MessageUtil.showError(this, errorMessage); //AZ
751             LOG.error(errorMessage);
752             HibernateUtil.rollbackTransaction();
753             //AZ Rollback changes
754             disc.setName(temporaryDiscName);//AZ rollback disc
755             disc.setYear(temporaryDiscYear);
756             disc.setGenre(temporaryDiscGenre);
757             disc.setNotes(temporaryDiscNotes);
758             nameField.setText(temporaryDiscName);
759             year.setText(temporaryDiscYear);
760             genre.setSelectedItem(temporaryDiscGenre);
761             notesField.setText(temporaryDiscNotes);
762             HibernateDao.refresh(disc);
763             updateModel();
764 
765             //Refresh View
766             final Object selectedNode = this.getMainModule().getSelection();
767             this.getMainModule().refreshTree(selectedNode);
768             rollback();
769             hasErrors = true;
770             getValidationModel().setDirty(false);//Set Dirty false to lock editor withour changes
771         } catch (Exception ex) {
772            	final String errorMessage = ResourceUtils.getString("messages.ErrorUpdatingDisc"); 
773             MessageUtil.showError(this, errorMessage); //AZ
774             LOG.error(errorMessage, ex);
775             HibernateUtil.rollbackTransaction();
776             HibernateDao.refresh(disc);
777             hasErrors = true;
778         } finally {
779             setBusyCursor(false);
780         }
781 
782         if (!hasErrors) {
783             if (aUpdateTags) {
784                 // now update the ID3 tags
785                 task = new UpdateTagsTask(disc);
786                 progressMonitor = new ProgressMonitor(getMainFrame(), Resources.getString("messages.updatetracks"), "",
787                                                       0, (int)task.getLengthOfTask());
788                 progressMonitor.setProgress(0);
789                 progressMonitor.setMillisToDecideToPopup(10);
790                 task.go();
791                 timer = new Timer(50, null);
792                 timer.addActionListener(new TimerListener(progressMonitor, task, timer));
793                 timer.start();
794             } else {
795                 MessageUtil.showSuccess(this);
796             }
797             super.commit();
798         }
799     }
800 
801     /**
802      * Initializes validation annotations.
803      */
804     private void initComponentAnnotations() {
805         ValidationComponentUtils.setInputHint(nameField, "Name is Mandatory, Length <= 100");
806         ValidationComponentUtils.setMandatory(nameField, true);
807         ValidationComponentUtils.setMessageKey(nameField, "Disc.Name");
808         ValidationComponentUtils.setInputHint(year, "Year is Mandatory, must be exactly 4 digits between 0000-9999");
809         ValidationComponentUtils.setMandatory(year, true);
810         ValidationComponentUtils.setMessageKey(year, "Disc.Year");
811         ValidationComponentUtils.setInputHint(genre, "Genre is Mandatory, Length <= 100, Enter <Genre>, <Genre: Style> or <Genre, Style>");
812         ValidationComponentUtils.setMandatory(genre, true);
813         ValidationComponentUtils.setMessageKey(genre, "Disc.Genre");
814         ValidationComponentUtils.setInputHint(notesField, "Notes Length <= 500");
815         ValidationComponentUtils.setMessageKey(notesField, "Disc.Notes");
816         /** AZ **/
817         ValidationComponentUtils.setInputHint(artistField, "Artist Name is Mandatory, Length <= 100");
818         ValidationComponentUtils.setMessageKey(artistField, "Artist.Name");
819         ValidationComponentUtils.setMandatory(artistField, true); 
820         
821         ValidationComponentUtils.setInputHint(genreEditor, "Genre is Mandatory, Length <= 100, Enter <Genre>, <Genre: Style> or <Genre, Style>");
822         }
823 
824     /**
825      *  Creates and configures the UI components;
826      */
827     private void initComponents() {
828         validationModel = new DiscValidationModel(new Disc());
829         ArtistValidationModel artistValidationModel = new ArtistValidationModel (new Artist());
830 
831         headerToolbar = buildToolBar();
832         nameField = ComponentFactory.createTextField(validationModel.getModel(Disc.PROPERTYNAME_NAME), false);
833         /** AZ **/
834         artistField = ComponentFactory.createTextField(artistValidationModel.getModel(Artist.PROPERTYNAME_NAME), false);
835         
836         year = ComponentFactory.createTextField(validationModel.getModel(Disc.PROPERTYNAME_YEAR), false);
837         ((JTextField)year).setColumns(5);
838         final ValueModel genreChoiceModel = validationModel.getModel(Disc.PROPERTYNAME_GENRE);
839         genreLabel = new JLabel();
840         genreLabel.setName("GENRE");
841         //genre = ComponentFactory.createComboBox(new SelectionInList(GENRE_LIST, genreChoiceModel));
842         //AZ - Constructs an editable JComboBox for the specified List of Genres
843         ComboBoxAdapter genreAdapter = new ComboBoxAdapter(GENRE_LIST.toArray(), genreChoiceModel); //AZ
844         genre = new JComboBox(genreAdapter); //AZ
845         genre.setEditable(true); //AZ  
846         //AZ set sub-component of ComboBox - ComboBoxEditorTextField - as JComponent
847         genreEditor = (JComponent)genre.getComponent(2);//AZ
848        
849         notesField = ComponentFactory.createTextArea(getValidationModel().getModel(Disc.PROPERTYNAME_NOTES), false);
850         notesField.setLineWrap(true);
851         notesField.setWrapStyleWord(true);
852         notesField.setRows(3);
853         location = ComponentFactory.createLabel(getValidationModel().getModel(Disc.PROPERTYNAME_LOCATION));
854         albumImage = new AlbumImage(new Dimension(this.getSettings().getCoverSizeLarge(), this.getSettings().getCoverSizeLarge()));
855         listModel = new DefaultListModel();
856         // Create the list and put it in a scroll pane.
857         trackList = new JList(listModel);
858         trackList.setFocusable(true);
859         trackList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
860         trackList.setSelectedIndex(0);
861         trackList.setVisibleRowCount(20);
862         trackList.setCellRenderer(new TrackListCellRenderer(this.getSettings()));
863         final JPopupMenu popup = MainMenuBuilder.buildPlayerPopupMenu(trackList);
864         MouseListener popupListener = new ListPopupListener(popup);
865         trackList.addMouseListener(popupListener);
866         trackList.add(popup);
867         listScrollPane = new SimpleInternalFrame(Resources.getString("label.tracks"));
868         listScrollPane.setFrameIcon(Resources.TRACK_TREE_ICON);
869         listScrollPane.setContent(UIFactory.createStrippedScrollPane(trackList));
870         listScrollPane.setSelected(true);
871     }
872 
873     // shows popups on right click of List nodes
874     private static class ListPopupListener
875         extends MouseAdapter {
876 
877         final JPopupMenu popup;
878 
879         public ListPopupListener(JPopupMenu popup) {
880             this.popup = popup;
881         }
882 
883         public void mousePressed(MouseEvent evt) {
884             maybeShowPopup(evt);
885 
886             // left mouse button pressed twice, queue in playlist
887             // middle mouse button pressed, queue in playlist
888             if ((((evt.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) && (evt.getClickCount() == 2)) ||
889                 ((evt.getModifiers() & MouseEvent.BUTTON2_MASK) != 0))
890             {
891                 LOG.debug("[List left double clicked or middle clicked].");
892                 JComponent source = (JComponent)evt.getSource();
893                 final ActionEvent event = new ActionEvent(source, 1, Actions.PLAYER_QUEUE_ID);
894                 source.putClientProperty(Resources.EDITOR_COMPONENT, source);
895                 ActionManager.get(Actions.PLAYER_QUEUE_ID).actionPerformed(event);
896             }
897         }
898 
899         public void mouseReleased(MouseEvent e) {
900             maybeShowPopup(e);
901         }
902 
903         private void maybeShowPopup(MouseEvent e) {
904             if (e.isPopupTrigger()) {
905                 ActionManager.get(Actions.TRACK_PLAY_IMMEDIATE_ID).setEnabled(true);
906 
907                 // popup the menu
908                 popup.show(e.getComponent(), e.getX(), e.getY());
909             }
910         }
911 
912     }
913 
914 }