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
64
65
66
67
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
87
88
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
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
103 final JPopupMenu popup = MainMenuBuilder.buildPlaylistPopupMenu(this, trackList);
104 MouseListener popupListener = new ListPopupListener(popup);
105 trackList.addMouseListener(popupListener);
106 trackList.add(popup);
107
108
109 setToolBar(buildToolBar());
110 setContent(buildContent());
111 setSelected(true);
112 }
113
114
115
116
117 public void clearSelection() {
118
119 trackList.clearSelection();
120 if (playlist.size() > 0) {
121 trackList.setSelectedIndex(0);
122 }
123 }
124
125
126
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
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
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
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
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
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
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
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
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
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
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
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
363
364
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
376
377
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
389
390 public void toggle(boolean current) {
391 this.playlist.setCurrent(current);
392 }
393
394
395
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
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
472
473
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
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
539 popup.show(e.getComponent(), e.getX(), e.getY());
540 }
541 }
542
543 }
544
545 }