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
65
66
67
68
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
88
89
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
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
104 final JPopupMenu popup = MainMenuBuilder.buildPlaylistPopupMenu(this, trackList);
105 MouseListener popupListener = new ListPopupListener(popup);
106 trackList.addMouseListener(popupListener);
107 trackList.add(popup);
108
109
110 setToolBar(buildToolBar());
111 setContent(buildContent());
112 setSelected(true);
113 }
114
115
116
117
118 public void clearSelection() {
119
120 trackList.clearSelection();
121 if (playlist.size() > 0) {
122 trackList.setSelectedIndex(0);
123 }
124 }
125
126
127
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
150 try {
151 GuiUtil.setBusyCursor(mainFrame, true);
152 final LoadPlaylistTask task = new LoadPlaylistTask(file, this.playlist);
153
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);
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);
177 LOG.error(errorMessage, ex);
178 } finally {
179 GuiUtil.setBusyCursor(mainFrame, false);
180 }
181 }
182
183
184
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
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
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
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
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
278 final Track currenttrack = this.playlist.getCurrentTrack();
279 if (currenttrack != null) {
280 albumImage.setDisc(currenttrack.getDisc());
281 if (MainModule.SETTINGS.isCopyImagesToDirectory()) {
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
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
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
330 if (this.playlist.getCurrentTrack() != null) {
331 this.playlist.removeCurrentTrack();
332 }
333 }
334
335
336
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
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);
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);
381 LOG.error(errorMessage, ex);
382 }
383 }
384
385
386
387
388
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
400
401
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
413
414 public void toggle(boolean current) {
415 this.playlist.setCurrent(current);
416 }
417
418
419
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
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
496
497
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
536 final JComponent panel = builder.getPanel();
537 panel.setBorder(new TitledBorder(ResourceUtils.getString("label.current")));
538 return panel;
539 }
540
541
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
563 popup.show(e.getComponent(), e.getX(), e.getY());
564 }
565 }
566
567 }
568
569 }