/*
* 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.windows;
import static org.jajuk.ui.actions.JajukActions.MUTE_STATE;
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_TRACK;
import static org.jajuk.ui.actions.JajukActions.QUEUE_TO_SLIM;
import static org.jajuk.ui.actions.JajukActions.STOP_TRACK;
import ext.DropDownButton;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.JToolBar;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import org.jajuk.base.File;
import org.jajuk.events.JajukEvent;
import org.jajuk.events.JajukEvents;
import org.jajuk.events.ObservationManager;
import org.jajuk.events.Observer;
import org.jajuk.services.players.Player;
import org.jajuk.services.players.QueueModel;
import org.jajuk.ui.actions.ActionManager;
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.views.QueueView;
import org.jajuk.ui.widgets.AmbienceComboBox;
import org.jajuk.ui.widgets.JajukButton;
import org.jajuk.ui.widgets.JajukInformationDialog;
import org.jajuk.ui.widgets.JajukJToolbar;
import org.jajuk.ui.widgets.PreferenceToolbar;
import org.jajuk.ui.widgets.SearchBox;
import org.jajuk.ui.widgets.SizedButton;
import org.jajuk.ui.widgets.WebRadioButton;
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.UtilString;
import org.jajuk.util.error.JajukException;
import org.jajuk.util.log.Log;
/**
* Jajuk Slim Interface
* <p>
* Singleton
* </p>
* .
*/
public final class JajukSlimbar extends JFrame implements IJajukWindow, Observer,
MouseWheelListener, ActionListener {
/** Generated serialVersionUID. */
private static final long serialVersionUID = 1L;
/** A queue view to be used by the show queue view slimbar switch button. Don't use it directly, use the getQueueWindow() method instead that lazy-load it. */
private JWindow queueViewWindow;
private JButton jbInfo;
private SizedButton jbPrevious;
private SizedButton jbNext;
private SizedButton jbPlayPause;
private SizedButton jbStop;
private PreferenceToolbar preferences;
private DropDownButton jddbSmart;
private JPopupMenu jpmSmart;
private JMenuItem jbBestof;
private JMenuItem jbNovelties;
private JMenuItem jbRandom;
private SizedButton jbFinishAlbum;
private JButton jbMaximize;
private SizedButton jbVolume;
private SearchBox sbSearch;
private JToolBar slimJajuk;
private JToolBar jtbPlay;
private DropDownButton webRadioButton;
private String title = "";
/** State decorator. */
private WindowStateDecorator decorator;
JajukInformationDialog balloon;
private static JajukSlimbar self;
/**
* Gets the single instance of JajukSlimbar.
*
* @return single instance of JajukSlimbar
*/
public static synchronized JajukSlimbar getInstance() {
if (self == null) {
self = new JajukSlimbar();
self.decorator = new WindowStateDecorator(self) {
@Override
public void specificAfterHidden() {
// Nothing particular to do here
}
@Override
public void specificBeforeHidden() {
// Nothing particular to do here
}
@Override
public void specificAfterShown() {
// Need focus for keystrokes
self.requestFocus();
}
@Override
public void specificBeforeShown() {
self.pack();
}
};
}
return self;
}
/*
* (non-Javadoc)
*
* @see org.jajuk.events.Observer#getRegistrationKeys()
*/
@Override
public Set<JajukEvents> getRegistrationKeys() {
Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>();
eventSubjectSet.add(JajukEvents.FILE_LAUNCHED);
eventSubjectSet.add(JajukEvents.WEBRADIO_LAUNCHED);
eventSubjectSet.add(JajukEvents.QUEUE_NEED_REFRESH);
eventSubjectSet.add(JajukEvents.PLAYER_STOP);
eventSubjectSet.add(JajukEvents.PARAMETERS_CHANGE);
return eventSubjectSet;
}
/**
* Sets the display queue.
*
* @param display the new display queue
*/
public void setDisplayQueue(boolean display) {
if (display) {
// Set position of queue dialog. We display the queue window either above or bellow the slimbar
// according to remaining vertical space.
int yLocation = this.getLocation().y;
if (this.getLocation().y + queueViewWindow.getSize().height + this.getSize().height > Toolkit
.getDefaultToolkit().getScreenSize().getHeight()) {
yLocation -= queueViewWindow.getSize().height;
} else {
yLocation += this.getSize().height;
}
getQueueWindow().setLocation(this.getLocation().x, yLocation);
getQueueWindow().setSize(this.getSize().width, queueViewWindow.getSize().height);
}
getQueueWindow().setVisible(display);
Conf.setProperty(Const.CONF_SLIMBAR_DISPLAY_QUEUE, Boolean.toString(isDisplayQueue()));
}
/**
* Checks if is display queue.
*
* @return true, if is display queue
*/
public boolean isDisplayQueue() {
return getQueueWindow().isVisible();
}
/*
* (non-Javadoc)
*
* @see org.jajuk.ui.widgets.JajukWindow#getWindowStateDecorator()
*/
@Override
public WindowStateDecorator getWindowStateDecorator() {
return decorator;
}
/** This mouse motion listener allows the whole slim bar dragging. */
private final MouseMotionAdapter motionAdapter = new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
Point point = e.getLocationOnScreen();
// compute coordinates of the event relative to the frame, not the screen
// so we can compensate the frame size to avoid it to jump when applying
// the new location
Point relativePoint = SwingUtilities.convertPoint(((JComponent) e.getSource()).getParent(),
((JComponent) e.getSource()).getLocation(), JajukSlimbar.this.getRootPane());
point = new Point((int) (point.getX() - relativePoint.getX()),
(int) (point.getY() - relativePoint.getY()));
setLocation(point);
setDisplayQueue(isDisplayQueue());
Conf.setProperty(Const.CONF_SLIMBAR_POSITION, (int) point.getX() + "," + (int) point.getY());
}
};
private SizedButton jbQueue;
/**
* Instantiates a new jajuk slimbar.
*/
private JajukSlimbar() {
setUndecorated(true);
setAlwaysOnTop(true);
}
/*
* (non-Javadoc)
*
* @see org.jajuk.ui.windows.IJajukWindow#initUI()
*/
@Override
public void initUI() {
// Instanciate the PlayerStateMediator to listen for player basic controls
PlayerStateMediator.getInstance();
setIconImage(IconLoader.getIcon(JajukIcons.LOGO).getImage());
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
try {
ActionManager.getAction(JajukActions.EXIT).perform(null);
} catch (Exception e1) {
Log.error(e1);
}
}
});
jbInfo = new JButton(IconLoader.getIcon(JajukIcons.INFO));
jbInfo.addActionListener(this);
// Listen for dragging
jbInfo.addMouseMotionListener(motionAdapter);
// Listen for balloon displaying
jbInfo.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
super.mouseMoved(e);
showBalloon();
}
});
jtbPlay = new JajukJToolbar();
jbPrevious = new SizedButton(ActionManager.getAction(PREVIOUS_TRACK), false);
jbPrevious.addMouseMotionListener(motionAdapter);
// 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);
}
}
});
jbNext = new SizedButton(ActionManager.getAction(NEXT_TRACK), false);
jbNext.addMouseMotionListener(motionAdapter);
jbPlayPause = new SizedButton(ActionManager.getAction(PAUSE_RESUME_TRACK), false);
jbPlayPause.addMouseMotionListener(motionAdapter);
jbStop = new SizedButton(ActionManager.getAction(STOP_TRACK), false);
jbStop.addMouseMotionListener(motionAdapter);
jbQueue = new SizedButton(ActionManager.getAction(QUEUE_TO_SLIM), false);
jbQueue.addMouseMotionListener(motionAdapter);
jtbPlay.add(jbPrevious);
jtbPlay.add(jbPlayPause);
jtbPlay.add(jbStop);
jtbPlay.add(jbNext);
jtbPlay.add(jbQueue);
JToolBar jtbSmart = new JajukJToolbar();
jddbSmart = new DropDownButton(IconLoader.getIcon(JajukIcons.INC_RATING)) {
private static final long serialVersionUID = 1L;
@Override
protected JPopupMenu getPopupMenu() {
return jpmSmart;
}
};
jbBestof = new JMenuItem(ActionManager.getAction(JajukActions.BEST_OF));
jbBestof.setIcon(IconLoader.getIcon(JajukIcons.BESTOF_16X16));
jbBestof.addActionListener(this);
jbNovelties = new JMenuItem(ActionManager.getAction(JajukActions.NOVELTIES));
jbNovelties.setIcon(IconLoader.getIcon(JajukIcons.NOVELTIES_16X16));
jbNovelties.addActionListener(this);
jbRandom = new JMenuItem(ActionManager.getAction(JajukActions.SHUFFLE_GLOBAL));
jbRandom.setIcon(IconLoader.getIcon(JajukIcons.SHUFFLE_GLOBAL_16X16));
jbRandom.addActionListener(this);
jpmSmart = new JPopupMenu();
jpmSmart.add(jbRandom);
jpmSmart.add(jbBestof);
jpmSmart.add(jbNovelties);
jddbSmart.addToToolBar(jtbSmart);
jddbSmart.addMouseMotionListener(motionAdapter);
if (JajukActions.SHUFFLE_GLOBAL.toString()
.equals(Conf.getString(Const.CONF_SLIMBAR_SMART_MODE))) {
jddbSmart.setAction(ActionManager.getAction(JajukActions.SHUFFLE_GLOBAL));
jddbSmart.setIcon(IconLoader.getIcon(JajukIcons.SHUFFLE_GLOBAL_16X16));
} else if (JajukActions.BEST_OF.toString()
.equals(Conf.getString(Const.CONF_SLIMBAR_SMART_MODE))) {
jddbSmart.setAction(ActionManager.getAction(JajukActions.BEST_OF));
jddbSmart.setIcon(IconLoader.getIcon(JajukIcons.BESTOF_16X16));
} else if (JajukActions.NOVELTIES.toString().equals(
Conf.getString(Const.CONF_SLIMBAR_SMART_MODE))) {
jddbSmart.setAction(ActionManager.getAction(JajukActions.NOVELTIES));
jddbSmart.setIcon(IconLoader.getIcon(JajukIcons.NOVELTIES_16X16));
}
preferences = new PreferenceToolbar();
jtbSmart.add(preferences);
JToolBar jtbTools = new JajukJToolbar();
int iVolume = (int) (100 * Conf.getFloat(Const.CONF_VOLUME));
if (iVolume > 100) { // can occur in some undefined cases
iVolume = 100;
}
jbVolume = new SizedButton(ActionManager.getAction(MUTE_STATE), false);
jbVolume.addMouseMotionListener(motionAdapter);
jbVolume.addMouseWheelListener(this);
jbVolume.setText(null);
jbVolume.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
super.mouseMoved(e);
// Request focus to allow volume change
jbVolume.requestFocus();
toFront();
}
});
MuteAction.setVolumeIcon(iVolume);
jbMaximize = new JajukButton(ActionManager.getAction(JajukActions.SLIM_JAJUK));
jbMaximize.addMouseMotionListener(motionAdapter);
jtbTools.add(jbVolume);
jtbTools.addSeparator();
jtbTools.add(jbMaximize);
jtbTools.add(new SizedButton(ActionManager.getAction(JajukActions.EXIT), false));
// Continue
jbFinishAlbum = new SizedButton(ActionManager.getAction(JajukActions.FINISH_ALBUM));
// Search
sbSearch = new SearchBox();
sbSearch.setPreferredSize(new Dimension(75, 20));
sbSearch.setMaximumSize(new Dimension(75, 20));
sbSearch.addMouseMotionListener(motionAdapter);
slimJajuk = new JajukJToolbar();
AmbienceComboBox ambienceCombo = new AmbienceComboBox();
ambienceCombo.setPreferredSize(new Dimension(42, 20));
ambienceCombo.addMouseMotionListener(motionAdapter);
// Webradio button
webRadioButton = new WebRadioButton(IconLoader.getIcon(JajukIcons.WEBRADIO_16X16));
JToolBar jtbWebRadio = new JajukJToolbar();
webRadioButton.addToToolBar(jtbWebRadio);
slimJajuk.add(Box.createHorizontalStrut(4));
slimJajuk.add(jbInfo);
slimJajuk.addSeparator();
slimJajuk.add(sbSearch);
slimJajuk.addSeparator();
slimJajuk.add(jtbWebRadio);
slimJajuk.add(jtbSmart);
slimJajuk.add(jbFinishAlbum);
slimJajuk.addSeparator();
slimJajuk.add(ambienceCombo);
slimJajuk.addSeparator();
slimJajuk.add(jtbPlay);
slimJajuk.addSeparator();
slimJajuk.add(jtbTools);
slimJajuk.add(Box.createHorizontalStrut(2));
slimJajuk.setBorder(BorderFactory.createRaisedBevelBorder());
getRootPane().setToolTipText(getPlayerInfo());
add(slimJajuk);
ObservationManager.register(this);
getRootPane().setWindowDecorationStyle(JRootPane.NONE);
updateCurrentTitle();
setVisible(true);
setAlwaysOnTop(true);
// Set location
String lastPosition = Conf.getString(Const.CONF_SLIMBAR_POSITION);
try {
StringTokenizer st = new StringTokenizer(lastPosition, ",");
int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());
Point point = new Point(x, y);
// Note that setLocation handle odd positions and fix them
setLocation(point);
} catch (Exception e) {
Log.debug("Cannot restore slimbar position");
Log.error(e);
}
// Force initial message refresh
UtilFeatures.updateStatus(this);
// Install global keystrokes
WindowGlobalKeystrokeManager.getInstance();
}
/**
* Returns or create a queue window to be displayed though the queue view slimbar button.
*
* @return the queue window
*/
private JWindow getQueueWindow() {
if (queueViewWindow == null) {
QueueView queueView = new QueueView();
queueView.initUI();
queueView.setBorder(BorderFactory.createLineBorder(Color.BLACK));
queueViewWindow = new JWindow(this);
queueViewWindow.getContentPane().add(queueView);
queueViewWindow.pack();
// Set position of queue dialog
queueViewWindow.setLocation(this.getLocation().x, this.getLocation().y
+ this.getSize().height);
queueViewWindow.setSize(this.getSize().width, queueViewWindow.getSize().height / 2);
queueViewWindow.setVisible(Conf.getBoolean(Const.CONF_SLIMBAR_DISPLAY_QUEUE));
}
return queueViewWindow;
}
/**
* Update current title.
*/
private void updateCurrentTitle() {
File file = QueueModel.getPlayingFile();
if (QueueModel.isPlayingRadio()) {
title = QueueModel.getCurrentRadio().getName();
} else if (file != null && !QueueModel.isStopped()) {
title = QueueModel.getPlayingFileTitle();
} else {
title = Messages.getString("JajukWindow.18");
}
setTitle(title);
}
/**
* Gets the player info.
*
* @return Player Info : current and next track
*/
public String getPlayerInfo() {
try {
String currentTrack = QueueModel.getPlayingFileTitle();
String nextFileTitle = "";
File nextFile;
try {
nextFile = QueueModel.getItem(QueueModel.getIndex() + 1).getFile();
} catch (Exception e) {
nextFile = QueueModel.getPlanned().get(0).getFile();
}
String pattern = Conf.getString(Const.CONF_PATTERN_FRAME_TITLE);
try {
nextFileTitle = UtilString.applyPattern(nextFile, pattern, false, false);
} catch (JajukException e) {
Log.error(e);
}
return " | Playing: " + currentTrack + " | Next: " + nextFileTitle;
} catch (Exception e) {
return Messages.getString("JajukWindow.17");
}
}
/*
* (non-Javadoc)
*
* @seejava.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event. MouseWheelEvent)
*/
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getSource().equals(jbVolume) && !Conf.getBoolean(Const.CONF_BIT_PERFECT)) {
int oldVolume = (int) (100 * Player.getCurrentVolume());
int newVolume = oldVolume - (e.getUnitsToScroll() * 3);
if (Player.isMuted()) {
Player.mute(false);
}
if (newVolume > 100) {
newVolume = 100;
} else if (newVolume < 0) {
newVolume = 0;
}
Player.setVolume((float) newVolume / 100);
jbVolume.setToolTipText(newVolume + " %");
// Force tooltip refresh live
hideToolTip(jbVolume);
postToolTip(jbVolume);
MuteAction.setVolumeIcon(newVolume);
}
}
/**
* Force tooltip refresh Thanks Santhosh Kumar
* http://www.jroller.com/santhosh/entry/tooltips_can_say_more
*
* @param comp
*/
public static void postToolTip(JComponent comp) {
Action action = comp.getActionMap().get("postTip");
if (action == null) { // no tooltip
return;
}
ActionEvent ae = new ActionEvent(comp, ActionEvent.ACTION_PERFORMED, "postTip",
EventQueue.getMostRecentEventTime(), 0);
action.actionPerformed(ae);
}
/**
* Remove tooltip Thanks Santhosh Kumar
* http://www.jroller.com/santhosh/entry/tooltips_can_say_more
*
* @param comp
*/
public static void hideToolTip(JComponent comp) {
Action action = comp.getActionMap().get("hideTip");
if (action == null) { // no tooltip
return;
}
ActionEvent ae = new ActionEvent(comp, ActionEvent.ACTION_PERFORMED, "hideTip",
EventQueue.getMostRecentEventTime(), 0);
action.actionPerformed(ae);
}
/*
* (non-Javadoc)
*
* @see org.jajuk.events.Observer#update(org.jajuk.events.JajukEvent)
*/
@Override
public void update(final JajukEvent event) {
// Update window title
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JajukEvents subject = event.getSubject();
if (JajukEvents.FILE_LAUNCHED.equals(subject)
|| JajukEvents.WEBRADIO_LAUNCHED.equals(subject)
|| JajukEvents.PLAYER_STOP.equals(subject)) {
updateCurrentTitle();
} else if (JajukEvents.PARAMETERS_CHANGE.equals(event.getSubject())) {
// Disable volume GUI in bit perfect mode
jbVolume.setEnabled(!Conf.getBoolean(Const.CONF_BIT_PERFECT));
}
}
});
}
/*
* (non-Javadoc)
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(final ActionEvent ae) {
if (ae.getSource() == jbBestof) {
jddbSmart.setAction(ActionManager.getAction(JajukActions.BEST_OF));
jddbSmart.setIcon(IconLoader.getIcon(JajukIcons.BESTOF_16X16));
Conf.setProperty(Const.CONF_SLIMBAR_SMART_MODE, JajukActions.BEST_OF.toString());
} else if (ae.getSource() == jbNovelties) {
jddbSmart.setAction(ActionManager.getAction(JajukActions.NOVELTIES));
jddbSmart.setIcon(IconLoader.getIcon(JajukIcons.NOVELTIES_16X16));
Conf.setProperty(Const.CONF_SLIMBAR_SMART_MODE, JajukActions.NOVELTIES.toString());
} else if (ae.getSource() == jbRandom) {
jddbSmart.setAction(ActionManager.getAction(JajukActions.SHUFFLE_GLOBAL));
jddbSmart.setIcon(IconLoader.getIcon(JajukIcons.SHUFFLE_GLOBAL_16X16));
Conf.setProperty(Const.CONF_SLIMBAR_SMART_MODE, JajukActions.SHUFFLE_GLOBAL.toString());
} else if (ae.getSource() == jbInfo) {
showBalloon();
}
}
/**
* Display the current playing album balloon when moving mouse over jbInfo or
* when clicking on it.
*/
private void showBalloon() {
// Leave if balloon already visible
if (balloon != null && balloon.isVisible()) {
return;
}
balloon = new JajukInformationDialog(QueueModel.getCurrentFileTitle());
Point buttonLocation = jbInfo.getLocationOnScreen();
Point location = null;
// If slimbar is too height in the screen, display the popup bellow it
if (buttonLocation.y < balloon.getHeight() + 10) {
location = new Point(buttonLocation.x, buttonLocation.y + 25);
} else {
location = new Point(buttonLocation.x, buttonLocation.y - (5 + balloon.getHeight()));
}
balloon.setLocation(location);
balloon.display();
}
/*
* (non-Javadoc)
*
* @see java.awt.Window#dispose()
*/
@Override
public void dispose() {
// there are some resources to close in the Search-Box that I could not get
// rid of with any of the default dispose-methods in Swing...
sbSearch.close();
super.dispose();
}
}