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