View Javadoc

1   package com.melloware.jukes.gui.view;
2   
3   import java.awt.Dimension;
4   import java.awt.event.ActionEvent;
5   import java.awt.event.ActionListener;
6   import java.awt.event.MouseAdapter;
7   import java.awt.event.MouseEvent;
8   import java.awt.event.MouseListener;
9   import java.beans.PropertyChangeEvent;
10  import java.beans.PropertyChangeListener;
11  import java.io.File;
12  import java.io.IOException;
13  
14  import javax.swing.AbstractButton;
15  import javax.swing.Icon;
16  import javax.swing.JComponent;
17  import javax.swing.JFileChooser;
18  import javax.swing.JLabel;
19  import javax.swing.JList;
20  import javax.swing.JPopupMenu;
21  import javax.swing.JScrollPane;
22  import javax.swing.JToggleButton;
23  import javax.swing.JToolBar;
24  import javax.swing.ListSelectionModel;
25  import javax.swing.ProgressMonitor;
26  import javax.swing.Timer;
27  import javax.swing.UIManager;
28  import javax.swing.border.TitledBorder;
29  import javax.swing.filechooser.FileFilter;
30  
31  import org.apache.commons.lang.StringUtils;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.xml.sax.SAXParseException;
35  
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.panel.SimpleInternalFrame;
43  import com.jgoodies.uif.util.ResourceUtils;
44  import com.jgoodies.uifextras.util.UIFactory;
45  import com.melloware.jukes.db.orm.Track;
46  import com.melloware.jukes.file.Playlist;
47  import com.melloware.jukes.file.filter.FilterFactory;
48  import com.melloware.jukes.file.filter.M3uFilter;
49  import com.melloware.jukes.file.filter.XspfFilter;
50  import com.melloware.jukes.file.image.ImageFactory;
51  import com.melloware.jukes.gui.tool.Actions;
52  import com.melloware.jukes.gui.tool.MainModule;
53  import com.melloware.jukes.gui.tool.Resources;
54  import com.melloware.jukes.gui.view.component.AlbumImage;
55  import com.melloware.jukes.gui.view.component.ComponentFactory;
56  import com.melloware.jukes.gui.view.component.PlaylistCellRenderer;
57  import com.melloware.jukes.gui.view.component.PlaylistListModel;
58  import com.melloware.jukes.gui.view.tasks.LoadPlaylistTask;
59  import com.melloware.jukes.gui.view.tasks.TimerListener;
60  import com.melloware.jukes.util.GuiUtil;
61  import com.melloware.jukes.util.MessageUtil;
62  
63  /**
64   * Display the playlist and all the playlist optins.
65   * <p>
66   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
67   * @author Emil A. Lefkof III <info@melloware.com>
68   * @version 4.0
69   */
70  public final class PlaylistPanel extends SimpleInternalFrame implements PropertyChangeListener {
71  
72     private static final Log LOG = LogFactory.getLog(PlaylistPanel.class);
73     private AlbumImage albumImage;
74     private JLabel artist;
75     private JLabel bitrate;
76     private JLabel disc;
77     private JLabel duration;
78     private JLabel track;
79     private final JList trackList;
80     private JScrollPane listScrollPane;
81     private JToggleButton shuffleCatalog;
82     private JToggleButton shufflePlaylist;
83     private final Playlist playlist;
84     private final PlaylistListModel listModel;
85  
86     /**
87      * Constructs a <code>PlaylistPanel</code> for the given module.
88      * 
89      * @param aPlaylist provides the playlist class needed for display
90      */
91     PlaylistPanel(Playlist aPlaylist) {
92        super(Resources.PLAYLIST_ICON, ResourceUtils.getString("label.playlist"));
93        LOG.debug("Playlist planel created.");
94        this.playlist = aPlaylist;
95        this.playlist.addPropertyChangeListener(this);
96  
97        // build playlist and assign listeners
98        listModel = new PlaylistListModel(this.playlist, this);
99        trackList = new JList(listModel);
100       trackList.putClientProperty(Resources.EDITOR_COMPONENT, this);
101       trackList.addKeyListener(listModel.getKeyTypedListener());
102 
103       // add right click menu to the playlist
104       final JPopupMenu popup = MainMenuBuilder.buildPlaylistPopupMenu(this, trackList);
105       MouseListener popupListener = new ListPopupListener(popup);
106       trackList.addMouseListener(popupListener);
107       trackList.add(popup);
108 
109       // build the panel
110       setToolBar(buildToolBar());
111       setContent(buildContent());
112       setSelected(true);
113    }
114 
115    /**
116     * Clears out selected items.
117     */
118    public void clearSelection() {
119       // clear the selection
120       trackList.clearSelection();
121       if (playlist.size() > 0) {
122          trackList.setSelectedIndex(0);
123       }
124    }
125 
126    /**
127     * Load a playlist from disk.
128     */
129    public void load() {
130       LOG.debug("Load playlist");
131       final JFileChooser chooser = new JFileChooser();
132       chooser.setDialogTitle("Load Playlist");
133       chooser.setAcceptAllFileFilterUsed(false);
134       chooser.setFileFilter(FilterFactory.playlistFileFilter());
135       chooser.setMultiSelectionEnabled(false);
136       chooser.setFileHidingEnabled(true);
137       final int returnVal = chooser.showOpenDialog(Application.getDefaultParentFrame());
138       if (returnVal != JFileChooser.APPROVE_OPTION) {
139          return;
140       }
141 
142       final MainFrame mainFrame = (MainFrame) Application.getDefaultParentFrame();
143 
144       final File file = chooser.getSelectedFile();
145       if (LOG.isDebugEnabled()) {
146          LOG.debug("Absolute: " + file.getAbsolutePath());
147       }
148 
149       // now try and import the playlist
150       try {
151          GuiUtil.setBusyCursor(mainFrame, true);
152          final LoadPlaylistTask task = new LoadPlaylistTask(file, this.playlist);
153          //AZ: Put the Title of Progress Monitor Dialog Box and Cancel button
154          UIManager.put("ProgressMonitor.progressText", Resources.getString("label.ProgressTitle"));
155          UIManager.put("OptionPane.cancelButtonText", Resources.getString("label.Cancel"));
156          
157          final ProgressMonitor progressMonitor = new ProgressMonitor(Application.getDefaultParentFrame(), Resources
158                   .getString("messages.loadtracks"), "", 0, (int) task.getLengthOfTask());
159          progressMonitor.setProgress(0);
160          progressMonitor.setMillisToDecideToPopup(10);
161          task.go();
162          final Timer timer = new Timer(50, null);
163          timer.addActionListener(new TimerListener(progressMonitor, task, timer));
164          timer.start();
165 
166       } catch (IOException ex) {
167     	  final String errorMessage = ResourceUtils.getString("messages.ErrorLoadingFile"); 
168           MessageUtil.showError(this, errorMessage); //AZ 
169           LOG.error(errorMessage + "\n\n" + ex.getMessage(), ex);
170       } catch (SAXParseException spe) {
171          final String err = spe.toString() + "\nLine number: " + spe.getLineNumber() + "\nColumn number: "
172                   + spe.getColumnNumber() + "\nPublic ID: " + spe.getPublicId() + "\nSystem ID: " + spe.getSystemId();
173          LOG.error(err, spe);
174       } catch (Exception ex) {
175     	  final String errorMessage = ResourceUtils.getString("messages.ErrorLoadingFile"); 
176           MessageUtil.showError(this, errorMessage); //AZ 
177           LOG.error(errorMessage, ex);
178       } finally {
179          GuiUtil.setBusyCursor(mainFrame, false);
180       }
181    }
182 
183    /**
184     * Moves items down on the list.
185     */
186    public void moveDown() {
187       LOG.debug("Moving on down");
188       int[] selected = trackList.getSelectedIndices();
189       if (selected.length <= 0) {
190          MessageUtil.showInformation(Application.getDefaultParentFrame(), ResourceUtils.getString("messages.SelectSomethingToMove"));
191       } else {
192          int[] newSelections = trackList.getSelectedIndices();
193          for (int i = selected.length - 1; i >= 0; i--) {
194             int index = selected[i];
195             if (index == (this.playlist.size() - 1)) {
196                break;
197             }
198             this.playlist.moveDown(index);
199             newSelections[i] = (index + 1);
200 
201          }
202          trackList.setSelectedIndices(newSelections);
203       }
204       this.playlist.updateState();
205    }
206 
207    /**
208     * Moves the selected tracks to the other list either history or current.
209     */
210    public void moveOver() {
211       LOG.debug("Moving Over");
212       int[] selected = trackList.getSelectedIndices();
213       if (selected.length <= 0) {
214          MessageUtil.showInformation(Application.getDefaultParentFrame(), ResourceUtils.getString("messages.SelectSomethingToMoveOver"));
215       } else {
216          for (int i = 0; i < selected.length; i++) {
217             int index = selected[i];
218             this.playlist.moveOver(index);
219          }
220          // now remove them from the list
221          for (int i = selected.length - 1; i >= 0; i--) {
222             int index = selected[i];
223             this.playlist.remove(index);
224          }
225          this.clearSelection();
226       }
227       this.playlist.updateState();
228 
229    }
230 
231    /**
232     * Moves items up on the list.
233     */
234    public void moveUp() {
235       LOG.debug("Moving on up");
236       int[] selected = trackList.getSelectedIndices();
237       if (selected.length <= 0) {
238          MessageUtil.showInformation(Application.getDefaultParentFrame(), ResourceUtils.getString("messages.SelectSomethingToMove"));
239       } else {
240          int[] newSelections = trackList.getSelectedIndices();
241          for (int i = 0; i < selected.length; i++) {
242             int index = selected[i];
243             if (index == 0) {
244                break;
245             }
246             this.playlist.moveUp(index);
247             newSelections[i] = (index - 1);
248          }
249          trackList.setSelectedIndices(newSelections);
250       }
251       this.playlist.updateState();
252    }
253 
254    /*
255     * If the playlist changes, update the frame title and icon
256     */
257    public void propertyChange(PropertyChangeEvent aEvt) {
258       StringBuffer sb = new StringBuffer();
259       Icon icon = null;
260       if (this.playlist.isCurrent()) {
261          sb.append(ResourceUtils.getString("label.playlist"));
262          sb.append("  (");
263          sb.append(this.playlist.getCurrentDuration());
264          sb.append(')');
265          icon = Resources.PLAYLIST_ICON;
266       } else {
267          sb.append(ResourceUtils.getString("label.history"));
268          sb.append("  (");
269          sb.append(this.playlist.getHistoryDuration());
270          sb.append(')');
271          icon = Resources.HISTORY_ICON;
272       }
273 
274       setTitle(sb.toString());
275       setFrameIcon(icon);
276 
277       // update the now playing
278       final Track currenttrack = this.playlist.getCurrentTrack();
279       if (currenttrack != null) {
280         albumImage.setDisc(currenttrack.getDisc()); 
281       	if (MainModule.SETTINGS.isCopyImagesToDirectory()) {  //AZ
282       		final String imageName = ImageFactory.standardImageFileName(currenttrack.getDisc().getArtist().getName(), currenttrack.getDisc().getName(), currenttrack.getDisc().getYear());
283       		albumImage.setImage(ImageFactory.getScaledImage(imageName, 150, 150).getImage());
284       	} else {
285          albumImage.setDisc(currenttrack.getDisc());
286          if (StringUtils.isNotBlank(currenttrack.getDisc().getCoverUrl())) {
287             albumImage.setImage(ImageFactory.getScaledImage(currenttrack.getDisc().getCoverUrl(), 150, 150).getImage());
288          } else {
289             albumImage.setImage(null);
290          }
291       	}
292          artist.setText(currenttrack.getDisc().getArtist().getName());
293          disc.setText(currenttrack.getDisc().getName());
294          track.setText(currenttrack.getDisplayText(MainModule.SETTINGS.getDisplayFormatTrack()));
295          duration.setText(currenttrack.getDurationTime());
296          bitrate.setText(currenttrack.getBitrate().toString() + " kbps");
297       }
298      
299    }
300 
301    /**
302     * Removes tracks from the playlist
303     */
304    public void removeTracks() {
305       LOG.debug("Remove tracks");
306       int[] selected = trackList.getSelectedIndices();
307       if (selected.length <= 0) {
308          MessageUtil.showInformation(Application.getDefaultParentFrame(), ResourceUtils.getString("messages.SelectSomethingToRemove"));
309       } else {
310          for (int i = selected.length - 1; i >= 0; i--) {
311             int index = selected[i];
312             this.playlist.remove(index);
313          }
314          this.clearSelection();
315       }
316       this.playlist.updateState();
317    }
318 
319    /**
320     * Clears the playlist.
321     */
322    public void removeAllTracks() {
323       LOG.debug("Remove ALL tracks");
324       for (int i = playlist.size() - 1; i >= 0; i--) {
325          this.playlist.remove(i);
326       }
327       this.clearSelection();
328 
329       //AZ clear the currently playing track from the playlist
330       if (this.playlist.getCurrentTrack() != null) {
331     	  this.playlist.removeCurrentTrack();
332       }
333    }
334 
335    /**
336     * Saves the current playlist.
337     */
338    public void save() {
339       LOG.debug("Save playlist");
340       final JFileChooser chooser = new JFileChooser();
341       chooser.setDialogTitle("Save Playlist");
342       chooser.setAcceptAllFileFilterUsed(false);
343       FileFilter m3u = FilterFactory.m3uFileFilter();
344       FileFilter xspf = FilterFactory.xspfFileFilter();
345       chooser.addChoosableFileFilter(m3u);
346       chooser.addChoosableFileFilter(xspf);
347       if (MainModule.SETTINGS.getPlaylistType().equals(XspfFilter.XSPF)) {
348          chooser.setFileFilter(xspf);
349       } else {
350          chooser.setFileFilter(m3u);
351       }
352       chooser.setMultiSelectionEnabled(false);
353       chooser.setFileHidingEnabled(true);
354       final int returnVal = chooser.showSaveDialog(Application.getDefaultParentFrame());
355       if (returnVal != JFileChooser.APPROVE_OPTION) {
356          return;
357       }
358       File file = chooser.getSelectedFile();
359       if (LOG.isDebugEnabled()) {
360          LOG.debug("Absolute: " + file.getAbsolutePath());
361       }
362 
363       // add the extension if missing
364       if (chooser.getFileFilter() instanceof M3uFilter) {
365          file = FilterFactory.forceM3uExtension(file);
366       } else if (chooser.getFileFilter() instanceof XspfFilter) {
367          file = FilterFactory.forceXspfExtension(file);
368       }
369 
370       try {
371          this.playlist.save(file);
372 
373          MessageUtil.showInformation(Application.getDefaultParentFrame(), Resources.getString("messages.PlaylistSavedSuccessfully"));
374       } catch (IOException ex) {
375      	  final String errorMessage = ResourceUtils.getString("label.Errorwritingfile")+"\n\n" + ex.getMessage(); 
376           MessageUtil.showError(this, errorMessage); //AZ 
377           LOG.error(errorMessage, ex);
378       } catch (Exception ex) {
379      	  final String errorMessage = ResourceUtils.getString("label.Errorwritingfile")+"\n\n" + ex.getMessage(); 
380           MessageUtil.showError(this, errorMessage); //AZ 
381           LOG.error(errorMessage, ex);
382       }
383    }
384 
385    /**
386     * Toggles shuffling catalog or not.
387     * <p>
388     * @param enabled whether to shuffle or not
389     */
390    public void shuffleCatalog(boolean enabled) {
391       LOG.debug("Shuffling catalog");
392       this.playlist.setShuffleCatalog(enabled);
393       shufflePlaylist.setSelected(false);
394       this.playlist.setShufflePlaylist(false);
395       this.playlist.updateState();
396    }
397 
398    /**
399     * Toggles shuffling playlist or not.
400     * <p>
401     * @param enabled whether to shuffle or not
402     */
403    public void shufflePlaylist(boolean enabled) {
404       LOG.debug("Shuffling playlist");
405       this.playlist.setShufflePlaylist(enabled);
406       shuffleCatalog.setSelected(false);
407       this.playlist.setShuffleCatalog(false);
408       this.playlist.updateState();
409    }
410 
411    /**
412     * Toggle between history and current playlist.
413     */
414    public void toggle(boolean current) {
415       this.playlist.setCurrent(current);
416    }
417 
418    /**
419     * Builds and answers the content pane.
420     */
421    private JComponent buildContent() {
422 
423       JScrollPane scrollPane = UIFactory.createStrippedScrollPane(buildPlaylist());
424       scrollPane.setMinimumSize(new Dimension(300, 100));
425       scrollPane.setPreferredSize(new Dimension(300, 100));
426       return scrollPane;
427    }
428 
429    private JComponent buildPlaylist() {
430       trackList.setFocusable(true);
431       trackList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
432       trackList.setSelectedIndex(0);
433       trackList.setVisibleRowCount(13);
434       trackList.setCellRenderer(new PlaylistCellRenderer());
435       listScrollPane = new JScrollPane(trackList);
436 
437       FormLayout layout = new FormLayout("left:275px, 4dlu, right:pref:grow", "p");
438 
439       layout.setRowGroups(new int[][] { { 1 } });
440       PanelBuilder builder = new PanelBuilder(layout);
441       CellConstraints cc = new CellConstraints();
442       builder.add(listScrollPane, cc.xy(1, 1, "left,top"));
443       builder.add(buildTrackPanel(), cc.xy(3, 1, "left,top"));
444       return builder.getPanel();
445    }
446 
447    /**
448     * Builds and answers the toolbar.
449     */
450    private JToolBar buildToolBar() {
451       final ToolBarBuilder bar = new ToolBarBuilder("Playlist");
452       AbstractButton button = null;
453       button = (JToggleButton) ComponentFactory.createToolBarToggleButton(Actions.PLAYLIST_TOGGLE_ID);
454       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
455       bar.add(button);
456       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_LOAD_ID);
457       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
458       bar.add(button);
459       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_SAVE_ID);
460       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
461       bar.add(button);
462       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.TRACK_PLAY_IMMEDIATE_ID);
463       button.putClientProperty(Resources.EDITOR_COMPONENT, trackList);
464       bar.add(button);
465       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_MOVEUP_ID);
466       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
467       bar.add(button);
468       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_MOVEDOWN_ID);
469       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
470       bar.add(button);
471       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_MOVEOVER_ID);
472       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
473       bar.add(button);
474       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_REMOVE_TRACK_ID);
475       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
476       bar.add(button);
477       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_CLEAR_ID);
478       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
479       bar.add(button);
480       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_GOTO_ID);
481       button.putClientProperty(Resources.EDITOR_COMPONENT, trackList);
482       bar.add(button);
483       shufflePlaylist = (JToggleButton) ComponentFactory.createToolBarToggleButton(Actions.PLAYLIST_SHUFFLE_LIST_ID);
484       shufflePlaylist.putClientProperty(Resources.EDITOR_COMPONENT, this);
485       bar.add(shufflePlaylist);
486       shuffleCatalog = (JToggleButton) ComponentFactory.createToolBarToggleButton(Actions.PLAYLIST_SHUFFLE_CATALOG_ID);
487       shuffleCatalog.putClientProperty(Resources.EDITOR_COMPONENT, this);
488       bar.add(shuffleCatalog);
489       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_CLOSE_ID);
490       bar.add(button);
491       return bar.getToolBar();
492    }
493 
494    /**
495     * Builds the Track editor panel.
496     * <p>
497     * @return the panel to edit track info.
498     */
499    private JComponent buildTrackPanel() {
500       FormLayout layout = new FormLayout("pref, left:min(80dlu;pref):grow, 40dlu, pref, right:pref",
501                "4px, p, 4px, p, 4px, p, 4px, p, 4px, p, p, 75px");
502       PanelBuilder builder = new PanelBuilder(layout);
503       CellConstraints cc = new CellConstraints();
504 
505       final MainFrame mainFrame = (MainFrame) Application.getDefaultParentFrame();
506       artist = UIFactory.createBoldLabel("");
507       disc = UIFactory.createBoldLabel("");
508       track = UIFactory.createBoldLabel("");
509       duration = UIFactory.createBoldLabel("");
510       bitrate = UIFactory.createBoldLabel("");
511       albumImage = new AlbumImage(new Dimension(170, 170));
512       ActionListener actionListener = new ActionListener() {
513          public void actionPerformed(ActionEvent event) {
514             AlbumImage preview = (AlbumImage) event.getSource();
515             if (preview.getDisc() != null) {
516                GuiUtil.setBusyCursor(mainFrame, true);
517                mainFrame.getMainModule().selectNodeInTree(preview.getDisc());
518                GuiUtil.setBusyCursor(mainFrame, false);
519             }
520          }
521       };
522       albumImage.addActionListener(actionListener);
523 
524       builder.addLabel(ResourceUtils.getString("label.artist") + ": ", cc.xy(1, 2));
525       builder.add(artist, cc.xyw(2, 2, 3));
526       builder.add(albumImage, cc.xywh(5, 2, 1, 11));
527       builder.addLabel(ResourceUtils.getString("label.disc") + ": ", cc.xy(1, 4));
528       builder.add(disc, cc.xyw(2, 4, 3));
529       builder.addLabel(ResourceUtils.getString("label.track") + ": ", cc.xy(1, 6));
530       builder.add(track, cc.xyw(2, 6, 3));
531       builder.addLabel(ResourceUtils.getString("label.duration") + ": ", cc.xy(1, 8));
532       builder.add(duration, cc.xyw(2, 8, 3));
533       builder.addLabel(ResourceUtils.getString("label.bitrate") + ": ", cc.xy(1, 10));
534       builder.add(bitrate, cc.xyw(2, 10, 3));
535       //builder.add(mainFrame.getAnalyzer(), cc.xywh(1, 11, 4, 2)); //AZ Suspended Spectrum analyzer
536       final JComponent panel = builder.getPanel();
537       panel.setBorder(new TitledBorder(ResourceUtils.getString("label.current")));
538       return panel;
539    }
540 
541    // shows popups on right click of List nodes
542    private static class ListPopupListener extends MouseAdapter {
543 
544       final JPopupMenu popup;
545 
546       public ListPopupListener(JPopupMenu popup) {
547          this.popup = popup;
548       }
549 
550       public void mousePressed(MouseEvent evt) {
551          maybeShowPopup(evt);
552       }
553 
554       public void mouseReleased(MouseEvent e) {
555          maybeShowPopup(e);
556       }
557 
558       private void maybeShowPopup(MouseEvent e) {
559          if (e.isPopupTrigger()) {
560             ActionManager.get(Actions.TRACK_PLAY_IMMEDIATE_ID).setEnabled(true);
561 
562             // popup the menu
563             popup.show(e.getComponent(), e.getX(), e.getY());
564          }
565       }
566 
567    }
568 
569 }