package com.sleazyweasel.applescriptifier;
import com.sleazyweasel.applescriptifier.preferences.MuseControllerPreferences;
import layout.TableLayout;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import static layout.TableLayoutConstants.FILL;
import static layout.TableLayoutConstants.PREFERRED;
public class PandoraUI implements MuseControllerFrame {
private static final Logger logger = Logger.getLogger(PandoraUI.class.getName());
private final AtomicBoolean executionLock = new AtomicBoolean(false);
private final Widgets widgets = new Widgets();
private final Models models = new Models();
private final MusicPlayer musicPlayer;
private final JMenuBar mainMenuBar;
private final JMenuItem pandoraMenuItem;
private final MuseControllerPreferences preferences;
public PandoraUI(MusicPlayer musicPlayer, JMenuBar mainMenuBar, JMenuItem pandoraMenuItem, MuseControllerPreferences preferences) {
this.musicPlayer = musicPlayer;
this.mainMenuBar = mainMenuBar;
this.pandoraMenuItem = pandoraMenuItem;
this.preferences = preferences;
initUserInterface();
preferences.setPandoraAsStreamer();
}
private void initUserInterface() {
initWidgetsAndModels();
initLayout();
}
private void initWidgetsAndModels() {
initMenuBar();
initWindow();
initStationComboBox();
initStationNameLabel();
initArtistLabel();
initAlbumLabel();
initSongLabel();
initTimeLabel();
initDurationLabel();
initTimeProgressIndicator();
initPlayPauseButton();
initNextButton();
initThumbsUpButton();
initThumbsDownButton();
initSleepButton();
initVolumeUpButton();
initVolumeDownButton();
initVolumeSlider();
initImageLabel();
initInfoLabel();
}
private void initWindow() {
widgets.window = new JFrame("Pandora");
widgets.window.setContentPane(new JPanel() {
@Override
public void paint(Graphics g) {
super.paint(g);
// ((TableLayout) getLayout()).drawGrid(this, g);
}
});
}
private void initMenuBar() {
widgets.menu = new JMenu("Control");
widgets.menu.setMnemonic('c');
widgets.menu.add(initPlayPauseMenuItem());
widgets.menu.add(initNextMenuItem());
widgets.menu.addSeparator();
widgets.menu.add(initThumbsUpMenuItem());
widgets.menu.add(initThumbsDownMenuItem());
widgets.menu.add(initSleepMenuItem());
widgets.menu.addSeparator();
widgets.menu.add(initVolumeUpMenuItem());
widgets.menu.add(initVolumeDownMenuItem());
widgets.menu.addSeparator();
widgets.menu.add(initRestartPandoraMenuItem());
}
private JMenuItem initVolumeDownMenuItem() {
JMenuItem menuItem = new JMenuItem("Volume Down");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.volumeDown();
}
});
menuItem.setEnabled(false);
widgets.volumeDownMenuItem = menuItem;
return menuItem;
}
private JMenuItem initVolumeUpMenuItem() {
JMenuItem menuItem = new JMenuItem("Volume Up");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.volumeUp();
}
});
menuItem.setEnabled(false);
widgets.volumeUpMenuItem = menuItem;
return menuItem;
}
private JMenuItem initThumbsDownMenuItem() {
JMenuItem menuItem = new JMenuItem("Thumbs Down");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.thumbsDown();
}
});
menuItem.setEnabled(false);
widgets.thumbsDownMenuItem = menuItem;
return menuItem;
}
private JMenuItem initThumbsUpMenuItem() {
JMenuItem menuItem = new JMenuItem("Thumbs Up");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.thumbsUp();
}
});
menuItem.setEnabled(false);
widgets.thumbsUpMenuItem = menuItem;
return menuItem;
}
private JMenuItem initSleepMenuItem() {
JMenuItem menuItem = new JMenuItem("Mark as Tired");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.sleep();
}
});
menuItem.setEnabled(false);
widgets.sleepMenuItem = menuItem;
return menuItem;
}
private JMenuItem initNextMenuItem() {
JMenuItem menuItem = new JMenuItem("Next");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.next();
}
});
menuItem.setEnabled(false);
widgets.nextMenuItem = menuItem;
return menuItem;
}
private JMenuItem initPlayPauseMenuItem() {
JMenuItem menuItem = new JMenuItem("Play/Pause");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.playPause();
}
});
menuItem.setEnabled(false);
widgets.playPauseMenuItem = menuItem;
return menuItem;
}
private JMenuItem initRestartPandoraMenuItem() {
JMenuItem restartMenuItem = new JMenuItem("Restart Pandora");
restartMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.bounce();
}
});
return restartMenuItem;
}
private void initStationComboBox() {
models.stationComboBoxModel = new StationComboBoxModel(musicPlayer);
widgets.stationComboBox = new JComboBox(models.stationComboBoxModel);
widgets.stationComboBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (acquireLock()) {
musicPlayer.askToChooseStation();
StationChoice selectedItem = models.stationComboBoxModel.getSelectedStation();
if (selectedItem != null) {
musicPlayer.selectStation(selectedItem.key());
}
releaseLock();
}
}
});
widgets.stationComboBox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value != null) {
StationChoice stationChoice = (StationChoice) value;
setText(stationChoice.stationName());
} else {
setText("Choose a station");
}
return component;
}
});
models.stationComboBoxModel.setSelectedItem(null);
}
private void initStationNameLabel() {
widgets.stationNameLabel = new JLabel();
widgets.stationNameLabel.setForeground(new Color(102, 102, 102));
widgets.stationNameLabel.setFont(Font.decode("Lucida Grande-Bold-14"));
}
private void initArtistLabel() {
widgets.artistLabel = new JLabel();
widgets.artistLabel.setForeground(new Color(102, 102, 102));
widgets.artistLabel.setFont(Font.decode("Lucida Grande-Bold-14"));
}
private void initAlbumLabel() {
widgets.albumLabel = new JLabel();
widgets.albumLabel.setForeground(new Color(102, 102, 102));
widgets.albumLabel.setFont(Font.decode("Lucida Grande-Bold-14"));
}
private void initSongLabel() {
widgets.songLabel = new JLabel();
widgets.songLabel.setForeground(new Color(51, 51, 51));
widgets.songLabel.setFont(Font.decode("Lucida Grande-Bold-14"));
}
private void initTimeLabel() {
widgets.timeLabel = new JLabel();
widgets.timeLabel.setFont(Font.decode("Lucida Grande-Bold-12"));
widgets.timeLabel.setForeground(new Color(102, 102, 102));
}
private void initDurationLabel() {
widgets.durationLabel = new JLabel();
widgets.durationLabel.setFont(Font.decode("Lucida Grande-Bold-12"));
widgets.durationLabel.setForeground(new Color(102, 102, 102));
}
private void initTimeProgressIndicator() {
widgets.timeProgressBar = new JProgressBar(0, 10000);
}
private void initPlayPauseButton() {
widgets.playPauseButton = new JButton(getIcon("playpause.png"));
setButtonDefaults(widgets.playPauseButton);
widgets.playPauseButton.setEnabled(false);
widgets.playPauseButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.playPause();
}
});
}
private ImageIcon getIcon(String name) {
return new ImageIcon(getClass().getClassLoader().getResource(name));
}
private void setButtonDefaults(JButton button) {
button.putClientProperty("JComponent.sizeVariant", "small");
button.putClientProperty("Quaqua.Button.style", "push");
}
private void initNextButton() {
widgets.nextButton = new JButton(getIcon("nextsong.png"));
setButtonDefaults(widgets.nextButton);
widgets.nextButton.setEnabled(false);
widgets.nextButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.next();
}
});
}
private void initThumbsUpButton() {
widgets.thumbsUpButton = new JButton(getIcon("ThumbsUp.png"));
setButtonDefaults(widgets.thumbsUpButton);
widgets.thumbsUpButton.setEnabled(false);
widgets.thumbsUpButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.thumbsUp();
}
});
}
private void initThumbsDownButton() {
widgets.thumbsDownButton = new JButton(getIcon("ThumbsDown.png"));
setButtonDefaults(widgets.thumbsDownButton);
widgets.thumbsDownButton.setEnabled(false);
widgets.thumbsDownButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.thumbsDown();
}
});
}
private void initSleepButton() {
widgets.sleepButton = new JButton(getIcon("Sleep.png"));
setButtonDefaults(widgets.sleepButton);
widgets.sleepButton.setToolTipText("Mark Song as Tired");
widgets.sleepButton.setEnabled(false);
widgets.sleepButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
musicPlayer.sleep();
}
});
}
private void initVolumeUpButton() {
widgets.volumeUpButton = new JButton(getIcon("volume_up.png"));
setButtonDefaults(widgets.volumeUpButton);
widgets.volumeUpButton.setEnabled(false);
widgets.volumeUpButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.volumeUp();
}
});
}
private void initVolumeDownButton() {
widgets.volumeDownButton = new JButton(getIcon("volume_down.png"));
setButtonDefaults(widgets.volumeDownButton);
widgets.volumeDownButton.setEnabled(false);
widgets.volumeDownButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
musicPlayer.volumeDown();
}
});
}
private void initVolumeSlider() {
widgets.volumeSlider = new JSlider();
widgets.volumeSlider.setEnabled(false);
widgets.volumeSlider.setMinimum(0);
widgets.volumeSlider.setMaximum(100);
widgets.volumeSlider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
musicPlayer.setVolume(getSliderValue());
}
});
}
private double getSliderValue() {
return widgets.volumeSlider.getValue() / 100d;
}
private void initImageLabel() {
widgets.imageLabel = new JLabel();
// widgets.imageLabel.setBorder(BorderFactory.createEtchedBorder());
}
private void initInfoLabel() {
widgets.infoLabel = new JLabel();
ImageIcon icon = getIcon("info.png");
widgets.infoLabel.setIcon(icon);
widgets.infoLabel.setVisible(false);
widgets.infoLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
String detailUrl = musicPlayer.getState().getDetailUrl();
if (detailUrl != null && detailUrl.length() > 0) {
try {
open(new URI(detailUrl));
} catch (URISyntaxException e1) {
logger.log(Level.WARNING, "Exception caught:", e1);
//do nothing if it doesn't parse?
}
}
}
});
}
private void initLayout() {
JPanel leftButtonPanel = new JPanel(new TableLayout(new double[][]{
{FILL, PREFERRED, PREFERRED, PREFERRED, PREFERRED, PREFERRED, FILL, PREFERRED, PREFERRED}, // horizontal
{FILL, PREFERRED} // vertical
}));
leftButtonPanel.add(widgets.playPauseButton, "1,1");
leftButtonPanel.add(widgets.nextButton, "2,1");
leftButtonPanel.add(widgets.thumbsUpButton, "3,1");
leftButtonPanel.add(widgets.thumbsDownButton, "4,1");
leftButtonPanel.add(widgets.sleepButton, "5,1");
int gap = -3;
JPanel infoPanel = new JPanel(new TableLayout(new double[][]{
{35, 25, FILL, 15, FILL, 10, 35}, // horizontal
{20, gap, 20, gap, 20, 8} // vertical
}));
infoPanel.add(widgets.artistLabel, "0, 0, 6, 0, L, t");
infoPanel.add(widgets.infoLabel, "6, 0, R, c");
infoPanel.add(widgets.songLabel, "0, 2, 6, 2, L, t");
infoPanel.add(widgets.albumLabel, "0, 4, 6, 4, L, t");
widgets.window.getContentPane().setLayout(new TableLayout(new double[][]{
{15, 150, 15, 35, 5, FILL, 5, 35, 15}, // horizontal
{15, 30, 10, 70, 1, 12, 5, 32, 2, 32, 15}} // vertical
));
widgets.window.getContentPane().add(widgets.stationComboBox, "1, 1, 7, 1, L");
widgets.window.getContentPane().add(infoPanel, "3, 3, 7, 3");
widgets.window.getContentPane().add(widgets.imageLabel, "1, 3, 1, 9, L, c");
JPanel progressPanel = new JPanel(new TableLayout(new double[][]{
{FILL}, //horizontal
{4, 4, 4} //vertical
}));
progressPanel.add(widgets.timeProgressBar, "0, 1");
widgets.window.getContentPane().add(widgets.timeLabel, "3, 5, c, c");
widgets.window.getContentPane().add(progressPanel, "5, 5");
widgets.window.getContentPane().add(widgets.durationLabel, "7, 5, c, c");
widgets.window.getContentPane().add(leftButtonPanel, "3, 7, 7, 7");
widgets.window.getContentPane().add(widgets.volumeDownButton, "3, 9");
widgets.window.getContentPane().add(widgets.volumeSlider, "5, 9");
widgets.window.getContentPane().add(widgets.volumeUpButton, "7, 9");
widgets.window.pack();
widgets.window.setResizable(false);
}
public JFrame getWindow() {
return widgets.window;
}
public void initialize() {
musicPlayer.addListener(new MusicPlayerStateChangeListener());
musicPlayer.activate();
musicPlayer.initializeFromSavedUserState(preferences);
mainMenuBar.add(widgets.menu);
initWidgetStateFromPlayer();
}
private void initWidgetStateFromPlayer() {
double volume = musicPlayer.getState().getVolume();
widgets.volumeSlider.setValue((int) (volume * 100));
}
public void close() {
musicPlayer.close();
mainMenuBar.remove(widgets.menu);
pandoraMenuItem.setEnabled(true);
widgets.window.dispose();
}
private static class Widgets {
private JComboBox stationComboBox;
private JFrame window;
private JButton playPauseButton;
private JButton nextButton;
private JButton thumbsUpButton;
private JButton thumbsDownButton;
private JButton sleepButton;
private JButton volumeUpButton;
private JButton volumeDownButton;
private JSlider volumeSlider;
private JLabel stationNameLabel;
private JLabel artistLabel;
private JLabel albumLabel;
private JLabel songLabel;
private JLabel imageLabel;
private JLabel timeLabel;
private JLabel durationLabel;
private JLabel infoLabel;
private JMenuItem volumeDownMenuItem;
private JMenuItem volumeUpMenuItem;
private JMenuItem thumbsDownMenuItem;
private JMenuItem thumbsUpMenuItem;
private JMenuItem sleepMenuItem;
private JMenuItem nextMenuItem;
private JMenuItem playPauseMenuItem;
private JMenu menu;
private JProgressBar timeProgressBar;
}
private static class Models {
private StationComboBoxModel stationComboBoxModel;
}
private class MusicPlayerStateChangeListener implements MusicPlayer.MusicPlayerStateChangeListener {
public void stateChanged(final MusicPlayer player, MusicPlayerState state) {
if (acquireLock()) {
widgets.playPauseButton.setEnabled(!state.isInputRequested());
widgets.nextButton.setEnabled(trackCanBeModified(state));
widgets.thumbsDownButton.setEnabled(trackCanBeModified(state));
widgets.volumeUpButton.setEnabled(!state.isInputRequested());
widgets.volumeDownButton.setEnabled(!state.isInputRequested());
widgets.volumeDownMenuItem.setEnabled(!state.isInputRequested());
widgets.volumeUpMenuItem.setEnabled(!state.isInputRequested());
widgets.volumeSlider.setEnabled(!state.isInputRequested());
widgets.volumeSlider.setValue((int) (state.getVolume() * 100));
widgets.thumbsDownMenuItem.setEnabled(trackCanBeModified(state));
widgets.thumbsUpMenuItem.setEnabled(trackCanBeModified(state));
widgets.sleepMenuItem.setEnabled(trackCanBeModified(state));
widgets.sleepButton.setEnabled(trackCanBeModified(state));
widgets.nextMenuItem.setEnabled(trackCanBeModified(state));
widgets.playPauseMenuItem.setEnabled(!state.isInputRequested());
widgets.stationNameLabel.setText(state.getStation());
widgets.artistLabel.setText(state.getArtist());
widgets.albumLabel.setText(state.getAlbum());
widgets.songLabel.setText(state.getTitle());
widgets.timeLabel.setText(formatTime(state.getCurrentTime()));
if (state.getCurrentTime() == 0 || state.getDuration() == 0) {
widgets.timeProgressBar.setValue(0);
} else {
widgets.timeProgressBar.setValue((int) (((float) state.getCurrentTime() / (float) state.getDuration()) * 10000f));
}
widgets.durationLabel.setText(formatTime(state.getDuration()));
models.stationComboBoxModel.refreshContents();
models.stationComboBoxModel.setSelectedItem(state.getCurrentStation());
widgets.thumbsUpButton.setEnabled(trackCanBeModified(state) && !state.isCurrentSongIsLoved());
if (state.isPlaying()) {
widgets.playPauseButton.setIcon(getIcon("pause.png"));
} else {
widgets.playPauseButton.setIcon(getIcon("play.png"));
}
if (state.getDetailUrl() != null && state.getDetailUrl().length() > 0) {
widgets.infoLabel.setVisible(true);
} else {
widgets.infoLabel.setVisible(false);
}
String albumArtUrl = state.getAlbumArtUrl();
if (widgets.imageLabel.getName() == null || !widgets.imageLabel.getName().equals(albumArtUrl)) {
widgets.imageLabel.setName(albumArtUrl);
if (albumArtUrl.startsWith("http")) {
setAlbumImage(albumArtUrl);
} else {
widgets.imageLabel.setIcon(null);
}
}
releaseLock();
}
}
private boolean trackCanBeModified(MusicPlayerState state) {
return !state.isInputRequested() && state.getTitle() != null && !state.getTitle().isEmpty();
}
}
private String formatTime(int currentTime) {
if (currentTime == 0) {
return "";
}
int minutes = currentTime / 60;
int seconds = currentTime % 60;
String secondsString = String.valueOf(seconds);
if (secondsString.length() == 1) {
secondsString = "0" + secondsString;
}
return minutes + ":" + secondsString;
}
private void setAlbumImage(final String albumArtUrl) {
new SwingWorker() {
@Override
protected Object doInBackground() throws Exception {
ImageIcon icon = null;
try {
URL imageUrl = new URL(albumArtUrl);
icon = new ImageIcon(imageUrl);
Image scaledImage = icon.getImage().getScaledInstance(150, 150, Image.SCALE_SMOOTH);
icon.setImage(scaledImage);
} catch (MalformedURLException e) {
logger.log(Level.WARNING, "Exception caught.", e);
}
return icon;
}
@Override
protected void done() {
try {
widgets.imageLabel.setIcon((Icon) get());
} catch (InterruptedException e) {
//make sure that anyone else gets notified that something should be done.
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
logger.log(Level.WARNING, "Exception caught.", e);
}
}
}.execute();
}
private static void open(URI uri) {
if (Desktop.isDesktopSupported()) {
Desktop desktop = Desktop.getDesktop();
try {
desktop.browse(uri);
} catch (IOException e) {
// TODO: error handling
}
} else {
// TODO: error handling
}
}
private boolean acquireLock() {
if (executionLock.get()) {
return false;
}
executionLock.getAndSet(true);
return true;
}
private void releaseLock() {
executionLock.set(false);
}
}