package com.limegroup.gnutella.gui.playlist; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import javax.swing.AbstractButton; import javax.swing.Box; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.filechooser.FileFilter; import com.limegroup.gnutella.gui.FileChooserHandler; import com.limegroup.gnutella.gui.GUIConstants; import com.limegroup.gnutella.gui.GUIMediator; import com.limegroup.gnutella.gui.PaddedPanel; import com.limegroup.gnutella.gui.mp3.MediaPlayerComponent; import com.limegroup.gnutella.gui.mp3.PlayList; import com.limegroup.gnutella.gui.tables.AbstractTableMediator; import com.limegroup.gnutella.gui.tables.DataLine; import com.limegroup.gnutella.gui.tables.DragManager; import com.limegroup.gnutella.gui.tables.FileTransfer; import com.limegroup.gnutella.gui.tables.LimeJTable; import com.limegroup.gnutella.gui.themes.ThemeMediator; import com.limegroup.gnutella.util.CommonUtils; import com.limegroup.gnutella.util.FileUtils; /** * This class acts as a mediator between all of the components of the * playlist table. */ public final class PlaylistMediator extends AbstractTableMediator { /** * Instance of singleton access */ private static final PlaylistMediator INSTANCE = new PlaylistMediator(); public static PlaylistMediator instance() { return INSTANCE; } /** * A lock to use for access to pl stuff. */ private final Object PLAY_LOCK = new Object(); /** * The active playlist. */ private PlayList _playList; /** * The temporary playlist - used when songs added but not yet saved. */ private PlayList _tempPL = new PlayList(".temp.m3u.LW"); /** * Whether or not songs meld into each other. */ private boolean _continuous = true; /** * Whether or not a song is playing right now. */ private boolean _songPlaying = false; /** * The one-time song that will play next. */ private File _oneTimeSongToPlay = null; /** * The last playlist that was opened. */ private File _lastOpenedPlaylist; /** * The last playlist that was saved. */ private File _lastSavedPlaylist; /** * Listeners so buttons and possibly future right-click menu share. */ ActionListener LOAD_LISTENER; ActionListener SAVE_LISTENER; ActionListener CONTINUOUS_LISTENER; ActionListener SHUFFLE_LISTENER; /** * DATA_MODEL casted to a PlaylistModel so we don't have to do * lots of casts. */ private PlaylistModel MODEL; /** * Build the listeners */ protected void buildListeners() { super.buildListeners(); LOAD_LISTENER = new LoadListener(); SAVE_LISTENER = new SaveListener(); CONTINUOUS_LISTENER = new ContinuousListener(); SHUFFLE_LISTENER = new ShuffleListener(); } /** * Add the listeners */ protected void addListeners() { super.addListeners(); } /** * Set up the necessary constants. */ protected void setupConstants() { MAIN_PANEL = new PaddedPanel(GUIMediator.getStringResource("PLAYLIST_TITLE")); DATA_MODEL = MODEL = new PlaylistModel(); TABLE = new LimeJTable(DATA_MODEL); BUTTON_ROW = (new PlaylistButtons(this)).getComponent(); } /** * Update the splash screen */ protected void updateSplashScreen() { GUIMediator.setSplashScreenString( GUIMediator.getStringResource("SPLASH_STATUS_PLAYLIST_WINDOW")); } /** * Constructor -- private for Singleton access */ private PlaylistMediator() { super("PLAYLIST_TABLE"); ThemeMediator.addThemeObserver(this); } // inherit doc comment protected JPopupMenu createPopupMenu() { return null; } /** * Builds the main panel, with checkboxes next to the buttons. */ protected void setupMainPanel() { JPanel jp = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1; gbc.weighty = 1; gbc.fill = GridBagConstraints.BOTH; jp.add(getScrolledTablePane(), gbc); gbc.gridy = 1; gbc.weighty = 0; gbc.fill = GridBagConstraints.HORIZONTAL; jp.add(Box.createVerticalStrut(GUIConstants.SEPARATOR), gbc); gbc.gridy = 2; jp.add(buildOptionsPanel(), gbc); MAIN_PANEL.add(jp); MAIN_PANEL.setMinimumSize(ZERO_DIMENSION); } /** * Sets up dnd */ protected void setupDragAndDrop() { DragManager.install(TABLE); } /** * Handles the selection of the specified row in the connection window, * enabling or disabling buttons * * @param row the selected row */ public void handleSelection(int row) { setButtonEnabled( PlaylistButtons.REMOVE_BUTTON, true ); } /** * Handles the deselection of all rows in the download table, * disabling all necessary buttons and menu items. */ public void handleNoSelection() { setButtonEnabled( PlaylistButtons.REMOVE_BUTTON, false ); } /** * Plays the currently selected song. */ public void handleActionKey() { playSong(); } /** * Returns the next file to play. */ public File getFileToPlay() { File retFile = null; synchronized(PLAY_LOCK) { if (_oneTimeSongToPlay != null) { retFile = _oneTimeSongToPlay; _oneTimeSongToPlay = null; return retFile; } if(!isContinuous()) return null; retFile = MODEL.getNextSong(); _songPlaying = true; } GUIMediator.safeInvokeAndWait(new Runnable() { public void run() { synchronized(PLAY_LOCK) { refresh(); // update the colors on the table. int playIndex = MODEL.getCurrentSongIndex(); if(playIndex >= 0) TABLE.ensureRowVisible(playIndex); } } }); return retFile; } /** * Adds a file to the playlist. */ public void addFileToPlaylist(final File f) { GUIMediator.safeInvokeAndWait(new Runnable() { public void run() { synchronized(PLAY_LOCK) { add(f); PlayList pl = getCurrentPlayList(); if(pl != null) pl.setSongs(MODEL.getSongs()); } } }); } /** * Adds a bunch of files to the playlist. */ public void addFilesToPlaylist(final File[] fs) { if(fs == null || fs.length == 0) return; GUIMediator.safeInvokeAndWait(new Runnable() { public void run() { synchronized(PLAY_LOCK) { for(int i = 0; i < fs.length; i++) addFileToPlaylist(fs[i]); } } }); } /** * Determines if a song is currently selected. */ public boolean isSongSelected() { return TABLE.getSelectedRow() != -1; } /** * Sets backwards mode on the playlist. */ public void setBackwardsMode() { MODEL.setBackwardsMode(); } /** * Whether or not a song is currently playing from the playlist. */ public boolean isSongPlaying() { return _songPlaying; } /** * Queues the song for next play. */ public void playSongNext(File f) { _oneTimeSongToPlay = f; } /** * Whether or not the list should repeat. */ public boolean isContinuous() { return _continuous; } /** * Notification that play started on a song. */ public void playStarted() { GUIMediator.safeInvokeAndWait(new Runnable() { public void run() { refresh(); // update the colors. } }); } /** * Notification that a song has stopped. 'hardStop' true * if the user manually pressed stop. */ public void playComplete(boolean hardStop) { if(!hardStop) return; _songPlaying = false; GUIMediator.safeInvokeAndWait(new Runnable() { public void run() { refresh(); // update the colors on the table. } }); } /** * Constructs the options panel. */ private JPanel buildOptionsPanel() { JLabel options = new JLabel( GUIMediator.getStringResource("PLAYLIST_OPTIONS_STRING")); JCheckBox shuffle = new JCheckBox( GUIMediator.getStringResource("PLAYLIST_OPTIONS_SHUFFLE"), false); shuffle.addActionListener(SHUFFLE_LISTENER); JCheckBox continuous = new JCheckBox( GUIMediator.getStringResource("PLAYLIST_OPTIONS_CONTINUE"), true); continuous.addActionListener(CONTINUOUS_LISTENER); JPanel checkBoxPanel = new JPanel(); checkBoxPanel.add(options); checkBoxPanel.add(continuous); checkBoxPanel.add(shuffle); JPanel optionsPanel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1; gbc.anchor = GridBagConstraints.WEST; optionsPanel.add(BUTTON_ROW, gbc); gbc.gridx = 1; gbc.weightx = 0; optionsPanel.add(checkBoxPanel, gbc); return optionsPanel; } /** * Determines if the file is playable on LimeWire's media player. */ public static boolean isPlayableFile(File f) { String ext = FileUtils.getFileExtension(f); if(ext == null) return false; ext = ext.toLowerCase(); return ext.equals("mp3") || ext.equals("ogg"); } /** * Plays the first selected item. */ private void playSong() { DataLine line = TABLE.getSelectedDataLine(); if(line == null) return; File f = ((FileTransfer)line).getFile(); MODEL.setCurrentSong(f); MediaPlayerComponent.playSongImmediately(f); _songPlaying = true; } /** * @return The playlist to use. May return null. */ private PlayList getCurrentPlayList() { return _playList != null ? _playList : _tempPL; } /** * Loads a playlist. */ private void loadPlaylist() { File parentFile = null; if (_lastOpenedPlaylist != null) { String parent = _lastOpenedPlaylist.getParent(); if(parent != null) parentFile = new File(parent); } if(parentFile == null) parentFile = CommonUtils.getCurrentDirectory(); final File selFile = FileChooserHandler.getInputFile(getComponent(), "PLAYLIST_DIALOG_OPEN_TITLE", parentFile, new PlayListFileFilter()); // nothing selected? exit. if(selFile == null || !selFile.isFile()) return; String path = selFile.getPath(); try { path = selFile.getCanonicalPath(); } catch(IOException ignored) {} PlayList pl = new PlayList(path); synchronized(PLAY_LOCK) { _lastOpenedPlaylist = selFile; _playList = pl; clearTable(); MODEL.addSongs(_playList); } } /** * Saves a playlist. */ private void savePlaylist() { // get the user to select a new one.... File suggested; if(_lastSavedPlaylist != null) suggested = _lastSavedPlaylist; else suggested = new File(CommonUtils.getCurrentDirectory(), "limewire.m3u"); File selFile = FileChooserHandler.getSaveAsFile( getComponent(), "PLAYLIST_DIALOG_SAVE_TITLE", suggested, new PlayListFileFilter()); // didn't select a file? nothing we can do. if(selFile == null) return; String path = selFile.getPath(); try { path = selFile.getCanonicalPath(); } catch(IOException ignored) {} // force m3u on the end. if(!path.toLowerCase().endsWith(".m3u")) path += ".m3u"; PlayList pl = new PlayList(path); synchronized(PLAY_LOCK) { _lastSavedPlaylist = new File(path); _playList = pl; _playList.setSongs(MODEL.getSongs()); _tempPL = null; } try { _playList.save(); } catch(IOException ignored) { //TODO: should message the user that it failed. } } /** * Listener that loads a playlist file. */ private class LoadListener implements ActionListener { public void actionPerformed(ActionEvent e) { loadPlaylist(); } } /** * Listener that saves a playlist file. */ private class SaveListener implements ActionListener { public void actionPerformed(ActionEvent e) { savePlaylist(); } } /** * Listener that toggles the 'continuous' setting. */ private class ContinuousListener implements ActionListener { public void actionPerformed(ActionEvent e) { AbstractButton b = (AbstractButton)e.getSource(); _continuous = b.isSelected(); } } /** * Listener that toggles the 'shuffle' setting. */ private class ShuffleListener implements ActionListener { public void actionPerformed(ActionEvent e) { AbstractButton b = (AbstractButton)e.getSource(); MODEL.setShuffle(b.isSelected()); } } /** * <tt>FileFilter</tt> class for only displaying m3u file types in * the directory chooser. */ private static class PlayListFileFilter extends FileFilter { public boolean accept(File f) { return f.isDirectory() || f.getName().toLowerCase().endsWith("m3u"); } public String getDescription() { return GUIMediator.getStringResource("PLAYLIST_FILE_DESCRIPTION"); } } }