/*
* 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 ext.JXTrayIcon;
import java.awt.AWTException;
import java.awt.Point;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
import org.jajuk.events.JajukEvent;
import org.jajuk.events.JajukEvents;
import org.jajuk.events.ObservationManager;
import org.jajuk.services.dj.Ambience;
import org.jajuk.services.dj.AmbienceManager;
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.FontManager;
import org.jajuk.ui.helpers.FontManager.JajukFont;
import org.jajuk.ui.helpers.JajukMouseAdapter;
import org.jajuk.ui.helpers.PlayerStateMediator;
import org.jajuk.ui.widgets.CommandJPanel;
import org.jajuk.ui.widgets.JajukInformationDialog;
import org.jajuk.ui.widgets.SearchBox;
import org.jajuk.ui.widgets.SizedJMenuItem;
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.UtilSystem;
import org.jajuk.util.log.Log;
/**
* Jajuk systray <br>
* Extends CommandJPanel for volume slider heritage only.
*/
public class JajukSystray extends CommandJPanel implements IJajukWindow {
/** Generated serialVersionUID. */
private static final long serialVersionUID = 1L;
/** State decorator. */
private WindowStateDecorator decorator;
// Systray variables
SystemTray stray;
JXTrayIcon trayIcon;
JPopupMenu jmenu;
JMenuItem jmiExit;
JMenuItem jmiMute;
JMenuItem jmiShuffle;
JMenuItem jmiBestof;
JMenuItem jmiDJ;
JMenuItem jmiNovelties;
JMenuItem jmiFinishAlbum;
JMenuItem jmiPlayPause;
JMenuItem jmiStop;
JMenuItem jmiPrevious;
JMenuItem jmiNext;
JMenu jmAmbience;
/** Self instance singleton. */
private static JajukSystray jsystray;
/** HTML Tooltip. */
JajukInformationDialog balloon;
/** Window type the tray has hidden on click if any */
private int lastHiddenDisplayMode = Const.DISPLAY_MODE_MAIN_WINDOW;
/**
* Checks if is loaded.
*
* @return whether the tray is loaded
*/
public static boolean isLoaded() {
return (jsystray != null);
}
/**
* Gets the instance.
*
* @return singleton
*/
public static synchronized JajukSystray getInstance() {
if (jsystray == null) {
jsystray = new JajukSystray();
jsystray.decorator = new WindowStateDecorator(jsystray) {
@Override
public void specificAfterHidden() {
if (jsystray != null) {
jsystray.closeSystray();
jsystray = null;
}
}
@Override
public void specificBeforeHidden() {
// Nothing particular to do here
}
@Override
public void specificAfterShown() {
// Force initial message refresh
UtilFeatures.updateStatus(jsystray);
}
@Override
public void specificBeforeShown() {
// Nothing particular to do here
}
};
}
return jsystray;
}
/*
* (non-Javadoc)
*
* @see org.jajuk.ui.widgets.JajukWindow#getWindowStateDecorator()
*/
@Override
public WindowStateDecorator getWindowStateDecorator() {
return decorator;
}
/**
* Systray constructor.
*/
public JajukSystray() {
super();
if (SystemTray.isSupported()) {
stray = SystemTray.getSystemTray();
}
}
/*
* (non-Javadoc)
*
* @see org.jajuk.ui.widgets.CommandJPanel#initUI()
*/
@Override
public final void initUI() {
// Instanciate the PlayerStateMediator to listen for player basic controls
PlayerStateMediator.getInstance();
jmenu = new JPopupMenu(Messages.getString("JajukWindow.3"));
jmiExit = new JMenuItem(ActionManager.getAction(JajukActions.EXIT));
// force icon to be display in 16x16
jmiMute = new SizedJMenuItem(ActionManager.getAction(JajukActions.MUTE_STATE));
jmiMute.addMouseWheelListener(this);
jmiShuffle = new SizedJMenuItem(ActionManager.getAction(JajukActions.SHUFFLE_GLOBAL));
jmiBestof = new SizedJMenuItem(ActionManager.getAction(JajukActions.BEST_OF));
jmiDJ = new SizedJMenuItem(ActionManager.getAction(JajukActions.DJ));
jmiFinishAlbum = new SizedJMenuItem(ActionManager.getAction(JajukActions.FINISH_ALBUM));
jmiNovelties = new SizedJMenuItem(ActionManager.getAction(JajukActions.NOVELTIES));
jmiPlayPause = new SizedJMenuItem(ActionManager.getAction(JajukActions.PAUSE_RESUME_TRACK));
jmiStop = new SizedJMenuItem(ActionManager.getAction(JajukActions.STOP_TRACK));
jmiPrevious = new SizedJMenuItem(ActionManager.getAction(JajukActions.PREVIOUS_TRACK));
jmiNext = new SizedJMenuItem(ActionManager.getAction(JajukActions.NEXT_TRACK));
JLabel jlTitle = new JLabel("Jajuk");
jlTitle.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
showMainOrSlimbarWindow(e);
}
}
});
jlTitle.setFont(FontManager.getInstance().getFont(JajukFont.BOLD_TITLE));
SearchBox searchBox = new SearchBox();
JPanel jpTitle = new JPanel(new MigLayout("ins 5", "[][grow]"));
jpTitle.add(jlTitle, "left,gapx 20px");
jpTitle.add(searchBox, "right,grow");
// Ambiences menu
Ambience defaultAmbience = AmbienceManager.getInstance().getSelectedAmbience();
jmAmbience = new JMenu(Messages.getString("JajukWindow.36")
+ " "
+ ((defaultAmbience == null) ? Messages.getString("DigitalDJWizard.64")
: defaultAmbience.getName()));
populateAmbiences();
jmenu.add(jpTitle);
jmenu.addSeparator();
jmenu.add(jmAmbience);
jmenu.addSeparator();
jmenu.add(jmiPlayPause);
jmenu.add(jmiStop);
jmenu.add(jmiPrevious);
jmenu.add(jmiNext);
jmenu.addSeparator();
jmenu.add(jmiShuffle);
jmenu.add(jmiBestof);
jmenu.add(jmiDJ);
jmenu.add(jmiNovelties);
jmenu.add(jmiFinishAlbum);
jmenu.addSeparator();
jmenu.add(jmiMute);
jmenu.addSeparator();
jmenu.add(jmiExit);
// Add a row under Linux to fix an issue : sometimes, when left-clicking on
// the tray, the exit menu item is executed and then close Jajuk accidently
if (UtilSystem.isUnderLinux()) {
jmenu.add("");
}
trayIcon = new JXTrayIcon(IconLoader.getIcon(JajukIcons.TRAY).getImage());
if (!UtilSystem.isUnderLinux()) {
// auto-resize looks OK under Windows but is ugly under Linux/KDE
trayIcon.setImageAutoSize(true);
}
trayIcon.addMouseMotionListener(new MouseMotionAdapter() {
long dateLastMove = 0;
@Override
public void mouseMoved(MouseEvent e) {
// [PERF] Consider only a single event per second
if (System.currentTimeMillis() - dateLastMove < 1000) {
return;
}
dateLastMove = System.currentTimeMillis();
String title = QueueModel.getCurrentFileTitle();
if (balloon != null && balloon.isVisible()) {
return;
}
balloon = new JajukInformationDialog(title);
Point location = new Point(e.getX() - balloon.getWidth(), e.getY()
- (20 + balloon.getHeight()));
balloon.setLocation(location);
balloon.display();
}
});
trayIcon.setJPopuMenu(jmenu);
// Note that under OSX, popup gesture recognition is inverted : a left click return true
if (UtilSystem.isUnderOSX()) {
// Don't use a JajukMouseAdapter here because tray has specific behavior under OSX
trayIcon.addMouseListener(new MouseAdapter() {
// Under OSX, the event to consider is PRESSED, not RELEASED,
// see http://developer.apple.com/mac/library/documentation/Java/Conceptual/Java14Development/07-NativePlatformIntegration/NativePlatformIntegration.html
@Override
public void mousePressed(MouseEvent e) {
if (!e.isPopupTrigger()) { //we invert here because it is a systray item
// popup gesture recognized, display the jdialog
trayIcon.showJPopupMenu(e);
} else {
showMainOrSlimbarWindow(e);
}
}
});
} else {
trayIcon.addMouseListener(new JajukMouseAdapter() {
@Override
public void handleActionSingleClick(MouseEvent e) {
showMainOrSlimbarWindow(e);
}
@Override
public void handlePopup(final MouseEvent e) {
trayIcon.showJPopupMenu(e);
}
});
}
try {
stray.add(trayIcon);
} catch (AWTException e) {
Log.error(e);
return;
}
// Register needed events
ObservationManager.register(this);
}
/**
* Invert current window visibility with a left click on the tray icon.
*
* @param e
*/
private void showMainOrSlimbarWindow(MouseEvent e) {
int displayMode = Conf.getInt(Const.CONF_STARTUP_DISPLAY);
// We don't want to hide/show the tray itself by only the
if (displayMode == Const.DISPLAY_MODE_TRAY) {
displayMode = lastHiddenDisplayMode;
} else {
lastHiddenDisplayMode = displayMode;
}
WindowStateDecorator windowDecorator = getWindowStateDecoratorByDisplayMode(displayMode);
boolean bShouldDisplay = false;
// Check the CONF_TRAY_CLICK_DISPLAY_WINDOW option
if (Conf.getBoolean(Const.CONF_TRAY_CLICK_DISPLAY_WINDOW)) {
bShouldDisplay = true;
} else {
// Invert visibility for the current window
bShouldDisplay = (windowDecorator.getWindowState() != WindowState.BUILT_DISPLAYED)
// force display if the window was minimalized
|| windowDecorator.isMinimalized();
}
windowDecorator.display(bShouldDisplay);
//Make sure to bring the window to front
if (bShouldDisplay) {
windowDecorator.toFront();
}
}
/**
* Return the window decorator for given mode value or main window decorator if unknown mode
* @param display mode
* @return the window decorator for given mode value
*/
private WindowStateDecorator getWindowStateDecoratorByDisplayMode(int mode) {
WindowStateDecorator windowDecorator = null;
if (mode == Const.DISPLAY_MODE_MAIN_WINDOW) {
windowDecorator = JajukMainWindow.getInstance().getWindowStateDecorator();
} else if (mode == Const.DISPLAY_MODE_TRAY) {
windowDecorator = JajukSystray.getInstance().getWindowStateDecorator();
} else if (mode == Const.DISPLAY_MODE_SLIMBAR_TRAY) {
windowDecorator = JajukSlimbar.getInstance().getWindowStateDecorator();
} else if (mode == Const.DISPLAY_MODE_FULLSCREEN) {
windowDecorator = JajukFullScreenWindow.getInstance().getWindowStateDecorator();
} else {
windowDecorator = JajukMainWindow.getInstance().getWindowStateDecorator();
}
return windowDecorator;
}
/*
* (non-Javadoc)
*
* @see org.jajuk.ui.widgets.CommandJPanel#getRegistrationKeys()
*/
@Override
public Set<JajukEvents> getRegistrationKeys() {
Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>();
eventSubjectSet.add(JajukEvents.ZERO);
eventSubjectSet.add(JajukEvents.FILE_LAUNCHED);
eventSubjectSet.add(JajukEvents.PLAYER_PLAY);
eventSubjectSet.add(JajukEvents.PLAYER_STOP);
eventSubjectSet.add(JajukEvents.AMBIENCES_CHANGE);
eventSubjectSet.add(JajukEvents.AMBIENCES_SELECTION_CHANGE);
return eventSubjectSet;
}
/*
* (non-Javadoc)
*
* @see org.jajuk.ui.Observer#update(java.lang.String)
*/
@Override
public final void update(final JajukEvent event) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JajukEvents subject = event.getSubject();
if (JajukEvents.FILE_LAUNCHED.equals(subject)) {
jmiPlayPause.setEnabled(true);
jmiStop.setEnabled(true);
jmiNext.setEnabled(true);
jmiPrevious.setEnabled(true);
jmiFinishAlbum.setEnabled(true);
} else if (JajukEvents.PLAYER_STOP.equals(subject)) {
// Enable the play button to allow restarting the queue but disable if
// the queue is void
boolean bQueueNotVoid = (QueueModel.getQueue().size() > 0);
jmiPlayPause.setEnabled(bQueueNotVoid);
jmiNext.setEnabled(bQueueNotVoid);
jmiPrevious.setEnabled(bQueueNotVoid);
jmiStop.setEnabled(false);
jmiFinishAlbum.setEnabled(false);
} else if (JajukEvents.ZERO.equals(subject)) {
jmiPlayPause.setEnabled(false);
jmiStop.setEnabled(false);
jmiNext.setEnabled(false);
jmiPrevious.setEnabled(false);
jmiFinishAlbum.setEnabled(false);
} else if (JajukEvents.PLAYER_PLAY.equals(subject)) {
jmiPlayPause.setEnabled(true);
jmiStop.setEnabled(true);
jmiNext.setEnabled(true);
jmiFinishAlbum.setEnabled(true);
} else if (JajukEvents.VOLUME_CHANGED.equals(event.getSubject())) {
JajukSystray.super.update(event);
} else if (JajukEvents.AMBIENCES_CHANGE.equals(subject)
|| JajukEvents.AMBIENCES_SELECTION_CHANGE.equals(subject)) {
Ambience ambience = AmbienceManager.getInstance().getSelectedAmbience();
if (ambience != null) {
jmAmbience.setText(Messages.getString("JajukWindow.36") + " "
+ AmbienceManager.getInstance().getSelectedAmbience().getName());
} else {
jmAmbience.setText(Messages.getString("JajukWindow.37"));
}
populateAmbiences();
}
}
});
}
/**
* Hide systray.
*/
public void closeSystray() {
if (stray != null && trayIcon != null) {
stray.remove(trayIcon);
}
}
/**
* Populate ambiences.
*/
final void populateAmbiences() {
// Ambience selection listener
ActionListener al = new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
JMenuItem jmi = (JMenuItem) ae.getSource();
// Selected 'Any" ambience
JMenuItem all = jmAmbience.getItem(0);
if (jmi.equals(all)) {
// reset default ambience
Conf.setProperty(Const.CONF_DEFAULT_AMBIENCE, "");
} else {// Selected an ambience
Ambience ambience = AmbienceManager.getInstance().getAmbienceByName(
jmi.getActionCommand());
Conf.setProperty(Const.CONF_DEFAULT_AMBIENCE, ambience.getID());
}
jmi.setFont(FontManager.getInstance().getFont(JajukFont.BOLD));
ObservationManager.notify(new JajukEvent(JajukEvents.AMBIENCES_SELECTION_CHANGE));
}
};
// Remove all item
jmAmbience.removeAll();
// Add "all" ambience
JMenuItem jmiAll = new JMenuItem("<html><i>" + Messages.getString("DigitalDJWizard.64")
+ "</i></html>");
jmiAll.setFont(FontManager.getInstance().getFont(JajukFont.BOLD));
jmiAll.addActionListener(al);
jmAmbience.add(jmiAll);
// Add available ambiences
for (Ambience ambience : AmbienceManager.getInstance().getAmbiences()) {
JMenuItem jmi = new JMenuItem(ambience.getName());
if (Conf.getString(Const.CONF_DEFAULT_AMBIENCE).equals(ambience.getID())) {
jmiAll.setFont(FontManager.getInstance().getFont(JajukFont.PLAIN));
jmi.setFont(FontManager.getInstance().getFont(JajukFont.BOLD));
}
jmi.addActionListener(al);
jmAmbience.add(jmi);
}
}
/**
* Gets the menu.
*
* @return the menu
*/
public JPopupMenu getMenu() {
return this.jmenu;
}
/**
* Gets the tray icon.
*
* @return the trayIcon
*/
public TrayIcon getTrayIcon() {
return this.trayIcon;
}
/*
* (non-Javadoc)
*
* @seejava.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event. MouseWheelEvent)
*/
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getSource().equals(jmiMute) && !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);
MuteAction.setVolumeIcon(newVolume);
}
}
}