/* * Jajuk * Copyright (C) The Jajuk Team * http://jajuk.info * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package org.jajuk.ui.views; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.MenuElement; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableColumn; import net.miginfocom.swing.MigLayout; import org.jajuk.base.Device; import org.jajuk.base.Directory; import org.jajuk.base.DirectoryManager; import org.jajuk.base.File; import org.jajuk.base.FileManager; import org.jajuk.base.Item; import org.jajuk.base.Playlist; import org.jajuk.base.Playlist.Type; import org.jajuk.events.JajukEvent; import org.jajuk.events.JajukEvents; import org.jajuk.events.ObservationManager; import org.jajuk.services.players.QueueModel; import org.jajuk.services.players.StackItem; import org.jajuk.ui.actions.ActionManager; import org.jajuk.ui.actions.JajukAction; import org.jajuk.ui.actions.JajukActions; import org.jajuk.ui.helpers.ILaunchCommand; import org.jajuk.ui.helpers.JajukMouseAdapter; import org.jajuk.ui.helpers.JajukTableModel; import org.jajuk.ui.helpers.PlayHighlighterPredicate; import org.jajuk.ui.helpers.PlaylistEditorTransferHandler; import org.jajuk.ui.helpers.PlaylistRepositoryTableModel; import org.jajuk.ui.helpers.PlaylistTableModel; import org.jajuk.ui.helpers.PreferencesJMenu; import org.jajuk.ui.widgets.InformationJPanel; import org.jajuk.ui.widgets.JajukButton; import org.jajuk.ui.widgets.JajukJSplitPane; import org.jajuk.ui.widgets.JajukJToolbar; import org.jajuk.ui.widgets.JajukTable; import org.jajuk.ui.widgets.SmartPlaylistView; import org.jajuk.util.Conf; import org.jajuk.util.Const; import org.jajuk.util.IconLoader; import org.jajuk.util.JajukIcons; import org.jajuk.util.Messages; import org.jajuk.util.UtilFeatures; import org.jajuk.util.UtilGUI; import org.jajuk.util.UtilSystem; import org.jajuk.util.error.JajukException; import org.jajuk.util.log.Log; import org.jdesktop.swingx.decorator.ColorHighlighter; /** * Playlist view. This views contains two parts : the "repository" selection panel (list of smart playlists icons * + the list of playlists table) and the playlist editor table. */ public class PlaylistView extends ViewAdapter implements ActionListener, ListSelectionListener { /** Generated serialVersionUID. */ private static final long serialVersionUID = -2851288035506442507L; // --Editor-- /** The playlist editor panel. */ private JPanel jpEditor; /** The editor controls panel. */ JPanel jpEditorControl; /** Button to run the playlist. */ private JajukButton jbRun; /** Button to save the current playlist. */ JajukButton jbSave; /** Button to remove the current item from the current playlist. */ JajukButton jbRemove; /** Button to push the current item sooner in the playlist. */ JajukButton jbUp; /** Button to drop down the current item in the curren tplaylist. */ JajukButton jbDown; /** Add a shuffle track button. */ JajukButton jbAddShuffle; /** Shuffle playlist button. */ JajukButton jbShufflePlaylist; /** Button to clear the current playlist. */ JajukButton jbClear; /** Title displayed in the editor part. */ JLabel jlTitle; /** Editor table. */ JajukTable editorTable; /** Menu item : play. */ JMenuItem jmiFilePlay; /** Menu item : remove. */ JMenuItem jmiFileRemove; /** Menu item : up. */ JMenuItem jmiFileUp; /** Menu item : down. */ JMenuItem jmiFileDown; /** Menu item : Open with Explorer. */ JMenuItem jmiOpenExplorer; /** Menu item : delete. */ JMenuItem jmiDelete; /** Current playlist. */ Playlist plf; /** Editor Model. */ protected PlaylistTableModel editorModel; /** Preference menu for current item. */ PreferencesJMenu pjmFilesEditor; // --- Repository --- /** Repository panel. */ private PlaylistRepository repositoryPanel; /** "New" smart playlist. */ private SmartPlaylistView spNew; /** Selected smart playlist. */ private SmartPlaylistView spSelected; /** Mouse adapter for smart playlist items. */ private MouseAdapter ma = new JajukMouseAdapter() { private static final long serialVersionUID = 1L; @Override public void handleAction(final MouseEvent e) { SmartPlaylistView sp = (SmartPlaylistView) e.getComponent(); if (sp == spSelected) { List<File> files; try { files = sp.getPlaylist().getFiles(); } catch (JajukException e1) { Log.error(e1); return; } if ((files == null) || (files.size() == 0)) { Messages.showErrorMessage(18); } else { QueueModel.push( UtilFeatures.createStackItems(UtilFeatures.applyPlayOption(files), Conf.getBoolean(Const.CONF_STATE_REPEAT), true), false); } } else { // user changed of smart playlist selection selectSmartPlaylist(sp); } } @Override public void handlePopup(final MouseEvent e) { SmartPlaylistView sp = (SmartPlaylistView) e.getComponent(); if (sp == spSelected) { // right click showSmartMenu(e); } else { selectSmartPlaylist(sp); showSmartMenu(e); } } /** * Display the playlist menu */ private void showSmartMenu(MouseEvent e) { // We use for smart playlists panels the same popup menu than the one from // the repository table // but we disable some items like delete or properties // Add generic menus JPopupMenu menu = new JPopupMenu(); JMenuItem jmiPlay = new JMenuItem(ActionManager.getAction(JajukActions.PLAY_SELECTION)); JMenuItem jmiFrontPush = new JMenuItem( ActionManager.getAction(JajukActions.PUSH_FRONT_SELECTION)); JMenuItem jmiPush = new JMenuItem(ActionManager.getAction(JajukActions.PUSH_SELECTION)); JMenuItem jmiPlayRepeat = new JMenuItem( ActionManager.getAction(JajukActions.PLAY_REPEAT_SELECTION)); JMenuItem jmiPlayShuffle = new JMenuItem( ActionManager.getAction(JajukActions.PLAY_SHUFFLE_SELECTION)); JMenuItem jmiPrepareParty = new JMenuItem(ActionManager.getAction(JajukActions.PREPARE_PARTY)); JMenuItem jmiRepositorySaveAs = new JMenuItem(ActionManager.getAction(JajukActions.SAVE_AS)); menu.add(jmiPlay); menu.add(jmiFrontPush); menu.add(jmiPush); menu.add(jmiPlayRepeat); menu.add(jmiPlayShuffle); menu.addSeparator(); menu.add(jmiPrepareParty); menu.add(jmiRepositorySaveAs); for (MenuElement item : menu.getSubElements()) { ((JComponent) item).putClientProperty(Const.DETAIL_SELECTION, spSelected.getPlaylist()); } menu.show(e.getComponent(), e.getX(), e.getY()); } }; /** * Select smart playlist. * * @param sp the smart playlist */ private void selectSmartPlaylist(SmartPlaylistView sp) { // remove table selection so an event will be thrown if user click on the table repositoryPanel.jtable.getSelectionModel().clearSelection(); // remove item border if (spSelected != null) { spSelected.getIcon().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); } sp.getIcon().setBorder(BorderFactory.createLineBorder(Color.ORANGE, 5)); // set new item spSelected = sp; // Update playlist editor selectPlaylist(sp.getPlaylist()); } /** * Return the editor table. * * @return the editor table */ public JajukTable getTable() { return this.editorTable; } /** * Inits the editor panel. */ private void initEditorPanel() { jpEditor = new JPanel(); // Control panel jpEditorControl = new JPanel(); jpEditorControl.setBorder(BorderFactory.createEtchedBorder()); jbRun = new JajukButton(IconLoader.getIcon(JajukIcons.RUN)); jbRun.setToolTipText(Messages.getString("AbstractPlaylistEditorView.2")); jbRun.addActionListener(this); jbSave = new JajukButton(IconLoader.getIcon(JajukIcons.SAVE)); jbSave.setToolTipText(Messages.getString("AbstractPlaylistEditorView.3")); jbSave.addActionListener(this); jbRemove = new JajukButton(IconLoader.getIcon(JajukIcons.REMOVE)); jbRemove.setToolTipText(Messages.getString("AbstractPlaylistEditorView.5")); jbRemove.addActionListener(this); jbUp = new JajukButton(IconLoader.getIcon(JajukIcons.UP)); jbUp.setToolTipText(Messages.getString("AbstractPlaylistEditorView.6")); jbUp.addActionListener(this); jbDown = new JajukButton(IconLoader.getIcon(JajukIcons.DOWN)); jbDown.setToolTipText(Messages.getString("AbstractPlaylistEditorView.7")); jbDown.addActionListener(this); jbAddShuffle = new JajukButton(IconLoader.getIcon(JajukIcons.ADD_SHUFFLE)); jbAddShuffle.setToolTipText(Messages.getString("AbstractPlaylistEditorView.10")); jbAddShuffle.addActionListener(this); jbShufflePlaylist = new JajukButton(IconLoader.getIcon(JajukIcons.SHUFFLE_GLOBAL_16X16)); jbShufflePlaylist.setToolTipText(Messages.getString("AbstractPlaylistEditorView.30")); jbShufflePlaylist.addActionListener(this); jbClear = new JajukButton(IconLoader.getIcon(JajukIcons.CLEAR)); jbClear.setToolTipText(Messages.getString("AbstractPlaylistEditorView.9")); jbClear.addActionListener(this); jlTitle = new JLabel(""); JToolBar jtb = new JajukJToolbar(); // Add items jpEditorControl.setLayout(new MigLayout("ins 0", "[][grow][]")); jtb.add(jbRun); jtb.add(jbSave); jtb.add(jbRemove); jtb.add(jbAddShuffle); jtb.add(jbShufflePlaylist); jtb.add(jbUp); jtb.add(jbDown); jtb.addSeparator(); jtb.add(jbClear); jpEditorControl.add(jtb, "left,gapright 5::"); jpEditorControl.add(jlTitle, "center"); editorModel = new PlaylistTableModel(false); editorTable = new JajukTable(editorModel, CONF_PLAYLIST_EDITOR_COLUMNS); editorModel.populateModel(editorTable.getColumnsConf()); editorTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // multi-row // selection editorTable.setSortable(false); editorTable.setTransferHandler(new PlaylistEditorTransferHandler(editorTable)); setRenderers(); // just an icon editorTable.getColumnModel().getColumn(0).setPreferredWidth(20); editorTable.getColumnModel().getColumn(0).setMaxWidth(20); editorTable.getTableHeader().setPreferredSize(new Dimension(0, 20)); editorTable.showColumns(editorTable.getColumnsConf()); // see JajukTable code, all the event code is centralized over there editorTable.addListSelectionListener(this); jpEditor.setLayout(new MigLayout("ins 0", "[grow]")); jpEditor.add(jpEditorControl, "growx,wrap"); JScrollPane jsp = new JScrollPane(editorTable); jpEditor.add(jsp, "growx"); initMenuItems(); ColorHighlighter colorHighlighter = new ColorHighlighter(new PlayHighlighterPredicate( editorTable), Color.ORANGE, null); editorTable.addHighlighter(colorHighlighter); // register events ObservationManager.register(this); // -- force a refresh -- // Add key listener to enable row suppression using SUPR key editorTable.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { // The fact that a selection can be removed or not is // in the jbRemove state if (e.getKeyCode() == KeyEvent.VK_DELETE && jbRemove.isEnabled()) { removeSelection(); // Refresh table refreshCurrentPlaylist(); } } }); // Add specific behavior on left click editorTable.setCommand(new ILaunchCommand() { @Override public void launch(int nbClicks) { int iSelectedCol = editorTable.getSelectedColumn(); // Convert column selection as columns may have been moved iSelectedCol = editorTable.convertColumnIndexToModel(iSelectedCol); // double click, launches selected track and all after if (nbClicks == 2 // click on play icon || (nbClicks == 1 && iSelectedCol == 0)) { StackItem item = editorModel.getStackItem(editorTable.getSelectedRow()); if (item != null) { // We launch all tracks from this // position // to the end of playlist QueueModel.push(editorModel.getItemsFrom(editorTable.getSelectedRow()), Conf.getBoolean(Const.CONF_OPTIONS_PUSH_ON_CLICK)); } } } }); } /** * This factorizes edit panel code between regular playlist view and queue * view for all menu items except the play that is queue-specific. */ void initMenuItems() { jmiFilePlay = new JMenuItem(ActionManager.getAction(JajukActions.PLAY_SELECTION)); jmiFilePlay.putClientProperty(Const.DETAIL_SELECTION, editorTable.getSelection()); JMenuItem jmiFileFrontPush = new JMenuItem( ActionManager.getAction(JajukActions.PUSH_FRONT_SELECTION)); jmiFileFrontPush.putClientProperty(Const.DETAIL_SELECTION, editorTable.getSelection()); JMenuItem jmiFilePush = new JMenuItem(ActionManager.getAction(JajukActions.PUSH_SELECTION)); jmiFilePush.putClientProperty(Const.DETAIL_SELECTION, editorTable.getSelection()); JMenuItem jmiFileAddFavorites = new JMenuItem( ActionManager.getAction(JajukActions.BOOKMARK_SELECTION)); jmiFileAddFavorites.putClientProperty(Const.DETAIL_SELECTION, editorTable.getSelection()); JMenuItem jmiFileProperties = new JMenuItem( ActionManager.getAction(JajukActions.SHOW_PROPERTIES)); jmiFileProperties.putClientProperty(Const.DETAIL_SELECTION, editorTable.getSelection()); jmiFileRemove = new JMenuItem(Messages.getString("AbstractPlaylistEditorView.5"), IconLoader.getIcon(JajukIcons.REMOVE)); jmiFileRemove.addActionListener(this); jmiFileUp = new JMenuItem(Messages.getString("AbstractPlaylistEditorView.6"), IconLoader.getIcon(JajukIcons.UP)); jmiFileUp.addActionListener(this); jmiFileDown = new JMenuItem(Messages.getString("AbstractPlaylistEditorView.7"), IconLoader.getIcon(JajukIcons.DOWN)); jmiFileDown.addActionListener(this); pjmFilesEditor = new PreferencesJMenu(editorTable.getSelection()); JMenuItem jmiFileCopyURL = new JMenuItem( ActionManager.getAction(JajukActions.COPY_TO_CLIPBOARD)); jmiFileCopyURL.putClientProperty(Const.DETAIL_CONTENT, editorTable.getSelection()); jmiOpenExplorer = new JMenuItem(ActionManager.getAction(JajukActions.OPEN_EXPLORER)); jmiOpenExplorer.putClientProperty(Const.DETAIL_CONTENT, editorTable.getSelection()); jmiDelete = new JMenuItem(ActionManager.getAction(JajukActions.DELETE)); jmiDelete.putClientProperty(Const.DETAIL_SELECTION, editorTable.getSelection()); editorTable.getMenu().add(jmiFilePlay); editorTable.getMenu().add(jmiFileFrontPush); editorTable.getMenu().add(jmiFilePush); editorTable.getMenu().addSeparator(); editorTable.getMenu().add(jmiFileRemove); editorTable.getMenu().add(jmiFileUp); editorTable.getMenu().add(jmiFileDown); editorTable.getMenu().addSeparator(); editorTable.getMenu().add(jmiFileCopyURL); editorTable.getMenu().add(jmiOpenExplorer); editorTable.getMenu().add(jmiDelete); editorTable.getMenu().addSeparator(); editorTable.getMenu().add(pjmFilesEditor); editorTable.getMenu().add(jmiFileAddFavorites); editorTable.getMenu().addSeparator(); editorTable.getMenu().add(jmiFileProperties); } /* * (non-Javadoc) * * @see org.jajuk.ui.IView#display() */ @Override public void initUI() { initEditorPanel(); spNew = new SmartPlaylistView(Type.NEW); spNew.addMouseListener(ma); SmartPlaylistView spBestof = new SmartPlaylistView(Type.BESTOF); spBestof.addMouseListener(ma); SmartPlaylistView spNovelties = new SmartPlaylistView(Type.NOVELTIES); spNovelties.addMouseListener(ma); SmartPlaylistView spBookmark = new SmartPlaylistView(Type.BOOKMARK); spBookmark.addMouseListener(ma); JPanel jpSmartPlaylists = new JPanel(); jpSmartPlaylists.setLayout(new FlowLayout(FlowLayout.LEFT)); jpSmartPlaylists.add(spNew); jpSmartPlaylists.add(spBestof); jpSmartPlaylists.add(spNovelties); jpSmartPlaylists.add(spBookmark); JPanel jpRepository = new JPanel(new MigLayout("ins 0", "[grow]")); repositoryPanel = new PlaylistRepository(); repositoryPanel.initUI(); repositoryPanel.setPerspective(getPerspective()); jpRepository.add(jpSmartPlaylists, "growx,wrap"); jpRepository.add(repositoryPanel, "growx"); JajukJSplitPane split = new JajukJSplitPane(JSplitPane.VERTICAL_SPLIT); split.setDividerLocation(0.5d); split.add(jpRepository); split.add(jpEditor); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); add(split); // Register keystrokes over table setKeystrokes(); } /** * Add keystroke support. */ protected void setKeystrokes() { // Bind keystrokes and selection editorTable.putClientProperty(Const.DETAIL_SELECTION, editorTable.getSelection()); InputMap inputMap = editorTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); ActionMap actionMap = editorTable.getActionMap(); // Properties ALT/ENTER JajukAction action = ActionManager.getAction(JajukActions.SHOW_PROPERTIES); inputMap.put(KeyStroke.getKeyStroke("alt ENTER"), "properties"); actionMap.put("properties", action); } /* * (non-Javadoc) * * @see org.jajuk.events.Observer#getRegistrationKeys() */ @Override public Set<JajukEvents> getRegistrationKeys() { Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>(); eventSubjectSet.add(JajukEvents.CUSTOM_PROPERTIES_ADD); eventSubjectSet.add(JajukEvents.CUSTOM_PROPERTIES_REMOVE); eventSubjectSet.add(JajukEvents.DEVICE_MOUNT); eventSubjectSet.add(JajukEvents.DEVICE_UNMOUNT); eventSubjectSet.add(JajukEvents.DEVICE_REFRESH); eventSubjectSet.add(JajukEvents.VIEW_REFRESH_REQUEST); eventSubjectSet.add(JajukEvents.QUEUE_NEED_REFRESH); eventSubjectSet.add(JajukEvents.PARAMETERS_CHANGE); eventSubjectSet.add(JajukEvents.RATE_CHANGED); return eventSubjectSet; } /* * (non-Javadoc) * * @see org.jajuk.ui.IView#getDesc() */ @Override public String getDesc() { return Messages.getString("AbstractPlaylistEditorView.15"); } /** * Sets the renderers. */ void setRenderers() { // set right cell renderer for play and rate icons // Play icon TableColumn col = editorTable.getColumnModel().getColumn(0); col.setMinWidth(PLAY_COLUMN_SIZE); col.setMaxWidth(PLAY_COLUMN_SIZE); // rate col = editorTable.getColumnModel().getColumn(5); col.setMinWidth(RATE_COLUMN_SIZE); col.setMaxWidth(RATE_COLUMN_SIZE); } /* * (non-Javadoc) * * @see org.jajuk.ui.Observer#update(java.lang.String) */ @Override public void update(final JajukEvent event) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { JajukEvents subject = event.getSubject(); editorTable.setAcceptColumnsEvents(false); // flag reloading to avoid // wrong // column changed of playlist // current playlist has changed if (JajukEvents.DEVICE_REFRESH.equals(subject) // We listen this event to paint the new running track in table || JajukEvents.QUEUE_NEED_REFRESH.equals(subject) || JajukEvents.RATE_CHANGED.equals(subject) || JajukEvents.DEVICE_MOUNT.equals(subject) || JajukEvents.DEVICE_UNMOUNT.equals(subject) || JajukEvents.PARAMETERS_CHANGE.equals(subject)) { refreshCurrentPlaylist(); } else if (JajukEvents.CUSTOM_PROPERTIES_ADD.equals(subject)) { Properties properties = event.getDetails(); if (properties == null) { // can be null at view populate return; } // create a new model editorModel = new PlaylistTableModel(false); editorModel.populateModel(editorTable.getColumnsConf()); editorTable.setModel(editorModel); setRenderers(); editorTable.addColumnIntoConf((String) properties.get(Const.DETAIL_CONTENT)); editorTable.showColumns(editorTable.getColumnsConf()); } else if (JajukEvents.CUSTOM_PROPERTIES_REMOVE.equals(subject)) { Properties properties = event.getDetails(); if (properties == null) { // can be null at view // populate return; } editorModel = new PlaylistTableModel(false); editorModel.populateModel(editorTable.getColumnsConf()); editorTable.setModel(editorModel); setRenderers(); // remove item from configuration cols editorTable.removeColumnFromConf((String) properties.get(Const.DETAIL_CONTENT)); editorTable.showColumns(editorTable.getColumnsConf()); } else if (JajukEvents.VIEW_REFRESH_REQUEST.equals(subject)) { // force filter to refresh if the events has been triggered by the // table itself after a column change JTable table = (JTable) event.getDetails().get(Const.DETAIL_CONTENT); if (table.equals(editorTable)) { refreshCurrentPlaylist(); } } } catch (Exception e) { Log.error(e); } finally { editorTable.setAcceptColumnsEvents(true); } } }); } /** * Refresh current playlist. */ private void refreshCurrentPlaylist() { if (plf == null) { // nothing ? leave return; } List<File> files = null; // Try to get playlist content try { files = plf.getFiles(); // When nothing is selected, set default button state if (editorTable.getSelectionModel().getMinSelectionIndex() == -1) { setButtonState(); } editorModel.setItems(UtilFeatures.createStackItems(files, Conf.getBoolean(Const.CONF_STATE_REPEAT), true)); editorModel.populateModel(editorTable.getColumnsConf()); int[] rows = editorTable.getSelectedRows(); try { editorModel.setRefreshing(true); // Force table refreshing editorModel.fireTableDataChanged(); // Save selection for (int element : rows) { // set saved selection after a refresh editorTable.getSelectionModel().addSelectionInterval(element, element); } } finally { editorModel.setRefreshing(false); } // Refresh the preference menu according to the selection // (useful on rating change for a single-row model for ie) pjmFilesEditor.resetUI(editorTable.getSelection()); } catch (JajukException je) { Log.warn("Cannot parse playlist : " + plf.getAbsolutePath()); // Clear the model so we don't keep previous playlist tracks editorModel.clear(); } } /** * Select playlist. * * @param plf the playlist (smart or not) */ private void selectPlaylist(Playlist plf) { // remove selection editorTable.getSelectionModel().clearSelection(); PlaylistView.this.plf = plf; // set title label jlTitle.setText(plf.getName()); if (plf.getType() == Playlist.Type.BESTOF) { jlTitle.setIcon(IconLoader.getIcon(JajukIcons.BESTOF_16X16)); } else if (plf.getType() == Playlist.Type.BOOKMARK) { jlTitle.setIcon(IconLoader.getIcon(JajukIcons.PLAYLIST_BOOKMARK_SMALL)); } else if (plf.getType() == Playlist.Type.NEW) { jlTitle.setIcon(IconLoader.getIcon(JajukIcons.PLAYLIST_NEW_SMALL)); } else if (plf.getType() == Playlist.Type.NOVELTIES) { jlTitle.setIcon(IconLoader.getIcon(JajukIcons.NOVELTIES_16X16)); } else { // remove last smart playlist item border if (spSelected != null) { spSelected.getIcon().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); spSelected = null; } jlTitle.setIcon(IconLoader.getIcon(JajukIcons.PLAYLIST_FILE)); } jlTitle.setToolTipText(plf.getName()); setButtonState(); refreshCurrentPlaylist(); updatePlaylistMenuItems(); UtilGUI.stopWaiting(); // stop waiting } /** * Update buttons state. */ private void setButtonState() { try { if (plf == null) { jbRun.setEnabled(false); jbClear.setEnabled(false); jbDown.setEnabled(false); jbAddShuffle.setEnabled(false); jbShufflePlaylist.setEnabled(false); jbRemove.setEnabled(false); jbUp.setEnabled(false); jbSave.setEnabled(false); } else { if (plf.getType() == Playlist.Type.BESTOF || plf.getType() == Playlist.Type.NOVELTIES) { jbClear.setEnabled(false); jbDown.setEnabled(false); jbAddShuffle.setEnabled(false); jbShufflePlaylist.setEnabled(false); jbRemove.setEnabled(false); jbUp.setEnabled(false); } else if (plf.getType() == Playlist.Type.BOOKMARK) { jbClear.setEnabled(true); jbDown.setEnabled(false); jbAddShuffle.setEnabled(false); jbShufflePlaylist.setEnabled(false); jbRemove.setEnabled(false); jbUp.setEnabled(false); } else { jbClear.setEnabled(true); // set it to false just for startup because nothing is selected jbDown.setEnabled(false); // set it to false just for startup because nothing is selected jbUp.setEnabled(false); // add at the FIFO end by default even with no selection jbAddShuffle.setEnabled(true); jbShufflePlaylist.setEnabled(true); // set it to false just for startup because nothing is selected jbRemove.setEnabled(false); } // Run button is available only if the playlist is not void jbRun.setEnabled(plf.isReady() && plf.getFiles().size() > 0); } } catch (Exception e) { Log.error(e); } } /* * (non-Javadoc) * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ @Override public void actionPerformed(ActionEvent ae) { try { if (ae.getSource() == jbRun) { plf.play(); } else if (ae.getSource() == jbSave) { // normal playlist if (plf.getType() == Playlist.Type.NORMAL) { try { plf.commit(); InformationJPanel.getInstance().setMessage( Messages.getString("AbstractPlaylistEditorView.22"), InformationJPanel.MessageType.INFORMATIVE); } catch (JajukException je) { Log.error(je); Messages.showErrorMessage(je.getCode()); } } else { // "Save as" for any other playlist type new Thread("Playlist Action Thread") { @Override public void run() { UtilGUI.waiting(); try { // special playlist, same behavior than a save as plf.saveAs(); // If the new playlist is saved in a known device location, // force a refresh to make it visible immediately (issue #1263) Device knownDevice = UtilSystem.getDeviceForFio(plf.getFIO()); if (knownDevice != null) { Directory directory = DirectoryManager.getInstance().getDirectoryForIO( plf.getFIO().getParentFile(), knownDevice); directory.refresh(false); // Force a table refresh to show the new playlist if it has // been saved in a known device ObservationManager.notify(new JajukEvent(JajukEvents.DEVICE_REFRESH)); } } catch (JajukException je) { Log.error(je); Messages.showErrorMessage(je.getCode()); } catch (Exception e) { Log.error(e); Messages.showErrorMessage(0, e.getMessage()); } finally { UtilGUI.stopWaiting(); } } }.start(); } } else if (ae.getSource() == jbClear) { // if it is the queue playlist, stop the selection plf.clear(); } else if (ae.getSource() == jbDown || ae.getSource() == jbUp || ae.getSource() == jmiFileDown || ae.getSource() == jmiFileUp) { int iRow = editorTable.getSelectedRow(); if (iRow != -1) { // -1 means nothing is selected if (ae.getSource() == jbDown || ae.getSource() == jmiFileDown) { plf.down(iRow); if (iRow < editorTable.getModel().getRowCount() - 1) { editorTable.getSelectionModel().setSelectionInterval(iRow + 1, iRow + 1); } } else if (ae.getSource() == jbUp || ae.getSource() == jmiFileUp) { plf.up(iRow); if (iRow > 0) { editorTable.getSelectionModel().setSelectionInterval(iRow - 1, iRow - 1); } } } } else if (ae.getSource() == jbRemove || ae.getSource() == jmiFileRemove) { removeSelection(); } else if (ae.getSource() == jbAddShuffle) { int iRow = editorTable.getSelectedRow(); if (iRow < 0 || iRow > editorTable.getRowCount()) { // no or invalid row is selected, add to the end iRow = editorTable.getRowCount(); } File file = FileManager.getInstance().getShuffleFile(); try { plf.addFile(iRow, file); jbRemove.setEnabled(true); } catch (JajukException je) { Messages.showErrorMessage(je.getCode()); Log.error(je); } } else if (ae.getSource() == jbShufflePlaylist) { List<File> files = plf.getFiles(); Collections.shuffle(files); editorModel.fireTableDataChanged(); } } catch (JajukException e2) { Log.error(e2); } finally { // force immediate table refresh refreshCurrentPlaylist(); } } /** * Import files, used when drag / dropping for ie. * * @param files files to be imported * @param position */ public void importFiles(List<File> files, int position) { plf.addFiles(UtilFeatures.applyPlayOption(files), position); refreshCurrentPlaylist(); } /** * Removes the selection. */ private void removeSelection() { int[] selectedRows = editorTable.getSelectedRows(); if (selectedRows.length > 1) {// if multiple selection, remove // selection editorTable.getSelectionModel().removeIndexInterval(0, editorTable.getRowCount() - 1); } for (int i = 0; i < selectedRows.length; i++) { // don't forget that index changes when removing plf.remove(selectedRows[i] - i); } // set selection to last line if end reached int lastRow = editorTable.getRowCount() - 1; if (selectedRows[0] == lastRow && editorTable.getRowCount() > 0) { editorTable.getSelectionModel().setSelectionInterval(lastRow - 1, lastRow - 1); } } /** * Gets the current playlist. * * @return Returns current playlist */ public Playlist getCurrentPlaylist() { return plf; } /** * Called when table selection changed. * * @param e */ @Override public void valueChanged(ListSelectionEvent e) { ListSelectionModel selection = (ListSelectionModel) e.getSource(); if (!selection.isSelectionEmpty()) { updateInformationPanel(getSelectedFiles()); // Refresh the preference menu according to the selection pjmFilesEditor.resetUI(editorTable.getSelection()); // -- now analyze each button -- // Remove button // check for first row remove case : we can't remove currently // played track if (plf.getType() == Playlist.Type.BESTOF || plf.getType() == Playlist.Type.NOVELTIES) { // neither for bestof nor novelties playlist jbRemove.setEnabled(false); } else { jbRemove.setEnabled(true); } // Add shuffle button if (plf.getType() == Playlist.Type.BESTOF // neither for bestof playlist || plf.getType() == Playlist.Type.NOVELTIES || plf.getType() == Playlist.Type.BOOKMARK || selection.getMinSelectionIndex() != selection.getMaxSelectionIndex() // multiple selection not supported ) { jbAddShuffle.setEnabled(false); } else { jbAddShuffle.setEnabled(true); } // Up button if (selection.getMinSelectionIndex() != selection.getMaxSelectionIndex() // check if several rows have been selected : // doesn't supported yet || plf.getType() == Playlist.Type.BESTOF || plf.getType() == Playlist.Type.NOVELTIES || plf.getType() == Playlist.Type.BOOKMARK) { // neither for bestof nor novelties playlist jbUp.setEnabled(false); } else { // still here ? if (selection.getMinSelectionIndex() == 0) { // check if we selected second track just after current // tracks jbUp.setEnabled(false); // already at the top } else { jbUp.setEnabled(true); } } // Down button if (selection.getMinSelectionIndex() != selection.getMaxSelectionIndex() // check if several rows have been selected : // doesn't supported yet || plf.getType() == Playlist.Type.BESTOF || plf.getType() == Playlist.Type.NOVELTIES || plf.getType() == Playlist.Type.BOOKMARK) { jbDown.setEnabled(false); } else { // yet here ? if (selection.getMaxSelectionIndex() < editorModel.getItems().size() - 1) { // a normal item can't go in the planned items jbDown.setEnabled(true); } else { jbDown.setEnabled(false); } } } } /** * Update the Information View with selection size. * * @param files : selection to consider */ void updateInformationPanel(List<File> files) { // Update information view // Compute recursive selection size, nb of items... long lSize = 0l; for (File file : files) { lSize += file.getSize(); } lSize /= 1048576; // set size in MB StringBuilder sbOut = new StringBuilder().append(files.size()).append( Messages.getString("FilesTreeView.52")); if (lSize > 1024) { // more than 1024 MB -> in GB sbOut.append(lSize / 1024).append('.').append(lSize % 1024) .append(Messages.getString("FilesTreeView.53")); } else { sbOut.append(lSize).append(Messages.getString("FilesTreeView.54")); } InformationJPanel.getInstance().setSelection(sbOut.toString()); } /** * Get selection as files. * * @return selected files */ List<File> getSelectedFiles() { List<File> selectedFiles = new ArrayList<File>(10); for (Item item : editorTable.getSelection()) { File file = (File) item; selectedFiles.add(file); } return selectedFiles; } /** * Disables some tracks menu items if in smart playlist. */ private void updatePlaylistMenuItems() { final boolean isReadOnly = (plf.getType() == Playlist.Type.BESTOF || plf.getType() == Playlist.Type.NOVELTIES || plf.getType() == Playlist.Type.BOOKMARK); jmiFileDown.setEnabled(!isReadOnly); jmiFileUp.setEnabled(!isReadOnly); jmiFileRemove.setEnabled(!isReadOnly); } /** * This class is not a view but the playlist upper panel of the PlaylistView * It leverages the Abstract Playlist code (filters...) */ private class PlaylistRepository extends AbstractTableView { /** Generated serialVersionUID. */ private static final long serialVersionUID = 3842568503545896845L; private JMenuItem jmiRepositorySaveAs; private JMenuItem jmiPrepareParty; /** List of playlists for which we already displayed a warning message if it contains old or external entries. */ private final List<Playlist> alreadyWarned = new ArrayList<Playlist>(10); /** * Instantiates a new playlist repository. */ public PlaylistRepository() { super(); columnsConf = CONF_PLAYLIST_REPOSITORY_COLUMNS; } @Override void initTable() { // required by abstract superclass, but nothing to do here... } @Override JajukTableModel populateTable() { return new PlaylistRepositoryTableModel(); } @Override public String getDesc() { return null; } /** * Override this method to make sure to provide a non-null view ID when * required. * * @return the ID */ @Override public String getID() { return PlaylistView.this.getID() + "/PlaylistRepository"; } @Override public void initUI() { UtilGUI.populate(this); } @Override public void shortCall(Object in) { jtable = new JajukTable(model, true, columnsConf); super.shortCall(null); jmiRepositorySaveAs = new JMenuItem(ActionManager.getAction(JajukActions.SAVE_AS)); jmiRepositorySaveAs.putClientProperty(Const.DETAIL_SELECTION, jtable.getSelection()); jmiPrepareParty = new JMenuItem(ActionManager.getAction(JajukActions.PREPARE_PARTY)); jmiPrepareParty.putClientProperty(Const.DETAIL_SELECTION, jtable.getSelection()); pjmTracks = new PreferencesJMenu(jtable.getSelection()); jmiOpenExplorer = new JMenuItem(ActionManager.getAction(JajukActions.OPEN_EXPLORER)); jmiOpenExplorer.putClientProperty(Const.DETAIL_CONTENT, jtable.getSelection()); jtable.getMenu().add(jmiOpenExplorer); jtable.getMenu().add(jmiPrepareParty); jtable.getMenu().add(jmiRepositorySaveAs); jtable.getMenu().addSeparator(); jtable.getMenu().add(pjmTracks); jtable.getMenu().addSeparator(); // Add this generic menu item manually to ensure it's the last one in // the list for GUI reasons jtable.getMenu().add(jmiProperties); jtbEditable.setVisible(false); jtbSync.setVisible(false); // Select "New" playlist as default selectSmartPlaylist(spNew); } @Override public Object longCall() { super.longCall(); return null; } @Override void onSelectionChange() { Playlist playlist = null; List<File> files = null; try { int selectedRow = jtable.getSelectedRow(); if (selectedRow < 0) { return; } int row = jtable.convertRowIndexToModel(selectedRow); JajukTableModel model = (JajukTableModel) jtable.getModel(); playlist = (Playlist) model.getItemAt(row); // load the playlist files = playlist.getFiles(); if (!alreadyWarned.contains(playlist) && playlist.containsExtFiles()) { Messages.showWarningMessage(Messages.getErrorMessage(142)); alreadyWarned.add(playlist); } } catch (JajukException e1) { // Display a warning if the playlist is not parsable but still select it Log.warn("Cannot parse playlist : " + plf.getAbsolutePath()); } // Select the playlist even if it cannot be read (we still have some titles) selectPlaylist(playlist); if (files != null) { updateInformationPanel(files); } else { // Reset selection tip InformationJPanel.getInstance().setSelection(Messages.getString("InformationJPanel.9")); } } } }