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.util.ArrayList;
11  import java.util.Collections;
12  import java.util.Iterator;
13  import java.util.List;
14  
15  import javax.swing.DefaultListModel;
16  import javax.swing.JComboBox;
17  import javax.swing.JComponent;
18  import javax.swing.JFileChooser;
19  import javax.swing.JLabel;
20  import javax.swing.JList;
21  import javax.swing.JPopupMenu;
22  import javax.swing.JTextArea;
23  import javax.swing.JTextField;
24  import javax.swing.JToolBar;
25  import javax.swing.ListSelectionModel;
26  import javax.swing.ProgressMonitor;
27  import javax.swing.Timer;
28  import javax.swing.text.JTextComponent;
29  
30  import org.apache.commons.lang.StringUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  import com.jgoodies.binding.list.SelectionInList;
35  import com.jgoodies.binding.value.ValueModel;
36  import com.jgoodies.forms.builder.PanelBuilder;
37  import com.jgoodies.forms.layout.CellConstraints;
38  import com.jgoodies.forms.layout.FormLayout;
39  import com.jgoodies.uif.action.ActionManager;
40  import com.jgoodies.uif.application.Application;
41  import com.jgoodies.uif.builder.ToolBarBuilder;
42  import com.jgoodies.uif.component.ToolBarButton;
43  import com.jgoodies.uif.panel.SimpleInternalFrame;
44  import com.jgoodies.uifextras.util.UIFactory;
45  import com.jgoodies.validation.view.ValidationComponentUtils;
46  import com.melloware.jukes.db.HibernateDao;
47  import com.melloware.jukes.db.HibernateUtil;
48  import com.melloware.jukes.db.orm.Artist;
49  import com.melloware.jukes.db.orm.Disc;
50  import com.melloware.jukes.db.orm.Track;
51  import com.melloware.jukes.exception.InfrastructureException;
52  import com.melloware.jukes.file.filter.FilterFactory;
53  import com.melloware.jukes.file.image.ChooserImagePreview;
54  import com.melloware.jukes.file.image.ImageFactory;
55  import com.melloware.jukes.file.image.ImageFileView;
56  import com.melloware.jukes.file.tag.MusicTag;
57  import com.melloware.jukes.file.tag.TagFactory;
58  import com.melloware.jukes.gui.tool.Actions;
59  import com.melloware.jukes.gui.tool.Resources;
60  import com.melloware.jukes.gui.view.MainMenuBuilder;
61  import com.melloware.jukes.gui.view.component.AlbumImage;
62  import com.melloware.jukes.gui.view.component.ComponentFactory;
63  import com.melloware.jukes.gui.view.component.TrackListCellRenderer;
64  import com.melloware.jukes.gui.view.dialogs.WebSearchDialog;
65  import com.melloware.jukes.gui.view.tasks.TimerListener;
66  import com.melloware.jukes.gui.view.tasks.UpdateTagsTask;
67  import com.melloware.jukes.gui.view.validation.DiscValidationModel;
68  import com.melloware.jukes.gui.view.validation.IconFeedbackPanel;
69  import com.melloware.jukes.util.JukesValidationMessage;
70  import com.melloware.jukes.util.MessageUtil;
71  
72  /**
73   * An implementation of {@link Editor} that displays a {@link Disc}.<p>
74   *
75   * This container uses a <code>FormLayout</code> and the panel building
76   * is done with the <code>PanelBuilder</code> class.
77   * Columns and rows are specified before the panel is filled with components.
78   * <p>
79   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
80   * @author Emil A. Lefkof III <info@melloware.com>
81   * @version 4.0
82   */
83  @SuppressWarnings("unchecked")
84  public final class DiscEditor
85      extends AbstractEditor {
86  
87      private static final Log LOG = LogFactory.getLog(DiscEditor.class);
88      private static final List GENRE_LIST = MusicTag.getGenreTypes();
89      private AlbumImage albumImage;
90      private DefaultListModel listModel;
91      private JComboBox genre;
92      private JComponent discPanel;
93      private JLabel genreLabel;
94      private JLabel location;
95      private JList trackList;
96      private JTextArea notesField;
97      private JTextComponent nameField;
98      private JTextComponent year;
99      private JToolBar headerToolbar;
100     private List tracks;
101     private SimpleInternalFrame listScrollPane;
102 
103     /**
104      * Constructs a <code>DiscEditor</code>.
105      */
106     public DiscEditor() {
107         super(Resources.DISC_TREE_ICON);
108     }
109 
110     /**
111      * Gets the domain class associated with this editor.
112      */
113     public Class getDomainClass() {
114         return Disc.class;
115     }
116 
117     /* (non-Javadoc)
118      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#getHeaderToolBar()
119      */
120     public JToolBar getHeaderToolBar() {
121         return headerToolbar;
122     }
123 
124     /**
125      * Builds the content pane.
126      */
127     public void build() {
128         initComponents();
129         initComponentAnnotations();
130         initEventHandling();
131 
132         discPanel = buildDiscPanel();
133 
134         FormLayout layout = new FormLayout("fill:pref:grow", "p, 12px, p, p, 12px, p, 12px, p, 12px, p");
135 
136         setLayout(layout);
137         PanelBuilder builder = new PanelBuilder(layout, this);
138         builder.setDefaultDialogBorder();
139         CellConstraints cc = new CellConstraints();
140 
141         builder.add(buildMusicPanel(), cc.xy(1, 1));
142         builder.add(buildHintAreaPane(), cc.xy(1, 3));
143         builder.addSeparator(Resources.getString("label.disc"), cc.xy(1, 4));
144         builder.add(discPanel, cc.xy(1, 6));
145         JComponent audit = buildAuditInfoPanel();
146         if (this.getSettings().isAuditInfo()) {
147         	builder.addSeparator(Resources.getString("label.auditinfo"), cc.xy(1, 8));
148             builder.add(audit, cc.xy(1, 10));
149 		}
150     }
151 
152     /**
153      * Commits with the update tags flag on meaning it will write the ID3 tags
154      * to all tracks.
155      */
156     public void commit() {
157         commit(true);
158     }
159 
160     /* (non-Javadoc)
161      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#delete()
162      */
163     public void delete() {
164         super.delete();
165         try {
166             if (!MessageUtil.confirmDelete(this)) {
167                 return;
168             }
169             // try to delete from database
170             setBusyCursor(true);
171             HibernateUtil.beginTransaction();
172             Disc disc = getDisc();
173             final Artist artist = disc.getArtist();
174             HibernateDao.refresh(artist);
175             artist.getDiscs().remove(disc);
176             HibernateDao.refresh(disc);
177             HibernateDao.delete(disc);
178             HibernateUtil.commitTransaction();
179 
180             // reset dirty flag since we are deleting
181             getValidationModel().setDirty(false);
182 
183             if (artist.getDiscs().size() == 0) {
184                 // refresh the tree
185                 ActionManager.get(Actions.REFRESH_ID).actionPerformed(null);
186             } else {
187                 // tell the tree to select the parent node
188                 this.getMainModule().refreshSelection(disc, Resources.NODE_DELETED);
189             }
190         } catch (Exception ex) {
191             LOG.error("Error deleting disc.", ex);
192             HibernateUtil.rollbackTransaction();
193         } finally {
194             setBusyCursor(false);
195         }
196     }
197 
198     /**
199      * Let the user select another album cover.
200      */
201     public void findCover() {
202         Disc disc = getDisc();
203         if (disc.isNotValid()) {
204             LOG.error(Resources.getString("messages.discnotexists"));
205             return;
206         }
207 
208         updateModel();
209 
210         // check for validation errors, if any then do no changes
211         boolean hasErrors = hasErrors();
212         if (hasErrors) {
213             LOG.error(Resources.MESSAGE_EDITOR_ERRORS);
214             return;
215         }
216 
217         final File currentDir = new File(getDisc().getLocation());
218         JFileChooser chooser = new JFileChooser();
219         chooser.setApproveButtonText("Select");
220         chooser.setDialogTitle("Find Cover Image");
221         chooser.setCurrentDirectory(currentDir);
222         chooser.addChoosableFileFilter(FilterFactory.imageFileFilter());
223         chooser.setAcceptAllFileFilterUsed(false);
224         chooser.setFileView(new ImageFileView());
225         chooser.setAccessory(new ChooserImagePreview(chooser));
226         chooser.setMultiSelectionEnabled(false);
227         int returnVal = chooser.showOpenDialog(Application.getDefaultParentFrame());
228         if (returnVal != JFileChooser.APPROVE_OPTION) {
229             return;
230         }
231 
232         File file = chooser.getSelectedFile();
233         // try to persist
234         try {
235             setBusyCursor(true);
236             HibernateUtil.beginTransaction();
237             disc.setCoverUrl(file.getAbsolutePath());
238             HibernateDao.persist(disc);
239             HibernateUtil.commitTransaction();
240 
241             // now update this editor
242             updateView();
243         } catch (InfrastructureException ex) {
244             HibernateUtil.rollbackTransaction();
245             LOG.error("Disc must be unique within an artist. \n\nMust have different Name than all other discs. ");
246             HibernateDao.refresh(disc);
247         } catch (Exception ex) {
248             HibernateUtil.rollbackTransaction();
249             LOG.error("Error updating cover image.", ex);
250             HibernateDao.refresh(disc);
251         } finally {
252             setBusyCursor(false);
253         }
254     }
255 
256     /**
257      * Rename the file to a good format.
258      */
259     public void renameFiles() {
260         if (getDisc().isNotValid()) {
261             LOG.error(Resources.getString("messages.discnotexists"));
262             return;
263         }
264 
265         updateModel();
266 
267         // check for validation errors, if any then do no changes
268         boolean hasErrors = hasErrors();
269         if (hasErrors) {
270             LOG.error(Resources.MESSAGE_EDITOR_ERRORS);
271             return;
272         }
273 
274         MusicTag musicTag = null;
275         try {
276             setBusyCursor(true);
277             for (Iterator iter = getDisc().getTracks().iterator(); iter.hasNext();) {
278                 Track track = (Track)iter.next();
279                 final File file = new File(track.getTrackUrl());
280 
281                 if (file.exists()) {
282                     musicTag = TagFactory.getTag(file);
283                     if (musicTag.renameFile(this.getSettings().getFileFormatMusic())) {
284                         track.setTrackUrl(musicTag.getAbsolutePath());
285                     }
286                 }
287             }
288             commit(false);
289         } catch (InfrastructureException ex) {
290             LOG.error(ex.getMessage());
291         } catch (Exception ex) {
292             LOG.error("Error renaming file.", ex);
293         } finally {
294             setBusyCursor(false);
295         }
296     }
297 
298     /* (non-Javadoc)
299      * @see com.melloware.jukes.gui.view.editor.AbstractEditor#rollback()
300      */
301     public void rollback() {
302         super.rollback();
303         try {
304             setBusyCursor(true);
305             // try to reload from database
306             Disc disc = getDisc();
307             HibernateDao.refresh(disc);
308             updateView();
309             super.rollback();
310         } catch (Exception ex) {
311             LOG.error("Error refreshing disc.", ex);
312         } finally {
313             setBusyCursor(false);
314         }
315     }
316 
317     /**
318      * Performs the Amazon.com web service search.
319      */
320     public void webSearch() {
321         super.webSearch();
322         final Disc disc = getDisc();
323         if (disc.isNotValid()) {
324             LOG.error(Resources.getString("messages.discnotexists"));
325             return;
326         }
327         // check for validation errors, if any then do no changes
328         if (hasErrors()) {
329             LOG.error(Resources.MESSAGE_EDITOR_ERRORS);
330             return;
331         }
332 
333         final WebSearchDialog dialog = new WebSearchDialog(getMainFrame(), getSettings());
334         dialog.setSelectedArtist(disc.getArtist().getName());
335         dialog.setSelectedDisc(disc.getName());
336         dialog.open();
337 
338         // if the user did not select anything
339         if (dialog.hasBeenCanceled()) {
340             return;
341         }
342 
343         // flag for if this year or name was modified
344         boolean modified = false;
345 
346         // if disc is not blank and does not contain a subtitle like - Disc 1
347         final String[] checkList = { "- Disc", "-Disc", "- disc" };
348         if (((StringUtils.isNotBlank(dialog.getSelectedDisc()))
349              && (StringUtils.indexOfAny(nameField.getText(), checkList) <= 0))) {
350             disc.setName(dialog.getSelectedDisc());
351             nameField.setText(dialog.getSelectedDisc());
352             modified = true;
353         }
354         if ((StringUtils.isNotBlank(dialog.getSelectedYear()))
355             && (Integer.valueOf(dialog.getSelectedYear()).intValue() < Integer.valueOf(year.getText()).intValue())) {
356             disc.setYear(dialog.getSelectedYear());
357             year.setText(dialog.getSelectedYear());
358             modified = true;
359         }
360         if (dialog.getSelectedImage() != null) {
361             try {
362                 if (StringUtils.isNotBlank(disc.getCoverUrl())) {
363                     ImageFactory.saveImage(dialog.getSelectedImage(), disc.getCoverUrl());
364                 } else {
365                     String filename = ImageFactory.saveImageWithFileFormat(dialog.getSelectedImage(),
366                                                                            this.getSettings().getFileFormatImage(),
367                                                                            disc.getLocation(),
368                                                                            disc.getArtist().getName(), disc.getName());
369                     disc.setCoverUrl(filename);
370                 }
371                 albumImage.setImage(ImageFactory.getDiscImage(disc.getCoverUrl()).getImage());
372             } catch (IOException ex) {
373                 LOG.error("Error saving cover image.", ex);
374             }
375         }
376 
377         // commit the changes and update the cover URL
378         commit(modified);
379     }
380 
381     /**
382      * Gets the title for the title bar.
383      * <p>
384      * @return the title to put on the title bar
385      */
386     protected String getTitleSuffix() {
387         return getDisc().getDisplayText(getSettings().getDisplayFormatDisc());
388     }
389 
390     /**
391      * Writes view contents to the underlying model.
392      */
393     protected void updateModel() {
394         Disc disc = getDisc();
395 
396         // compare any fields that may have changed.
397         if (!StringUtils.equals(disc.getName(), nameField.getText())) {
398             disc.setName(nameField.getText());
399         }
400 
401         if (!StringUtils.equalsIgnoreCase(disc.getYear(), year.getText())) {
402             disc.setYear(year.getText());
403         }
404 
405         if (!StringUtils.equalsIgnoreCase(disc.getGenre(), (String)genre.getSelectedItem())) {
406             disc.setGenre((String)genre.getSelectedItem());
407         }
408 
409         if (!StringUtils.equalsIgnoreCase(disc.getNotes(), notesField.getText())) {
410             disc.setNotes(notesField.getText());
411         }
412     }
413 
414     /**
415      * Reads view contents from the underlying model.
416      */
417     protected void updateView() {
418         Disc disc = getDisc();
419         // reset any fields
420         genre.setSelectedItem(null);
421 
422         // load new values
423         nameField.setText(disc.getName());
424         year.setText(disc.getYear());
425         genre.setSelectedItem(disc.getGenre());
426         genreLabel.setText(disc.getGenre());
427         location.setText(disc.getLocation());
428         location.setToolTipText(disc.getLocation());
429         notesField.setText(disc.getNotes());
430         createdDateLabel.setText(DATE_FORMAT.format(disc.getCreatedDate()));
431         createdByLabel.setText(disc.getCreatedUser());
432         modifiedDateLabel.setText(DATE_FORMAT.format(disc.getModifiedDate()));
433         modifiedByLabel.setText(disc.getModifiedUser());
434         listModel.removeAllElements();
435         tracks = new ArrayList();
436         for (Iterator iter = disc.getTracks().iterator(); iter.hasNext();) {
437             final Track track = (Track)iter.next();
438             final String name = track.getDisplayText(getSettings().getDisplayFormatTrack());
439             final JukesValidationMessage message = new JukesValidationMessage(name, null, track);
440             tracks.add(message);
441         }
442         Collections.sort(tracks, TrackListCellRenderer.TRACK_COMPARATOR);
443         for (Iterator iter = tracks.iterator(); iter.hasNext();) {
444             JukesValidationMessage message = (JukesValidationMessage)iter.next();
445             listModel.addElement(message);
446         }
447 
448         // load the album cover
449         int dimension = this.getSettings().getCoverSizeLarge();
450         albumImage.setDisc(disc);
451         albumImage.setImage(ImageFactory.getScaledImage(disc.getCoverUrl(), dimension, dimension).getImage());
452     }
453 
454     /**
455      * Gets the domain object associated with this editor.
456      * <p>
457      * @return an Disc instance associated with this editor
458      */
459     private Disc getDisc() {
460         return (Disc)getModel();
461     }
462 
463     /**
464      * Builds the Disc editor panel.
465      * <p>
466      * @return the panel to edit disc info.
467      */
468     private JComponent buildDiscPanel() {
469         FormLayout layout = new FormLayout("right:max(14dlu;pref), 4dlu, left:min(80dlu;pref):grow, pref, 4dlu, pref, 25px",
470                                            "p, 4px, p, 4px, p, 4px, p, 4px, p, 4px");
471 
472         layout.setRowGroups(new int[][] {
473                                 { 1, 3 }
474                             });
475         PanelBuilder builder = new PanelBuilder(layout);
476         CellConstraints cc = new CellConstraints();
477 
478         builder.addLabel(Resources.getString("label.name") + ": ", cc.xy(1, 1));
479         builder.add(nameField, cc.xyw(3, 1, 3));
480         builder.add(ComponentFactory.createTitleCaseButton(nameField), cc.xy(6, 1));
481         builder.addLabel(Resources.getString("label.genre") + ": ", cc.xy(1, 3));
482         builder.add(genreLabel, cc.xyw(2, 3, 2));
483         builder.add(genre, cc.xyw(3, 3, 3));
484         builder.addLabel(Resources.getString("label.year") + ": ", cc.xy(1, 5));
485         builder.add(year, cc.xy(3, 5));
486         builder.addLabel(Resources.getString("label.file") + ": ", cc.xy(1, 7));
487         builder.add(location, cc.xyw(3, 7, 5));
488         builder.addLabel(Resources.getString("label.notes") + ": ", cc.xy(1, 9, "left,top"));
489         builder.add(notesField, cc.xyw(3, 9, 3));
490 
491         return new IconFeedbackPanel(getValidationModel().getValidationResultModel(), builder.getPanel());
492     }
493 
494     /**
495      * Builds the Music information panel.
496      * <p>
497      * @return the panel to display the music info
498      */
499     private JComponent buildMusicPanel() {
500         FormLayout layout = new FormLayout("left:pref, 4dlu, right:pref", "p");
501 
502         layout.setRowGroups(new int[][] {
503                                 { 1 }
504                             });
505         PanelBuilder builder = new PanelBuilder(layout);
506         CellConstraints cc = new CellConstraints();
507         builder.add(listScrollPane, cc.xy(1, 1, "left,top"));
508         builder.add(albumImage, cc.xy(3, 1, "left,top"));
509         return builder.getPanel();
510     }
511 
512     private JToolBar buildToolBar() {
513     	final ToolBarBuilder bar = new ToolBarBuilder("Disc Toolbar");
514         ToolBarButton button = null;
515         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.UNLOCK_ID);
516         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
517         bar.add(button);
518         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.COMMIT_ID);
519         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
520         bar.add(button);
521         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.ROLLBACK_ID);
522         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
523         bar.add(button);
524         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.DELETE_ID);
525         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
526         bar.add(button);
527         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.FILE_RENAME_ID);
528         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
529         bar.add(button);
530         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.DISC_COVER_ID);
531         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
532         bar.add(button);
533         button = (ToolBarButton)ComponentFactory.createToolBarButton(Actions.DISC_WEB_ID);
534         button.putClientProperty(Resources.EDITOR_COMPONENT, this);
535         bar.add(button);
536         return bar.getToolBar();
537     }
538 
539     /**
540      * Commits to the database and if updateTags flag is set to true it updates
541      * the ID3 tags as well.
542      * <p>
543      * @param aUpdateTags true to update tags, false to not update tags
544      */
545     private void commit(boolean aUpdateTags) {
546         super.commit();
547         final Disc disc = getDisc();
548         updateModel();
549 
550         // check for validation errors, if any then do no changes
551         boolean hasErrors = hasErrors();
552         if (hasErrors) {
553             LOG.error(Resources.MESSAGE_EDITOR_ERRORS);
554             return;
555         }
556 
557         // try to persist
558         try {
559             setBusyCursor(true);
560             HibernateUtil.beginTransaction();
561             HibernateDao.saveOrUpdate(disc);
562             HibernateUtil.commitTransaction();
563 
564             // now update this editor and the treeview
565             updateView();
566             this.getMainModule().refreshSelection(disc, Resources.NODE_CHANGED);
567         } catch (InfrastructureException ex) {
568             HibernateUtil.rollbackTransaction();
569             LOG.error("Disc must be unique within an artist. \n\nMust have different Name than all other discs. ");
570             HibernateDao.refresh(disc);
571             hasErrors = true;
572         } catch (Exception ex) {
573             HibernateUtil.rollbackTransaction();
574             LOG.error("Error updating disc.", ex);
575             HibernateDao.refresh(disc);
576             hasErrors = true;
577         } finally {
578             setBusyCursor(false);
579         }
580 
581         if (!hasErrors) {
582             if (aUpdateTags) {
583                 // now update the ID3 tags
584                 task = new UpdateTagsTask(disc);
585                 progressMonitor = new ProgressMonitor(getMainFrame(), Resources.getString("messages.updatetracks"), "",
586                                                       0, (int)task.getLengthOfTask());
587                 progressMonitor.setProgress(0);
588                 progressMonitor.setMillisToDecideToPopup(10);
589                 task.go();
590                 timer = new Timer(50, null);
591                 timer.addActionListener(new TimerListener(progressMonitor, task, timer));
592                 timer.start();
593             } else {
594                 MessageUtil.showSuccess(this);
595             }
596             super.commit();
597         }
598     }
599 
600     /**
601      * Initializes validation annotations.
602      */
603     private void initComponentAnnotations() {
604         ValidationComponentUtils.setInputHint(nameField, "Name is Mandatory, Length <= 100");
605         ValidationComponentUtils.setMandatory(nameField, true);
606         ValidationComponentUtils.setMessageKey(nameField, "Disc.Name");
607         ValidationComponentUtils.setInputHint(year, "Year is Mandatory, must be exactly 4 digits between 0000-9999");
608         ValidationComponentUtils.setMandatory(year, true);
609         ValidationComponentUtils.setMessageKey(year, "Disc.Year");
610         ValidationComponentUtils.setMessageKey(genre, "Disc.Genre");
611         ValidationComponentUtils.setInputHint(notesField, "Notes Length <= 500");
612         ValidationComponentUtils.setMessageKey(notesField, "Disc.Notes");
613     }
614 
615     /**
616      *  Creates and configures the UI components;
617      */
618     private void initComponents() {
619         validationModel = new DiscValidationModel(new Disc());
620 
621         headerToolbar = buildToolBar();
622         nameField = ComponentFactory.createTextField(validationModel.getModel(Disc.PROPERTYNAME_NAME), false);
623         year = ComponentFactory.createTextField(validationModel.getModel(Disc.PROPERTYNAME_YEAR), false);
624         ((JTextField)year).setColumns(5);
625         final ValueModel genreChoiceModel = validationModel.getModel(Disc.PROPERTYNAME_GENRE);
626         genreLabel = new JLabel();
627         genreLabel.setName("GENRE");
628         genre = ComponentFactory.createComboBox(new SelectionInList(GENRE_LIST, genreChoiceModel));
629         notesField = ComponentFactory.createTextArea(getValidationModel().getModel(Disc.PROPERTYNAME_NOTES), false);
630         notesField.setLineWrap(true);
631         notesField.setWrapStyleWord(true);
632         notesField.setRows(3);
633         location = ComponentFactory.createLabel(getValidationModel().getModel(Disc.PROPERTYNAME_LOCATION));
634         albumImage = new AlbumImage(new Dimension(this.getSettings().getCoverSizeLarge(), this.getSettings().getCoverSizeLarge()));
635         listModel = new DefaultListModel();
636         // Create the list and put it in a scroll pane.
637         trackList = new JList(listModel);
638         trackList.setFocusable(true);
639         trackList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
640         trackList.setSelectedIndex(0);
641         trackList.setVisibleRowCount(20);
642         trackList.setCellRenderer(new TrackListCellRenderer(this.getSettings()));
643         final JPopupMenu popup = MainMenuBuilder.buildPlayerPopupMenu(trackList);
644         MouseListener popupListener = new ListPopupListener(popup);
645         trackList.addMouseListener(popupListener);
646         trackList.add(popup);
647         listScrollPane = new SimpleInternalFrame(Resources.getString("label.tracks"));
648         listScrollPane.setFrameIcon(Resources.TRACK_TREE_ICON);
649         listScrollPane.setContent(UIFactory.createStrippedScrollPane(trackList));
650         listScrollPane.setSelected(true);
651     }
652 
653     // shows popups on right click of List nodes
654     private static class ListPopupListener
655         extends MouseAdapter {
656 
657         final JPopupMenu popup;
658 
659         public ListPopupListener(JPopupMenu popup) {
660             this.popup = popup;
661         }
662 
663         public void mousePressed(MouseEvent evt) {
664             maybeShowPopup(evt);
665 
666             // left mouse button pressed twice, queue in playlist
667             // middle mouse button pressed, queue in playlist
668             if ((((evt.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) && (evt.getClickCount() == 2)) ||
669                 ((evt.getModifiers() & MouseEvent.BUTTON2_MASK) != 0))
670             {
671                 LOG.debug("[List left double clickedor middle clicked].");
672                 JComponent source = (JComponent)evt.getSource();
673                 final ActionEvent event = new ActionEvent(source, 1, Actions.PLAYER_QUEUE_ID);
674                 source.putClientProperty(Resources.EDITOR_COMPONENT, source);
675                 ActionManager.get(Actions.PLAYER_QUEUE_ID).actionPerformed(event);
676             }
677         }
678 
679         public void mouseReleased(MouseEvent e) {
680             maybeShowPopup(e);
681         }
682 
683         private void maybeShowPopup(MouseEvent e) {
684             if (e.isPopupTrigger()) {
685                 ActionManager.get(Actions.TRACK_PLAY_IMMEDIATE_ID).setEnabled(true);
686 
687                 // popup the menu
688                 popup.show(e.getComponent(), e.getX(), e.getY());
689             }
690         }
691 
692     }
693 
694 }