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          LOG.error("Error loading file: \n\n" + ex.getMessage(), ex);
163       } catch (SAXParseException spe) {
164          final String err = spe.toString() + "\nLine number: " + spe.getLineNumber() + "\nColumn number: "
165                   + spe.getColumnNumber() + "\nPublic ID: " + spe.getPublicId() + "\nSystem ID: " + spe.getSystemId();
166          LOG.error(err, spe);
167       } catch (Exception ex) {
168          LOG.error("Unexpected error loading file.", ex);
169       } finally {
170          GuiUtil.setBusyCursor(mainFrame, false);
171       }
172    }
173 
174    /**
175     * Moves items down on the list.
176     */
177    public void moveDown() {
178       LOG.debug("Moving on down");
179       int[] selected = trackList.getSelectedIndices();
180       if (selected.length <= 0) {
181          MessageUtil.showInformation(Application.getDefaultParentFrame(), "Select something to move down.");
182       } else {
183          int[] newSelections = trackList.getSelectedIndices();
184          for (int i = selected.length - 1; i >= 0; i--) {
185             int index = selected[i];
186             if (index == (this.playlist.size() - 1)) {
187                break;
188             }
189             this.playlist.moveDown(index);
190             newSelections[i] = (index + 1);
191 
192          }
193          trackList.setSelectedIndices(newSelections);
194       }
195       this.playlist.updateState();
196    }
197 
198    /**
199     * Moves the selected tracks to the other list either history or current.
200     */
201    public void moveOver() {
202       LOG.debug("Moving Over");
203       int[] selected = trackList.getSelectedIndices();
204       if (selected.length <= 0) {
205          MessageUtil.showInformation(Application.getDefaultParentFrame(), "Select something to move over.");
206       } else {
207          for (int i = 0; i < selected.length; i++) {
208             int index = selected[i];
209             this.playlist.moveOver(index);
210          }
211          // now remove them from the list
212          for (int i = selected.length - 1; i >= 0; i--) {
213             int index = selected[i];
214             this.playlist.remove(index);
215          }
216          this.clearSelection();
217       }
218       this.playlist.updateState();
219 
220    }
221 
222    /**
223     * Moves items up on the list.
224     */
225    public void moveUp() {
226       LOG.debug("Moving on up");
227       int[] selected = trackList.getSelectedIndices();
228       if (selected.length <= 0) {
229          MessageUtil.showInformation(Application.getDefaultParentFrame(), "Select something to move up.");
230       } else {
231          int[] newSelections = trackList.getSelectedIndices();
232          for (int i = 0; i < selected.length; i++) {
233             int index = selected[i];
234             if (index == 0) {
235                break;
236             }
237             this.playlist.moveUp(index);
238             newSelections[i] = (index - 1);
239          }
240          trackList.setSelectedIndices(newSelections);
241       }
242       this.playlist.updateState();
243    }
244 
245    /*
246     * If the playlist changes, update the frame title and icon
247     */
248    public void propertyChange(PropertyChangeEvent aEvt) {
249       StringBuffer sb = new StringBuffer();
250       Icon icon = null;
251       if (this.playlist.isCurrent()) {
252          sb.append(ResourceUtils.getString("label.playlist"));
253          sb.append("  (");
254          sb.append(this.playlist.getCurrentDuration());
255          sb.append(')');
256          icon = Resources.PLAYLIST_ICON;
257       } else {
258          sb.append(ResourceUtils.getString("label.history"));
259          sb.append("  (");
260          sb.append(this.playlist.getHistoryDuration());
261          sb.append(')');
262          icon = Resources.HISTORY_ICON;
263       }
264 
265       setTitle(sb.toString());
266       setFrameIcon(icon);
267 
268       // update the now playing
269       final Track currenttrack = this.playlist.getCurrentTrack();
270       if (currenttrack != null) {
271          albumImage.setDisc(currenttrack.getDisc());
272          if (StringUtils.isNotBlank(currenttrack.getDisc().getCoverUrl())) {
273             albumImage.setImage(ImageFactory.getScaledImage(currenttrack.getDisc().getCoverUrl(), 150, 150).getImage());
274          } else {
275             albumImage.setImage(null);
276          }
277          artist.setText(currenttrack.getDisc().getArtist().getName());
278          disc.setText(currenttrack.getDisc().getName());
279          track.setText(currenttrack.getDisplayText(MainModule.SETTINGS.getDisplayFormatTrack()));
280          duration.setText(currenttrack.getDurationTime());
281          bitrate.setText(currenttrack.getBitrate().toString() + " kbps");
282       }
283      
284    }
285 
286    /**
287     * Removes tracks from the playlist
288     */
289    public void removeTracks() {
290       LOG.debug("Remove tracks");
291       int[] selected = trackList.getSelectedIndices();
292       if (selected.length <= 0) {
293          MessageUtil.showInformation(Application.getDefaultParentFrame(), "Select something to remove.");
294       } else {
295          for (int i = selected.length - 1; i >= 0; i--) {
296             int index = selected[i];
297             this.playlist.remove(index);
298          }
299          this.clearSelection();
300       }
301       this.playlist.updateState();
302    }
303 
304    /**
305     * Clears the playlist.
306     */
307    public void removeAllTracks() {
308       LOG.debug("Remove ALL tracks");
309       for (int i = playlist.size() - 1; i >= 0; i--) {
310          this.playlist.remove(i);
311       }
312       this.clearSelection();
313    }
314 
315    /**
316     * Saves the current playlist.
317     */
318    public void save() {
319       LOG.debug("Save playlist");
320       final JFileChooser chooser = new JFileChooser();
321       chooser.setDialogTitle("Save Playlist");
322       chooser.setAcceptAllFileFilterUsed(false);
323       FileFilter m3u = FilterFactory.m3uFileFilter();
324       FileFilter xspf = FilterFactory.xspfFileFilter();
325       chooser.addChoosableFileFilter(m3u);
326       chooser.addChoosableFileFilter(xspf);
327       if (MainModule.SETTINGS.getPlaylistType().equals(XspfFilter.XSPF)) {
328          chooser.setFileFilter(xspf);
329       } else {
330          chooser.setFileFilter(m3u);
331       }
332       chooser.setMultiSelectionEnabled(false);
333       chooser.setFileHidingEnabled(true);
334       final int returnVal = chooser.showSaveDialog(Application.getDefaultParentFrame());
335       if (returnVal != JFileChooser.APPROVE_OPTION) {
336          return;
337       }
338       File file = chooser.getSelectedFile();
339       if (LOG.isDebugEnabled()) {
340          LOG.debug("Absolute: " + file.getAbsolutePath());
341       }
342 
343       // add the extension if missing
344       if (chooser.getFileFilter() instanceof M3uFilter) {
345          file = FilterFactory.forceM3uExtension(file);
346       } else if (chooser.getFileFilter() instanceof XspfFilter) {
347          file = FilterFactory.forceXspfExtension(file);
348       }
349 
350       try {
351          this.playlist.save(file);
352 
353          MessageUtil.showInformation(Application.getDefaultParentFrame(), "Playlist saved successfully.");
354       } catch (IOException ex) {
355          LOG.error("Error writing file: \n\n" + ex.getMessage(), ex);
356       } catch (Exception ex) {
357          LOG.error("Unexpected error writing file.", ex);
358       }
359    }
360 
361    /**
362     * Toggles shuffling catalog or not.
363     * <p>
364     * @param enabled whether to shuffle or not
365     */
366    public void shuffleCatalog(boolean enabled) {
367       LOG.debug("Shuffling catalog");
368       this.playlist.setShuffleCatalog(enabled);
369       shufflePlaylist.setSelected(false);
370       this.playlist.setShufflePlaylist(false);
371       this.playlist.updateState();
372    }
373 
374    /**
375     * Toggles shuffling playlist or not.
376     * <p>
377     * @param enabled whether to shuffle or not
378     */
379    public void shufflePlaylist(boolean enabled) {
380       LOG.debug("Shuffling playlist");
381       this.playlist.setShufflePlaylist(enabled);
382       shuffleCatalog.setSelected(false);
383       this.playlist.setShuffleCatalog(false);
384       this.playlist.updateState();
385    }
386 
387    /**
388     * Toggle between history and current playlist.
389     */
390    public void toggle(boolean current) {
391       this.playlist.setCurrent(current);
392    }
393 
394    /**
395     * Builds and answers the content pane.
396     */
397    private JComponent buildContent() {
398 
399       JScrollPane scrollPane = UIFactory.createStrippedScrollPane(buildPlaylist());
400       scrollPane.setMinimumSize(new Dimension(300, 100));
401       scrollPane.setPreferredSize(new Dimension(300, 100));
402       return scrollPane;
403    }
404 
405    private JComponent buildPlaylist() {
406       trackList.setFocusable(true);
407       trackList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
408       trackList.setSelectedIndex(0);
409       trackList.setVisibleRowCount(13);
410       trackList.setCellRenderer(new PlaylistCellRenderer());
411       listScrollPane = new JScrollPane(trackList);
412 
413       FormLayout layout = new FormLayout("left:275px, 4dlu, right:pref:grow", "p");
414 
415       layout.setRowGroups(new int[][] { { 1 } });
416       PanelBuilder builder = new PanelBuilder(layout);
417       CellConstraints cc = new CellConstraints();
418       builder.add(listScrollPane, cc.xy(1, 1, "left,top"));
419       builder.add(buildTrackPanel(), cc.xy(3, 1, "left,top"));
420       return builder.getPanel();
421    }
422 
423    /**
424     * Builds and answers the toolbar.
425     */
426    private JToolBar buildToolBar() {
427       final ToolBarBuilder bar = new ToolBarBuilder("Playlist");
428       AbstractButton button = null;
429       button = (JToggleButton) ComponentFactory.createToolBarToggleButton(Actions.PLAYLIST_TOGGLE_ID);
430       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
431       bar.add(button);
432       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_LOAD_ID);
433       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
434       bar.add(button);
435       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_SAVE_ID);
436       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
437       bar.add(button);
438       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.TRACK_PLAY_IMMEDIATE_ID);
439       button.putClientProperty(Resources.EDITOR_COMPONENT, trackList);
440       bar.add(button);
441       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_MOVEUP_ID);
442       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
443       bar.add(button);
444       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_MOVEDOWN_ID);
445       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
446       bar.add(button);
447       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_MOVEOVER_ID);
448       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
449       bar.add(button);
450       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_REMOVE_TRACK_ID);
451       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
452       bar.add(button);
453       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_CLEAR_ID);
454       button.putClientProperty(Resources.EDITOR_COMPONENT, this);
455       bar.add(button);
456       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_GOTO_ID);
457       button.putClientProperty(Resources.EDITOR_COMPONENT, trackList);
458       bar.add(button);
459       shufflePlaylist = (JToggleButton) ComponentFactory.createToolBarToggleButton(Actions.PLAYLIST_SHUFFLE_LIST_ID);
460       shufflePlaylist.putClientProperty(Resources.EDITOR_COMPONENT, this);
461       bar.add(shufflePlaylist);
462       shuffleCatalog = (JToggleButton) ComponentFactory.createToolBarToggleButton(Actions.PLAYLIST_SHUFFLE_CATALOG_ID);
463       shuffleCatalog.putClientProperty(Resources.EDITOR_COMPONENT, this);
464       bar.add(shuffleCatalog);
465       button = (AbstractButton) ComponentFactory.createToolBarButton(Actions.PLAYLIST_CLOSE_ID);
466       bar.add(button);
467       return bar.getToolBar();
468    }
469 
470    /**
471     * Builds the Track editor panel.
472     * <p>
473     * @return the panel to edit track info.
474     */
475    private JComponent buildTrackPanel() {
476       FormLayout layout = new FormLayout("pref, left:min(80dlu;pref):grow, 40dlu, pref, right:pref",
477                "4px, p, 4px, p, 4px, p, 4px, p, 4px, p, p, 75px");
478       PanelBuilder builder = new PanelBuilder(layout);
479       CellConstraints cc = new CellConstraints();
480 
481       final MainFrame mainFrame = (MainFrame) Application.getDefaultParentFrame();
482       artist = UIFactory.createBoldLabel("");
483       disc = UIFactory.createBoldLabel("");
484       track = UIFactory.createBoldLabel("");
485       duration = UIFactory.createBoldLabel("");
486       bitrate = UIFactory.createBoldLabel("");
487       albumImage = new AlbumImage(new Dimension(170, 170));
488       ActionListener actionListener = new ActionListener() {
489          public void actionPerformed(ActionEvent event) {
490             AlbumImage preview = (AlbumImage) event.getSource();
491             if (preview.getDisc() != null) {
492                GuiUtil.setBusyCursor(mainFrame, true);
493                mainFrame.getMainModule().selectNodeInTree(preview.getDisc());
494                GuiUtil.setBusyCursor(mainFrame, false);
495             }
496          }
497       };
498       albumImage.addActionListener(actionListener);
499 
500       builder.addLabel(ResourceUtils.getString("label.artist") + ": ", cc.xy(1, 2));
501       builder.add(artist, cc.xyw(2, 2, 3));
502       builder.add(albumImage, cc.xywh(5, 2, 1, 11));
503       builder.addLabel(ResourceUtils.getString("label.disc") + ": ", cc.xy(1, 4));
504       builder.add(disc, cc.xyw(2, 4, 3));
505       builder.addLabel(ResourceUtils.getString("label.track") + ": ", cc.xy(1, 6));
506       builder.add(track, cc.xyw(2, 6, 3));
507       builder.addLabel(ResourceUtils.getString("label.duration") + ": ", cc.xy(1, 8));
508       builder.add(duration, cc.xyw(2, 8, 3));
509       builder.addLabel(ResourceUtils.getString("label.bitrate") + ": ", cc.xy(1, 10));
510       builder.add(bitrate, cc.xyw(2, 10, 3));
511       builder.add(mainFrame.getAnalyzer(), cc.xywh(1, 11, 4, 2));
512       final JComponent panel = builder.getPanel();
513       panel.setBorder(new TitledBorder(ResourceUtils.getString("label.current")));
514       return panel;
515    }
516 
517    // shows popups on right click of List nodes
518    private static class ListPopupListener extends MouseAdapter {
519 
520       final JPopupMenu popup;
521 
522       public ListPopupListener(JPopupMenu popup) {
523          this.popup = popup;
524       }
525 
526       public void mousePressed(MouseEvent evt) {
527          maybeShowPopup(evt);
528       }
529 
530       public void mouseReleased(MouseEvent e) {
531          maybeShowPopup(e);
532       }
533 
534       private void maybeShowPopup(MouseEvent e) {
535          if (e.isPopupTrigger()) {
536             ActionManager.get(Actions.TRACK_PLAY_IMMEDIATE_ID).setEnabled(true);
537 
538             // popup the menu
539             popup.show(e.getComponent(), e.getX(), e.getY());
540          }
541       }
542 
543    }
544 
545 }