/*
* 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.widgets;
import static org.jajuk.ui.actions.JajukActions.CONFIGURE_DJS;
import static org.jajuk.ui.actions.JajukActions.FINISH_ALBUM;
import static org.jajuk.ui.actions.JajukActions.MUTE_STATE;
import static org.jajuk.ui.actions.JajukActions.NEXT_ALBUM;
import static org.jajuk.ui.actions.JajukActions.NEXT_TRACK;
import static org.jajuk.ui.actions.JajukActions.PAUSE_RESUME_TRACK;
import static org.jajuk.ui.actions.JajukActions.PREVIOUS_ALBUM;
import static org.jajuk.ui.actions.JajukActions.PREVIOUS_TRACK;
import static org.jajuk.ui.actions.JajukActions.STOP_TRACK;
import ext.DropDownButton;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JSlider;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.miginfocom.swing.MigLayout;
import org.jajuk.events.JajukEvent;
import org.jajuk.events.JajukEvents;
import org.jajuk.events.ObservationManager;
import org.jajuk.events.Observer;
import org.jajuk.services.dj.DigitalDJ;
import org.jajuk.services.dj.DigitalDJManager;
import org.jajuk.services.players.Player;
import org.jajuk.ui.actions.ActionManager;
import org.jajuk.ui.actions.ActionUtil;
import org.jajuk.ui.actions.JajukAction;
import org.jajuk.ui.actions.JajukActions;
import org.jajuk.ui.actions.MuteAction;
import org.jajuk.ui.helpers.JajukMouseAdapter;
import org.jajuk.ui.helpers.PlayerStateMediator;
import org.jajuk.ui.substance.CircleButtonShaper;
import org.jajuk.ui.substance.LeftConcaveButtonShaper;
import org.jajuk.ui.substance.RightConcaveButtonShaper;
import org.jajuk.ui.substance.RoundRectButtonShaper;
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.log.Log;
import org.jdesktop.swingx.JXPanel;
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
/**
* Command panel ( static view )
* <p>
* Singleton
* </p>.
*/
public class CommandJPanel extends JXPanel implements ActionListener, ChangeListener, Observer,
MouseWheelListener {
/** Generated serialVersionUID. */
private static final long serialVersionUID = 1L;
// singleton
private static CommandJPanel command = new CommandJPanel();
// widgets declaration
/** Continue mode button. */
private JajukToggleButton jbContinue;
private JajukToggleButton jbRepeat;
private JajukToggleButton jbShuffle;
private JToolBar jtbSpecial;
private DropDownButton ddbGlobalRandom;
private JRadioButtonMenuItem jmiShuffleModeSong;
private JRadioButtonMenuItem jmiShuffleModeAlbum;
private JRadioButtonMenuItem jmiShuffleModeAlbum2;
private JPopupMenu popupGlobalRandom;
private JajukButton jbBestof;
private DropDownButton ddbNovelties;
private JPopupMenu popupNovelties;
private WebRadioButton webRadioButton;
private JRadioButtonMenuItem jmiNoveltiesModeSong;
private JRadioButtonMenuItem jmiNoveltiesModeAlbum;
private JajukButton jbNorm;
private DropDownButton ddbDDJ;
private JPopupMenu popupDDJ;
private JButton jbPrevious;
private JButton jbNext;
private JButton jbPlayPause;
private JButton jbStop;
private JSlider jsVolume;
private JLabel jlVolume;
private PreferenceToolbar evaltoobar;
private JajukButton jbMute;
// variables declaration
private JajukToggleButton jbRepeatAll;
/**
* Gets the instance.
*
* @return singleton
*/
public static CommandJPanel getInstance() {
return command;
}
/**
* Constructor, this objects needs to be implemented for the tray (child
* object).
*/
public CommandJPanel() {
super();
}
/**
* Inits the ui.
*
*/
public void initUI() {
// Instanciate the PlayerStateMediator to listen for player basic controls
PlayerStateMediator.getInstance();
// Install keystrokes on invisible components
ActionUtil.installKeystrokes(CommandJPanel.this, ActionManager.getAction(NEXT_ALBUM),
ActionManager.getAction(PREVIOUS_ALBUM));
// Mode toolbar
// we need an inner toolbar to apply size properly
JPanel jpModes = new JPanel();
jpModes.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 3));
// make it not floatable as this behavior is managed by vldocking
jbContinue = new JajukToggleButton(ActionManager.getAction(JajukActions.CONTINUE_MODE));
jbContinue.setSelected(Conf.getBoolean(Const.CONF_STATE_CONTINUE));
jbRepeat = new JajukToggleButton(ActionManager.getAction(JajukActions.REPEAT_MODE));
jbRepeat.setSelected(Conf.getBoolean(Const.CONF_STATE_REPEAT));
jbRepeatAll = new JajukToggleButton(ActionManager.getAction(JajukActions.REPEAT_ALL_MODE));
jbRepeatAll.setSelected(Conf.getBoolean(Const.CONF_STATE_REPEAT_ALL));
jbShuffle = new JajukToggleButton(ActionManager.getAction(JajukActions.SHUFFLE_MODE));
jbShuffle.setSelected(Conf.getBoolean(Const.CONF_STATE_SHUFFLE));
jpModes.add(jbContinue);
jpModes.add(jbRepeat);
jpModes.add(jbRepeatAll);
jpModes.add(jbShuffle);
// Eval toolbar
evaltoobar = new PreferenceToolbar();
// Volume
int iVolume = (int) (100 * Conf.getFloat(Const.CONF_VOLUME));
// Perform bounds test, -1 or >100 can occur in some undefined cases (see
// #1169)
if (iVolume > 100) {
iVolume = 100;
} else if (iVolume < 0) {
iVolume = 0;
}
jsVolume = new JSlider(0, 100, iVolume);
jlVolume = new JLabel(iVolume + " %");
jsVolume.addChangeListener(CommandJPanel.this);
jbMute = new JajukButton(ActionManager.getAction(MUTE_STATE));
jbMute.setText(null);
jbMute.putClientProperty(SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY,
new RoundRectButtonShaper());
MuteAction.setVolumeIcon(iVolume);
jbMute.addMouseWheelListener(CommandJPanel.this);
jsVolume.addMouseWheelListener(CommandJPanel.this);
ActionUtil.installKeystrokes(jbMute, ActionManager.getAction(JajukActions.DECREASE_VOLUME),
ActionManager.getAction(JajukActions.INCREASE_VOLUME));
ActionUtil.installKeystrokes(jsVolume, ActionManager.getAction(JajukActions.DECREASE_VOLUME),
ActionManager.getAction(JajukActions.INCREASE_VOLUME));
// Special functions toolbar
jtbSpecial = new JajukJToolbar();
ddbGlobalRandom = new DropDownButton(IconLoader.getIcon(JajukIcons.SHUFFLE_GLOBAL)) {
private static final long serialVersionUID = 1L;
@Override
protected JPopupMenu getPopupMenu() {
return popupGlobalRandom;
}
};
ddbGlobalRandom.setAction(ActionManager.getAction(JajukActions.SHUFFLE_GLOBAL));
popupGlobalRandom = new JPopupMenu();
// Global shuffle
jmiShuffleModeSong = new JRadioButtonMenuItem(Messages.getString("CommandJPanel.20"));
jmiShuffleModeSong.addActionListener(this);
// album / album
jmiShuffleModeAlbum = new JRadioButtonMenuItem(Messages.getString("CommandJPanel.21"));
jmiShuffleModeAlbum.addActionListener(this);
// Shuffle album / album
jmiShuffleModeAlbum2 = new JRadioButtonMenuItem(Messages.getString("CommandJPanel.22"));
jmiShuffleModeAlbum2.addActionListener(this);
if (Conf.getString(Const.CONF_GLOBAL_RANDOM_MODE).equals(Const.MODE_TRACK)) {
jmiShuffleModeSong.setSelected(true);
} else if (Conf.getString(Const.CONF_GLOBAL_RANDOM_MODE).equals(Const.MODE_ALBUM2)) {
jmiShuffleModeAlbum2.setSelected(true);
} else {
jmiShuffleModeAlbum.setSelected(true);
}
ButtonGroup bgGlobalRandom = new ButtonGroup();
bgGlobalRandom.add(jmiShuffleModeSong);
bgGlobalRandom.add(jmiShuffleModeAlbum);
bgGlobalRandom.add(jmiShuffleModeAlbum2);
popupGlobalRandom.add(jmiShuffleModeSong);
popupGlobalRandom.add(jmiShuffleModeAlbum);
popupGlobalRandom.add(jmiShuffleModeAlbum2);
ddbGlobalRandom.setText("");// no text visible
jbBestof = new JajukButton(ActionManager.getAction(JajukActions.BEST_OF));
ddbNovelties = new DropDownButton(IconLoader.getIcon(JajukIcons.NOVELTIES)) {
private static final long serialVersionUID = 1L;
@Override
protected JPopupMenu getPopupMenu() {
return popupNovelties;
}
};
ddbNovelties.setAction(ActionManager.getAction(JajukActions.NOVELTIES));
popupNovelties = new JPopupMenu();
jmiNoveltiesModeSong = new JRadioButtonMenuItem(Messages.getString("CommandJPanel.20"));
jmiNoveltiesModeSong.addActionListener(this);
jmiNoveltiesModeAlbum = new JRadioButtonMenuItem(Messages.getString("CommandJPanel.22"));
jmiNoveltiesModeAlbum.addActionListener(this);
if (Conf.getString(Const.CONF_NOVELTIES_MODE).equals(Const.MODE_TRACK)) {
jmiNoveltiesModeSong.setSelected(true);
} else {
jmiNoveltiesModeAlbum.setSelected(true);
}
ButtonGroup bgNovelties = new ButtonGroup();
bgNovelties.add(jmiNoveltiesModeSong);
bgNovelties.add(jmiNoveltiesModeAlbum);
popupNovelties.add(jmiNoveltiesModeSong);
popupNovelties.add(jmiNoveltiesModeAlbum);
ddbNovelties.setText("");// no text visible
jbNorm = new JajukButton(ActionManager.getAction(FINISH_ALBUM));
// Radio tool bar
popupDDJ = new JPopupMenu();
ddbDDJ = new DropDownButton(IconLoader.getIcon(JajukIcons.DIGITAL_DJ)) {
private static final long serialVersionUID = 1L;
@Override
protected JPopupMenu getPopupMenu() {
return popupDDJ;
}
};
ddbDDJ.setAction(ActionManager.getAction(JajukActions.DJ));
populateDJs();
// no text visible
ddbDDJ.setText("");
webRadioButton = new WebRadioButton(IconLoader.getIcon(JajukIcons.WEBRADIO));
JToolBar jtbWebRadio = new JajukJToolbar();
webRadioButton.addToToolBar(jtbWebRadio);
ddbDDJ.addToToolBar(jtbSpecial);
ddbNovelties.addToToolBar(jtbSpecial);
ddbGlobalRandom.addToToolBar(jtbSpecial);
jtbSpecial.add(jbBestof);
jtbSpecial.add(jbNorm);
// Play buttons
jbPrevious = new JajukButton(ActionManager.getAction(PREVIOUS_TRACK));
// Manage right click : replay the track (this not triggers an action so we use a MouseAdapter here)
jbPrevious.addMouseListener(new JajukMouseAdapter() {
@Override
public void handlePopup(final MouseEvent me) {
// Create an ActionEvent from this MouseEvent with a custom modifier : the right click
ActionEvent ae = new ActionEvent(jbPrevious, 0, PREVIOUS_TRACK.name(), 4332424);
try {
ActionManager.getAction(PREVIOUS_TRACK).perform(ae);
} catch (Exception e) {
Log.error(e);
}
}
});
// Compute concavity of player icon
int concavity = IconLoader.getIcon(JajukIcons.PLAYER_PLAY).getIconHeight();
jbPrevious.putClientProperty(SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY,
new RightConcaveButtonShaper(concavity));
jbNext = new JajukButton(ActionManager.getAction(NEXT_TRACK));
jbNext.putClientProperty(SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY,
new LeftConcaveButtonShaper(concavity));
jbPlayPause = new JajukButton(ActionManager.getAction(PAUSE_RESUME_TRACK));
jbPlayPause.putClientProperty(SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY,
new CircleButtonShaper());
jbStop = new JajukButton(ActionManager.getAction(STOP_TRACK));
jbStop.putClientProperty(SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY,
new RoundRectButtonShaper());
// Add items
setLayout(new MigLayout("insets 5 0 0 5", "[grow][grow][grow]"));
add(jtbWebRadio, "left,split 2,gapright 10");
add(jtbSpecial, "left,gapright 10");
add(jbStop, "center,split 7,width 40!,height 30,gapright 5!");
add(jbPrevious, "center,width 58!,height 30!,gapright 0");
add(jbPlayPause, "center,width 45!,height 45!,gapright 0");
add(jbNext, "center,width 58!,height 30!,gapright 3");
add(jbMute, "center,width 42!,height 30!,gapright 5");
add(jsVolume, "center,growx,width 25::100,gapright 3");
add(jlVolume, "width 40!,gapright 10");
add(jpModes, "right,split 2,gapright 5");
add(evaltoobar, "right");
// register to player events
ObservationManager.register(CommandJPanel.this);
// Update initial status
UtilFeatures.updateStatus(this);
}
/*
* (non-Javadoc)
*
* @see org.jajuk.events.Observer#getRegistrationKeys()
*/
@Override
public Set<JajukEvents> getRegistrationKeys() {
Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>();
eventSubjectSet.add(JajukEvents.PLAYER_STOP);
eventSubjectSet.add(JajukEvents.PLAYER_PLAY);
eventSubjectSet.add(JajukEvents.PLAYER_PAUSE);
eventSubjectSet.add(JajukEvents.PLAYER_RESUME);
eventSubjectSet.add(JajukEvents.SMART_FUNCTION_LAUNCHED);
eventSubjectSet.add(JajukEvents.ZERO);
eventSubjectSet.add(JajukEvents.MODE_STATUS_CHANGED);
eventSubjectSet.add(JajukEvents.CLEAR_HISTORY);
eventSubjectSet.add(JajukEvents.VOLUME_CHANGED);
eventSubjectSet.add(JajukEvents.DJS_CHANGE);
eventSubjectSet.add(JajukEvents.WEBRADIOS_CHANGE);
eventSubjectSet.add(JajukEvents.WEBRADIO_LAUNCHED);
eventSubjectSet.add(JajukEvents.PARAMETERS_CHANGE);
eventSubjectSet.add(JajukEvents.DEVICE_REFRESH);
return eventSubjectSet;
}
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(final ActionEvent ae) {
// do not run this in a separate thread because Player actions would die
// with the thread
try {
if (ae.getSource().equals(jmiNoveltiesModeSong)) {
Conf.setProperty(Const.CONF_NOVELTIES_MODE, Const.MODE_TRACK);
} else if (ae.getSource().equals(jmiNoveltiesModeAlbum)) {
Conf.setProperty(Const.CONF_NOVELTIES_MODE, Const.MODE_ALBUM);
} else if (ae.getSource().equals(jmiShuffleModeSong)) {
Conf.setProperty(Const.CONF_GLOBAL_RANDOM_MODE, Const.MODE_TRACK);
} else if (ae.getSource().equals(jmiShuffleModeAlbum)) {
Conf.setProperty(Const.CONF_GLOBAL_RANDOM_MODE, Const.MODE_ALBUM);
} else if (ae.getSource().equals(jmiShuffleModeAlbum2)) {
Conf.setProperty(Const.CONF_GLOBAL_RANDOM_MODE, Const.MODE_ALBUM2);
} else if (ae.getSource().equals(jmiShuffleModeAlbum2)) {
Conf.setProperty(Const.CONF_GLOBAL_RANDOM_MODE, Const.MODE_ALBUM2);
}
} catch (Exception e) {
Log.error(e);
}
}
/*
* @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent )
*/
/*
* (non-Javadoc)
*
* @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
*/
@Override
public void stateChanged(ChangeEvent e) {
if (e.getSource() == jsVolume) {
float newVolume = (float) jsVolume.getValue() / 100;
Player.setVolume(newVolume);
}
}
/*
* (non-Javadoc)
*
* @seejava.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event. MouseWheelEvent)
*/
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getSource() == jsVolume
|| (e.getSource() == jbMute && !Conf.getBoolean(Const.CONF_BIT_PERFECT))) {
int iOld = jsVolume.getValue();
float newVolume = ((float) (iOld - (e.getUnitsToScroll() * 3))) / 100;
Player.setVolume(newVolume);
}
}
/**
* Gets the current volume.
*
* @return Volume value
*/
public int getCurrentVolume() {
return this.jsVolume.getValue();
}
/*
* (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() {
JajukEvents subject = event.getSubject();
if (JajukEvents.PLAYER_PAUSE.equals(subject)) {
// Disable volume control when pausing to fix an mplayer
// issue:
// setting the volume resume the file
jsVolume.setEnabled(false);
jbMute.removeMouseWheelListener(CommandJPanel.this);
jsVolume.removeMouseWheelListener(CommandJPanel.this);
} else if (JajukEvents.PLAYER_RESUME.equals(subject)) {
// Enable the volume when resuming (fix a mplayer issue, see
// above)
jsVolume.setEnabled(!Conf.getBoolean(Const.CONF_BIT_PERFECT));
jbMute.addMouseWheelListener(CommandJPanel.this);
jsVolume.addMouseWheelListener(CommandJPanel.this);
} else if (JajukEvents.PLAYER_PLAY.equals(subject)) {
jsVolume.setEnabled(!Conf.getBoolean(Const.CONF_BIT_PERFECT));
} else if (JajukEvents.MODE_STATUS_CHANGED.equals(subject)) {
updateModesGUIStatus();
} else if (JajukEvents.VOLUME_CHANGED.equals(event.getSubject())) {
// Update volume GUI
jsVolume.removeChangeListener(CommandJPanel.this);
jbMute.removeMouseWheelListener(CommandJPanel.this);
jsVolume.removeMouseWheelListener(CommandJPanel.this);
jsVolume.setValue((int) (100 * Player.getCurrentVolume()));
String sVolume = (int) (100 * Player.getCurrentVolume()) + " %";
jsVolume.setToolTipText(sVolume);
jlVolume.setText(sVolume);
jbMute.setSelected(Player.isMuted());
jsVolume.addChangeListener(CommandJPanel.this);
jbMute.addMouseWheelListener(CommandJPanel.this);
jsVolume.addMouseWheelListener(CommandJPanel.this);
} else if (JajukEvents.DJS_CHANGE.equals(event.getSubject())) {
populateDJs();
// If no more DJ, change the tooltip
if (DigitalDJManager.getInstance().getDJs().size() == 0) {
JajukAction action = ActionManager.getAction(JajukActions.DJ);
action.setShortDescription(Messages.getString("CommandJPanel.18"));
}
} else if (JajukEvents.WEBRADIOS_CHANGE.equals(event.getSubject())) {
webRadioButton.populateWebRadios();
} else if (JajukEvents.WEBRADIO_LAUNCHED.equals(event.getSubject())) {
webRadioButton.populateWebRadios();
} else if (JajukEvents.PARAMETERS_CHANGE.equals(event.getSubject())) {
// Disable volume GUI in bit perfect mode
jsVolume.setEnabled(!Conf.getBoolean(Const.CONF_BIT_PERFECT));
} else if (JajukEvents.DEVICE_REFRESH.equals(event.getSubject())) {
webRadioButton.populateWebRadios();
}
}
});
}
/**
* Populate DJs.
*/
private void populateDJs() {
try {
ddbDDJ.setToolTipText(Const.HTML + Messages.getString("CommandJPanel.18") + Const.P_B
+ DigitalDJManager.getCurrentDJ() + "</b></html>");
popupDDJ.removeAll();
JMenuItem jmiNew = new JMenuItem(ActionManager.getAction(CONFIGURE_DJS));
popupDDJ.add(jmiNew);
Iterator<DigitalDJ> it = DigitalDJManager.getInstance().getDJs().iterator();
while (it.hasNext()) {
final DigitalDJ dj = it.next();
JCheckBoxMenuItem jmi = new JCheckBoxMenuItem(dj.getName(),
IconLoader.getIcon(JajukIcons.DIGITAL_DJ_16X16));
jmi.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
Conf.setProperty(Const.CONF_DEFAULT_DJ, dj.getID());
DigitalDJManager.setCurrentDJ(dj);
// force to reselect the item
populateDJs();
// update action tooltip on main button with right item
JajukAction action = ActionManager.getAction(JajukActions.DJ);
action.setShortDescription(Const.HTML + Messages.getString("CommandJPanel.18")
+ Const.P_B + dj.getName() + Const.B_P_HTML);
}
});
popupDDJ.add(jmi);
jmi.setSelected(Conf.getString(Const.CONF_DEFAULT_DJ).equals(dj.getID()));
}
} catch (Exception e) {
Log.error(e);
}
}
/**
* ToString() method.
*
* @return the string
*/
@Override
public String toString() {
return getClass().getName();
}
/**
* Update mode buttons after a mode change
*/
private void updateModesGUIStatus() {
this.jbRepeat.setSelected(Conf.getBoolean(Const.CONF_STATE_REPEAT));
this.jbRepeatAll.setSelected(Conf.getBoolean(Const.CONF_STATE_REPEAT_ALL));
this.jbContinue.setSelected(Conf.getBoolean(Const.CONF_STATE_CONTINUE));
this.jbShuffle.setSelected(Conf.getBoolean(Const.CONF_STATE_SHUFFLE));
}
}