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 final String errorMessage = ResourceUtils.getString("messages.ErrorLoadingFile");
163 MessageUtil.showError(this, errorMessage);
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);
172 LOG.error(errorMessage, ex);
173 } finally {
174 GuiUtil.setBusyCursor(mainFrame, false);
175 }
176 }
177
178
179
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
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
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
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
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
273 final Track currenttrack = this.playlist.getCurrentTrack();
274 if (currenttrack != null) {
275 albumImage.setDisc(currenttrack.getDisc());
276 if (MainModule.SETTINGS.isCopyImagesToDirectory()) {
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
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
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
325 if (this.playlist.getCurrentTrack() != null) {
326 this.playlist.removeCurrentTrack();
327 }
328 }
329
330
331
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
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);
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);
376 LOG.error(errorMessage, ex);
377 }
378 }
379
380
381
382
383
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
395
396
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
408
409 public void toggle(boolean current) {
410 this.playlist.setCurrent(current);
411 }
412
413
414
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
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
491
492
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
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
558 popup.show(e.getComponent(), e.getX(), e.getY());
559 }
560 }
561
562 }
563
564 }