package org.limewire.ui.swing.player; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.font.FontRenderContext; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.plaf.basic.BasicSliderUI; import net.miginfocom.swing.MigLayout; import org.jdesktop.application.Resource; import org.jdesktop.swingx.JXPanel; import org.jdesktop.swingx.painter.Painter; import org.limewire.player.api.PlayerState; import org.limewire.setting.evt.SettingEvent; import org.limewire.setting.evt.SettingListener; import org.limewire.ui.swing.components.IconButton; import org.limewire.ui.swing.components.LimeSliderBar; import org.limewire.ui.swing.components.MarqueeButton; import org.limewire.ui.swing.components.VolumeSlider; import org.limewire.ui.swing.components.decorators.SliderBarDecorator; import org.limewire.ui.swing.painter.ComponentBackgroundPainter; import org.limewire.ui.swing.painter.BorderPainter.AccentType; import org.limewire.ui.swing.settings.SwingUiSettings; import org.limewire.ui.swing.util.GuiUtils; import org.limewire.ui.swing.util.I18n; import org.limewire.ui.swing.util.ResizeUtils; import com.google.inject.Inject; import com.google.inject.Provider; /** * Main UI container for the media player. */ public class PlayerPanel extends JXPanel implements PlayerMediatorListener { @Resource private int arcWidth; @Resource private int arcHeight; @Resource private Color innerBorder; @Resource private Color innerBackground; @Resource private Color bevelLeft; @Resource private Color bevelTop1; @Resource private Color bevelTop2; @Resource private Color bevelRight; @Resource private Color bevelBottom; @Resource private Icon backIcon; @Resource private Icon backIconPressed; @Resource private Icon backIconRollover; @Resource private Icon backIconDisabled; @Resource private Icon forwardIcon; @Resource private Icon forwardIconPressed; @Resource private Icon forwardIconRollover; @Resource private Icon forwardIconDisabled; @Resource private Icon playIcon; @Resource private Icon playIconPressed; @Resource private Icon playIconRollover; @Resource private Icon playIconDisabled; @Resource private Icon pauseIcon; @Resource private Icon pauseIconPressed; @Resource private Icon pauseIconRollover; @Resource private Icon volumeIcon; @Resource private Icon volumeIconPressed; @Resource private Icon volumeIconRollover; @Resource private Icon volumeIconDisabled; @Resource private Icon shuffleIcon; @Resource private Icon shuffleIconPressed; @Resource private Icon shuffleIconRollover; @Resource private Icon shuffleIconActive; @Resource private Font font; private final JXPanel innerPanel; private final JButton backButton; private final JButton playButton; private final JButton pauseButton; private final JButton forwardButton; private final LimeSliderBar progressSlider; private final JPanel statusPanel; private final JButton volumeButton; private final JButton shuffleButton; private final JPopupMenu volumeControlPopup; private final VolumeSlider volumeSlider; private final MarqueeButton titleLabel; private static final String BACK = "BACK"; private static final String PLAY = "PLAY"; private static final String PAUSE = "PAUSE"; private static final String FORWARD = "FORWARD"; private static final String VOLUME = "VOLUME"; private static final String SHUFFLE = "SHUFFLE"; private final Provider<PlayerMediator> playerProvider; /** * Constructs a PlayerPanel with the specified component providers and * decorators. */ @Inject public PlayerPanel(Provider<PlayerMediator> playerProvider, SliderBarDecorator sliderBarDecorator) { this.playerProvider = playerProvider; GuiUtils.assignResources(this); setLayout(new MigLayout("insets 0, filly, alignx center")); setOpaque(false); final ButtonListener playerListener = new ButtonListener(); backButton = new IconButton(backIcon, backIconRollover, backIconPressed); backButton.addActionListener(playerListener); backButton.setActionCommand(BACK); backButton.setDisabledIcon(backIconDisabled); backButton.setToolTipText(I18n.tr("Skip Back")); playButton = new IconButton(playIcon, playIconRollover, playIconPressed); playButton.addActionListener(playerListener); playButton.setActionCommand(PLAY); playButton.setDisabledIcon(playIconDisabled); playButton.setToolTipText(I18n.tr("Play")); pauseButton = new IconButton(pauseIcon, pauseIconRollover, pauseIconPressed); pauseButton.addActionListener(playerListener); pauseButton.setActionCommand(PAUSE); pauseButton.setVisible(false); pauseButton.setToolTipText(I18n.tr("Pause")); pauseButton.setMinimumSize(playButton.getMinimumSize()); pauseButton.setPreferredSize(playButton.getPreferredSize()); forwardButton = new IconButton(forwardIcon, forwardIconRollover, forwardIconPressed); forwardButton.addActionListener(playerListener); forwardButton.setActionCommand(FORWARD); forwardButton.setDisabledIcon(forwardIconDisabled); forwardButton.setToolTipText(I18n.tr("Skip Forward")); volumeButton = new IconButton(volumeIcon, volumeIconRollover, volumeIconPressed); volumeButton.addActionListener(playerListener); volumeButton.setActionCommand(VOLUME); volumeButton.setDisabledIcon(volumeIconDisabled); volumeButton.setToolTipText(I18n.tr("Volume")); volumeSlider = new VolumeSlider(0, 100); volumeControlPopup = volumeSlider.createPopup(); shuffleButton = new IconButton(shuffleIcon, shuffleIconRollover, shuffleIconPressed, shuffleIconActive); shuffleButton.addActionListener(playerListener); shuffleButton.setActionCommand(SHUFFLE); shuffleButton.setRolloverSelectedIcon(shuffleIconActive); shuffleButton.setToolTipText(I18n.tr("Shuffle")); progressSlider = new LimeSliderBar(); sliderBarDecorator.decoratePlain(progressSlider); initProgressControl(); statusPanel = new JPanel(new MigLayout()); titleLabel = new MarqueeButton("", 150); titleLabel.setFont(font); ResizeUtils.forceSize(titleLabel, new Dimension(206, (int) font.getMaxCharBounds(new FontRenderContext(null, false, false)).getHeight())); statusPanel.add(titleLabel); statusPanel.add(progressSlider, "dock south"); statusPanel.setOpaque(false); int buttonWidth = backButton.getPreferredSize().width + playButton.getPreferredSize().width + forwardButton.getPreferredSize().width; Dimension statusSize = new Dimension(buttonWidth, statusPanel.getPreferredSize().height); statusPanel.setPreferredSize(statusSize); innerPanel = new JXPanel(new MigLayout("insets 4 10 4 10, filly, gapy 5, alignx center")); innerPanel.setOpaque(false); innerPanel.setBackgroundPainter(createStatusBackgroundPainter()); innerPanel.add(backButton, "gapright 1"); innerPanel.add(pauseButton, "hidemode 3"); innerPanel.add(playButton, "hidemode 3"); innerPanel.add(forwardButton, "gapright 3"); innerPanel.add(statusPanel, "gapbottom 2, hidemode 2"); innerPanel.add(volumeButton, "gapleft 2"); innerPanel.add(shuffleButton, "gapleft 2"); innerPanel.setVisible(SwingUiSettings.PLAYER_ENABLED.getValue()); add(innerPanel, "gaptop 2, gapbottom 2"); VolumeController volumeController = new VolumeController(); volumeSlider.addChangeListener(volumeController); volumeControlPopup.addPopupMenuListener(new PopupMenuListener() { @Override public void popupMenuCanceled(PopupMenuEvent e) { playerListener.clearMenu(); } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { playerListener.clearMenu(); } @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { } }); } /** * Registers listeners for player events. */ @Inject void register() { getPlayerMediator().addMediatorListener(this); // Stop player if disabled, and show/hide player. SwingUiSettings.PLAYER_ENABLED.addSettingListener(new SettingListener(){ @Override public void settingChanged(final SettingEvent evt) { SwingUtilities.invokeLater(new Runnable(){ public void run() { boolean enabled = SwingUiSettings.PLAYER_ENABLED.getValue(); if (!enabled) { getPlayerMediator().stop(); } PlayerPanel.this.innerPanel.setVisible(enabled); } }); } }); } /** * Initializes the progress component. */ private void initProgressControl() { progressSlider.addChangeListener(new AudioProgressListener()); progressSlider.setMaximum(Integer.MAX_VALUE); progressSlider.setMaximumSize(new Dimension(206, 6)); progressSlider.setMinimumSize(new Dimension(206, 6)); progressSlider.setPreferredSize(new Dimension(206, 6)); progressSlider.setSize(new Dimension(206, 4)); progressSlider.addMouseListener(new MouseAdapter() { /** * Reposition the thumb on the jslider to the location of the mouse * click */ @Override public void mousePressed(MouseEvent e) { if (!progressSlider.isEnabled()) return; mouseSkip(e.getX()); } /** * Overrides the mouse press increment when a mouse click occurs in the * jslider. Repositions the jslider directly to the location the mouse * click avoiding the standard step increment with each click * @param x - location of mouse click */ protected void mouseSkip(int x) { if (progressSlider.getUI() instanceof BasicSliderUI) { progressSlider.setValue(((BasicSliderUI)progressSlider.getUI()).valueForXPosition(x)); } } }); } /** * Creates a background painter for the container. */ private Painter<JXPanel> createStatusBackgroundPainter() { return new ComponentBackgroundPainter<JXPanel>(innerBackground, innerBorder, bevelLeft, bevelTop1, bevelTop2, bevelRight, bevelBottom, arcWidth, arcHeight, AccentType.SHADOW); } /** * Returns the mediator component that controls the player. */ private PlayerMediator getPlayerMediator() { return playerProvider.get(); } /** * Handles update to the specified progress value to adjust the visual * position of the slider. */ @Override public void progressUpdated(float progress) { if (!(progressSlider.getValueIsAdjusting() || getPlayerMediator().getStatus() == PlayerState.SEEKING)) { progressSlider.setValue((int) (progressSlider.getMaximum() * progress)); } } /** * Handles song change to the specified song name. */ @Override public void songChanged(String name) { // Update volume. updateVolume(); // Enable progress slider. progressSlider.setEnabled(getPlayerMediator().isSeekable()); // Set song text. titleLabel.setText(name); titleLabel.setToolTipText(name); titleLabel.start(); if (!innerPanel.isVisible()) { innerPanel.setVisible(true); } } /** * Hanldes state change in the player to the specified state. */ @Override public void stateChanged(PlayerState playerState) { if ((playerState == PlayerState.OPENED) || (playerState == PlayerState.SEEKED)) { updateVolume(); } else if (playerState == PlayerState.GAIN) { // Exit on volumn change. return; } // Update buttons based on player status. if ((playerState == PlayerState.PLAYING) || (playerState == PlayerState.SEEKING_PLAY)) { playButton.setVisible(false); pauseButton.setVisible(true); titleLabel.start(); } else if (playerState == PlayerState.PAUSED || playerState == PlayerState.SEEKING_PAUSED || playerState == PlayerState.EOM || playerState == PlayerState.STOPPED || playerState == PlayerState.UNKNOWN) { playButton.setVisible(true); pauseButton.setVisible(false); titleLabel.stop(); } } /** * Updates the volume in the player. */ private void updateVolume() { getPlayerMediator().setVolume(((double) volumeSlider.getValue()) / volumeSlider.getMaximum()); } /** * Listener to handle user actions on various player buttons. */ private class ButtonListener implements ActionListener { private long menuInvizTime = -1; public void clearMenu() { menuInvizTime = System.currentTimeMillis(); } @Override public void actionPerformed(ActionEvent e) { if (e.getActionCommand() == PLAY){ getPlayerMediator().resume(); } else if (e.getActionCommand() == PAUSE){ getPlayerMediator().pause(); } else if (e.getActionCommand() == FORWARD) { getPlayerMediator().nextSong(); } else if (e.getActionCommand() == BACK) { getPlayerMediator().prevSong(); } else if (e.getActionCommand() == VOLUME) { if (System.currentTimeMillis() - menuInvizTime > 250f) { volumeControlPopup.show(volumeButton, 0, 14); } } else if (e.getActionCommand() == SHUFFLE) { // Toggle shuffle mode. getPlayerMediator().setShuffle(!getPlayerMediator().isShuffle()); // Update button state. shuffleButton.setSelected(getPlayerMediator().isShuffle()); shuffleButton.setPressedIcon(getPlayerMediator().isShuffle() ? shuffleIconActive : shuffleIconPressed); } } } /** * Listener to handle change to progress bar to skip to a new position in * the song. */ private class AudioProgressListener implements ChangeListener { private boolean waiting = false; @Override public void stateChanged(ChangeEvent e) { if (progressSlider.getMaximum() != 0 && progressSlider.getValueIsAdjusting()) { if (!waiting) { waiting = true; } } else if (waiting) { waiting = false; double percent = (double)progressSlider.getValue() / (double)progressSlider.getMaximum(); getPlayerMediator().skip(percent); progressSlider.setValue((int)(percent * progressSlider.getMaximum())); } } } /** * Listener to update volume when volume slider is adjusted. */ private class VolumeController implements ChangeListener { @Override public void stateChanged(ChangeEvent e) { updateVolume(); } } }