/*
* Copyright (c) 2008, 2009, 2010, 2011 Denis Tulskiy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with this work. If not, see <http://www.gnu.org/licenses/>.
*/
package com.tulskiy.musique.gui.playlist;
import java.awt.BorderLayout;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.TransferHandler;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import com.tulskiy.musique.audio.player.Player;
import com.tulskiy.musique.audio.player.PlayerEvent;
import com.tulskiy.musique.audio.player.PlayerListener;
import com.tulskiy.musique.gui.dialogs.OptionsDialog;
import com.tulskiy.musique.gui.dialogs.PlaybackQueueDialog;
import com.tulskiy.musique.gui.dialogs.ProgressDialog;
import com.tulskiy.musique.gui.dialogs.SearchDialog;
import com.tulskiy.musique.gui.dialogs.Task;
import com.tulskiy.musique.gui.dialogs.TreeFileChooser;
import com.tulskiy.musique.images.Images;
import com.tulskiy.musique.playlist.PlaybackOrder;
import com.tulskiy.musique.playlist.Playlist;
import com.tulskiy.musique.playlist.PlaylistManager;
import com.tulskiy.musique.playlist.Track;
import com.tulskiy.musique.system.Application;
import com.tulskiy.musique.system.configuration.Configuration;
import com.tulskiy.musique.system.configuration.PlaylistConfiguration;
import com.tulskiy.musique.util.Util;
/**
* @Author: Denis Tulskiy
* @Date: Feb 6, 2010
*/
public class PlaylistPanel extends JPanel {
private Application app = Application.getInstance();
private Configuration config = app.getConfiguration();
private PlaylistTabs tabs;
private List<TableColumnModel> columnModels = new LinkedList<TableColumnModel>();
public PlaylistPanel() {
PlaylistManager playlistManager = app.getPlaylistManager();
ArrayList<Playlist> playlists = playlistManager.getPlaylists();
setLayout(new BorderLayout());
tabs = new PlaylistTabs();
add(tabs, BorderLayout.CENTER);
List<String> bounds = PlaylistConfiguration.getTabBounds();
for (int i = 0; i < playlists.size(); i++) {
Playlist pl = playlists.get(i);
PlaylistTable newTable = new PlaylistTable(pl, pl.getColumns());
newTable.setUpDndCCP();
columnModels.add(newTable.getColumnModel());
//try to set last position
try {
String s = bounds.get(i);
Integer y = Integer.valueOf(s);
newTable.scrollRectToVisible(new Rectangle(0, y, 0, 0));
} catch (Exception ignored) {
}
tabs.addTab(pl.getName(), newTable.getScrollPane());
}
final Playlist playlist = playlistManager.getActivePlaylist();
tabs.setSelectedIndex(-1);
tabs.setSelectedIndex(playlists.indexOf(playlist));
PlaybackOrder order = app.getPlayer().getPlaybackOrder();
Track lastPlayed = order.getLastPlayed();
if (lastPlayed != null) {
PlaylistTable table = tabs.getSelectedTable();
if (table != null) {
int index = table.getPlaylist().indexOf(lastPlayed);
if (index != -1)
table.setRowSelectionInterval(index, index);
}
}
final Player player = app.getPlayer();
final Timer update = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (player.isPlaying()) {
PlaylistTable table = tabs.getSelectedTable();
if (table != null) {
int index = table.getPlaylist().indexOf(player.getTrack());
((AbstractTableModel) table.getModel()).fireTableRowsUpdated(index, index);
}
}
}
});
player.addListener(new PlayerListener() {
@Override
public void onEvent(PlayerEvent e) {
Track track = player.getTrack();
if (track != null && track.getTrackData().isStream()) {
update.start();
} else {
update.stop();
}
}
});
}
public void saveSettings() {
PlaylistManager playlistManager = app.getPlaylistManager();
ArrayList<Playlist> playlists = playlistManager.getPlaylists();
for (int n = 0; n < columnModels.size(); n++) {
List<PlaylistColumn> columns = playlists.get(n).getColumns();
TableColumnModel columnModel = columnModels.get(n);
for (int i = 0; i < columnModel.getColumnCount(); i++) {
TableColumn tc = columnModel.getColumn(i);
PlaylistColumn pc = columns.get(tc.getModelIndex());
pc.setPosition(i);
pc.setSize(tc.getWidth());
}
Collections.sort(columns);
config.put("playlist.selectedTrack", null);
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < tabs.getTabCount(); i++) {
PlaylistTable t = tabs.getTableAt(i);
list.add(t.getVisibleRect().y);
}
// TODO check that bounds stuff works as expected
PlaylistConfiguration.setTabBounds(list);
}
}
private JMenuItem newItem(String name, String hotkey, ActionListener al) {
JMenuItem item = new JMenuItem(name);
item.setAccelerator(KeyStroke.getKeyStroke(hotkey));
item.addActionListener(al);
return item;
}
private Action tableAction(final String actionName, String name) {
return new AbstractAction(name) {
@Override
public void actionPerformed(ActionEvent e) {
PlaylistTable table = tabs.getSelectedTable();
if (table != null)
table.runAction(actionName);
}
};
}
public void addMenu(JMenuBar menuBar) {
Icon emptyIcon = Images.getEmptyIcon();
final JComponent comp = getRootPane();
JMenu fileMenu = new JMenu("File ");
menuBar.add(fileMenu);
JMenu editMenu = new JMenu("Edit");
menuBar.add(editMenu);
final JMenu playbackMenu = new JMenu("Playback");
menuBar.add(playbackMenu);
ActionMap tMap = tabs.getActions();
fileMenu.add(tMap.get("newPlaylist")).setAccelerator(KeyStroke.getKeyStroke("ctrl T"));
fileMenu.add(tMap.get("removePlaylist")).setIcon(emptyIcon);
fileMenu.add(tMap.get("loadPlaylist"));
fileMenu.add(tMap.get("savePlaylist")).setAccelerator(KeyStroke.getKeyStroke("ctrl S"));
fileMenu.addSeparator();
fileMenu.add("Add Files").addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addItems(JFileChooser.FILES_ONLY);
}
});
fileMenu.add("Add Folder").addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addItems(JFileChooser.DIRECTORIES_ONLY);
}
});
fileMenu.add("Add Location").addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String ret = JOptionPane.showInputDialog(comp, "Enter URL", "Add Location", JOptionPane.QUESTION_MESSAGE);
if (!Util.isEmpty(ret)) {
PlaylistTable table = tabs.getSelectedTable();
if (table == null)
return;
table.getPlaylist().insertItem(ret, -1, false, null);
}
}
});
fileMenu.addSeparator();
fileMenu.add(newItem("Close", "ctrl W", new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (config.getBoolean("tray.enabled", false) &&
config.getBoolean("tray.minimizeOnClose", true)) {
SwingUtilities.windowForComponent(comp).setVisible(false);
} else {
app.exit();
}
}
}));
fileMenu.add(newItem("Quit", "ctrl Q", new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
app.exit();
}
}));
TransferActionListener transferListener = new TransferActionListener();
JMenuItem menuItem = new JMenuItem("Cut");
menuItem.setActionCommand((String) TransferHandler.getCutAction().
getValue(Action.NAME));
menuItem.addActionListener(transferListener);
menuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl X"));
menuItem.setMnemonic(KeyEvent.VK_T);
editMenu.add(menuItem);
menuItem = new JMenuItem("Copy");
menuItem.setActionCommand((String) TransferHandler.getCopyAction().
getValue(Action.NAME));
menuItem.addActionListener(transferListener);
menuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl C"));
menuItem.setMnemonic(KeyEvent.VK_C);
editMenu.add(menuItem);
menuItem = new JMenuItem("Paste");
menuItem.setActionCommand((String) TransferHandler.getPasteAction().
getValue(Action.NAME));
menuItem.addActionListener(transferListener);
menuItem.setAccelerator(KeyStroke.getKeyStroke("ctrl V"));
menuItem.setMnemonic(KeyEvent.VK_P);
editMenu.add(menuItem);
editMenu.addSeparator();
editMenu.add("Clear").addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PlaylistTable table = tabs.getSelectedTable();
if (table == null)
return;
table.getPlaylist().clear();
table.update();
}
});
editMenu.add(tableAction("removeSelected", "Remove Tracks"));
final String[] groupItems = {"None", "Artist", "Album Artist", "Artist/Album",
"Artist/Album/Date", null, "Custom"};
final String[] groupValues = {null, "%artist%", "%albumArtist%", "%albumArtist%[ - %album%]",
"%albumArtist%[ - %album%][ '['%year%']']"
};
JMenu groups = new JMenu("Group playlist");
ActionListener groupListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PlaylistTable table = tabs.getSelectedTable();
if (table == null)
return;
Playlist playlist = table.getPlaylist();
int row = table.rowAtPoint(table.getVisibleRect().getLocation());
Track firstVisibleTrack;
do {
firstVisibleTrack = playlist.get(row++);
} while (firstVisibleTrack.getTrackData().getLocation() == null);
JMenuItem src = (JMenuItem) e.getSource();
Integer index = (Integer) src.getClientProperty("index");
if (index < groupItems.length - 1) {
playlist.setGroupBy(groupValues[index]);
} else {
Object ret = JOptionPane.showInputDialog(comp,
"Select formatting",
config.getString("playlists.groupBy", playlist.getGroupBy()));
if (ret != null) {
playlist.setGroupBy(ret.toString());
config.setString("playlists.groupBy", ret.toString());
}
}
int firstVisibleIndex = playlist.indexOf(firstVisibleTrack);
if (firstVisibleIndex != -1) {
Rectangle cellRect = table.getCellRect(firstVisibleIndex, 0, true);
Rectangle visibleRect = table.getVisibleRect();
cellRect.setSize(visibleRect.width, visibleRect.height);
table.scrollRectToVisible(cellRect);
}
table.update();
}
};
for (int i = 0; i < groupItems.length; i++) {
String groupValue = groupItems[i];
if (groupValue == null) {
groups.addSeparator();
continue;
}
AbstractButton item = groups.add(groupValue);
item.setIcon(emptyIcon);
item.addActionListener(groupListener);
item.putClientProperty("index", i);
}
editMenu.add(groups);
JMenu sort = new JMenu("Sort");
String[] sortItems = {
"Sort by...", "Randomize", "Reverse",
"Sort by Artist", "Sort by Album",
"Sort by File Path", "Sort by Title",
"Sort by Track Number", "Sort by Album Artist/Year/Album/Disc/Track/File Name"
};
final String[] sortValues = {
null, null, null, "%artist%", "%album%",
"%file%", "%title%", "%trackNumber%",
"%albumArtist% - %year% - %album% - %discNumber% - %trackNumber% - %fileName%"
};
ActionListener sortListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JMenuItem src = (JMenuItem) e.getSource();
Integer index = (Integer) src.getClientProperty("index");
PlaylistTable table = tabs.getSelectedTable();
if (table == null)
return;
Playlist playlist = table.getPlaylist();
switch (index) {
case 0:
Object ret = JOptionPane.showInputDialog(comp,
"Sort By...",
config.getString("playlist.sortString", ""));
if (ret != null) {
playlist.sort(ret.toString(), false);
config.setString("playlist.sortString", ret.toString());
}
break;
case 1:
Collections.shuffle(playlist);
playlist.firePlaylistChanged();
break;
case 2:
Collections.reverse(playlist);
playlist.firePlaylistChanged();
break;
default:
playlist.sort(sortValues[index], false);
}
}
};
for (int i = 0; i < sortItems.length; i++) {
String sortValue = sortItems[i];
if (sortValue == null) {
sort.addSeparator();
continue;
}
AbstractButton item = sort.add(sortValue);
item.setIcon(emptyIcon);
item.addActionListener(sortListener);
item.putClientProperty("index", i);
}
editMenu.add(sort);
editMenu.addSeparator();
editMenu.add(tableAction("clearQueue", "Clear Playback Queue"));
editMenu.add(new JMenuItem("View Playback Queue")).addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new PlaybackQueueDialog(comp).setVisible(true);
}
});
editMenu.add(newItem("Search", "ctrl F", new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PlaylistTable table = tabs.getSelectedTable();
if (table == null)
return;
new SearchDialog(table).setVisible(true);
}
}));
editMenu.add("Remove Dead Items").addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PlaylistTable table = tabs.getSelectedTable();
if (table != null) {
table.getPlaylist().removeDeadItems();
}
}
});
editMenu.add("Remove Duplicates").addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PlaylistTable table = tabs.getSelectedTable();
if (table != null) {
table.getPlaylist().removeDuplicates();
}
}
});
editMenu.addSeparator();
JMenuItem propsItem = editMenu.add("Properties");
propsItem.setIcon(emptyIcon);
propsItem.setAccelerator(KeyStroke.getKeyStroke("ctrl P"));
propsItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new OptionsDialog(comp).setVisible(true);
}
});
JMenu orderMenu = new JMenu("Order");
playbackMenu.add(orderMenu);
ActionListener orderListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JMenuItem item = (JMenuItem) e.getSource();
int index = (Integer) item.getClientProperty("order");
config.setInt("player.playbackOrder", index);
}
};
final ButtonGroup gr = new ButtonGroup();
for (PlaybackOrder.Order o : PlaybackOrder.Order.values()) {
JCheckBoxMenuItem item = new JCheckBoxMenuItem(o.toString());
item.addActionListener(orderListener);
item.putClientProperty("order", o.ordinal());
gr.add(item);
orderMenu.add(item);
}
config.addPropertyChangeListener("player.playbackOrder", true, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
int value = config.getInt(evt.getPropertyName(), 0);
Enumeration<AbstractButton> items = gr.getElements();
while (items.hasMoreElements()) {
AbstractButton item = items.nextElement();
if (item.getClientProperty("order").equals(value)) {
item.setSelected(true);
}
}
}
});
playbackMenu.addSeparator();
playbackMenu.add(tableAction("showNowPlaying", "Scroll to Now Playing"));
boolean selected = config.getBoolean("playlists.cursorFollowsPlayback", true);
playbackMenu.add(new JCheckBoxMenuItem("Cursor Follows Playback", selected)).addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource();
config.setBoolean("playlists.cursorFollowsPlayback", item.isSelected());
}
});
selected = config.getBoolean("playlists.playbackFollowsCursor", false);
playbackMenu.add(new JCheckBoxMenuItem("Playback Follows Cursor", selected)).addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource();
config.setBoolean("playlists.playbackFollowsCursor", item.isSelected());
}
});
final JCheckBoxMenuItem stopAfterCurrent = new JCheckBoxMenuItem("Stop After Current");
playbackMenu.add(stopAfterCurrent).addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource();
app.getPlayer().setStopAfterCurrent(item.isSelected());
}
});
app.getPlayer().addListener(new PlayerListener() {
@Override
public void onEvent(PlayerEvent e) {
if (e.getEventCode() == PlayerEvent.PlayerEventCode.STOPPED) {
stopAfterCurrent.setSelected(false);
}
}
});
}
private void addItems(int selectionMode) {
boolean allowFiles = selectionMode != JFileChooser.DIRECTORIES_ONLY;
TreeFileChooser fc = new TreeFileChooser(this,
allowFiles ? "Open file" : "Open folder",
allowFiles);
File[] files = fc.showOpenDialog();
if (files != null) {
final PlaylistTable table = tabs.getSelectedTable();
if (table == null)
return;
ProgressDialog dialog = new ProgressDialog(table.getParentFrame(), "Adding Files");
dialog.show(new Task.FileAddingTask(table.getPlaylist(), files, -1));
}
}
public class TransferActionListener implements ActionListener,
PropertyChangeListener {
private JComponent focusOwner = null;
public TransferActionListener() {
KeyboardFocusManager manager = KeyboardFocusManager.
getCurrentKeyboardFocusManager();
manager.addPropertyChangeListener("permanentFocusOwner", this);
}
public void propertyChange(PropertyChangeEvent e) {
Object o = e.getNewValue();
if (o instanceof JComponent) {
focusOwner = (JComponent) o;
} else {
focusOwner = null;
}
}
public void actionPerformed(ActionEvent e) {
if (focusOwner == null)
return;
String action = e.getActionCommand();
Action a = focusOwner.getActionMap().get(action);
if (a != null) {
a.actionPerformed(new ActionEvent(focusOwner,
ActionEvent.ACTION_PERFORMED,
null));
}
}
}
}