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