/**
* $RCSfile: ,v $
* $Revision: $
* $Date: $
*
* Copyright (C) 2004-2011 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.sparkimpl.plugin.systray;
import java.awt.MouseInfo;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import org.jivesoftware.Spark;
import org.jivesoftware.resource.Default;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smackx.ChatState;
import org.jivesoftware.spark.ChatManager;
import org.jivesoftware.spark.NativeHandler;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.plugin.Plugin;
import org.jivesoftware.spark.ui.PresenceListener;
import org.jivesoftware.spark.ui.status.CustomStatusItem;
import org.jivesoftware.spark.ui.status.StatusBar;
import org.jivesoftware.spark.ui.status.StatusItem;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.settings.local.LocalPreferences;
import org.jivesoftware.sparkimpl.settings.local.SettingsManager;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smackx.ChatStateListener;
public class SysTrayPlugin implements Plugin, NativeHandler, ChatManagerListener, ChatStateListener {
private static String MESSAGE_COUNTER_REG_EXP = "\\[\\d+\\] ";
private JPopupMenu popupMenu = new JPopupMenu();
private JMenuItem openMenu;
private JMenuItem minimizeMenu;
private JMenuItem exitMenu;
private JMenu statusMenu;
private JMenuItem logoutMenu;
private LocalPreferences pref = SettingsManager.getLocalPreferences();
private ImageIcon availableIcon;
private ImageIcon dndIcon;
private ImageIcon awayIcon;
private ImageIcon offlineIcon;
private ImageIcon connectingIcon;
private ImageIcon newMessageIcon;
private ImageIcon typingIcon;
private TrayIcon trayIcon;
private boolean newMessage = false;
ChatMessageHandlerImpl chatMessageHandler = new ChatMessageHandlerImpl();
@Override
public boolean canShutDown() {
return true;
}
@Override
public void initialize() {
if (SystemTray.isSupported()) {
openMenu = new JMenuItem(Res.getString("menuitem.open"));
minimizeMenu = new JMenuItem(Res.getString("menuitem.hide"));
exitMenu = new JMenuItem(Res.getString("menuitem.exit"));
statusMenu = new JMenu(Res.getString("menuitem.status"));
logoutMenu = new JMenuItem(
Res.getString("menuitem.logout.no.status"));
SystemTray tray = SystemTray.getSystemTray();
SparkManager.getNativeManager().addNativeHandler(this);
ChatManager.getInstance().addChatMessageHandler(chatMessageHandler);
//XEP-0085 suport (replaces the obsolete XEP-0022)
SparkManager.getConnection().getChatManager().addChatListener(this);
if (Spark.isLinux()) {
newMessageIcon = SparkRes
.getImageIcon(SparkRes.MESSAGE_NEW_TRAY_LINUX);
typingIcon = SparkRes.getImageIcon(SparkRes.TYPING_TRAY_LINUX);
} else {
newMessageIcon = SparkRes
.getImageIcon(SparkRes.MESSAGE_NEW_TRAY);
typingIcon = SparkRes.getImageIcon(SparkRes.TYPING_TRAY);
}
availableIcon = Default.getImageIcon(Default.TRAY_IMAGE);
if (Spark.isLinux()) {
if (availableIcon == null) {
availableIcon = SparkRes
.getImageIcon(SparkRes.TRAY_IMAGE_LINUX);
Log.error(availableIcon.toString());
}
awayIcon = SparkRes.getImageIcon(SparkRes.TRAY_AWAY_LINUX);
dndIcon = SparkRes.getImageIcon(SparkRes.TRAY_DND_LINUX);
offlineIcon = SparkRes
.getImageIcon(SparkRes.TRAY_OFFLINE_LINUX);
connectingIcon = SparkRes
.getImageIcon(SparkRes.TRAY_CONNECTING_LINUX);
} else {
if (availableIcon == null) {
availableIcon = SparkRes.getImageIcon(SparkRes.TRAY_IMAGE);
}
awayIcon = SparkRes.getImageIcon(SparkRes.TRAY_AWAY);
dndIcon = SparkRes.getImageIcon(SparkRes.TRAY_DND);
offlineIcon = SparkRes.getImageIcon(SparkRes.TRAY_OFFLINE);
connectingIcon = SparkRes
.getImageIcon(SparkRes.TRAY_CONNECTING);
}
popupMenu.add(openMenu);
openMenu.addActionListener(new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent event) {
SparkManager.getMainWindow().setVisible(true);
SparkManager.getMainWindow().toFront();
}
});
popupMenu.add(minimizeMenu);
minimizeMenu.addActionListener(new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent event) {
SparkManager.getMainWindow().setVisible(false);
}
});
popupMenu.addSeparator();
addStatusMessages();
popupMenu.add(statusMenu);
statusMenu.addActionListener(new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent event) {
}
});
if (Spark.isWindows()) {
if (!Default.getBoolean("DISABLE_EXIT"))
popupMenu.add(logoutMenu);
logoutMenu.addActionListener(new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
SparkManager.getMainWindow().logout(false);
}
});
}
// Exit Menu
exitMenu.addActionListener(new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
SparkManager.getMainWindow().shutdown();
}
});
if (!Default.getBoolean("DISABLE_EXIT"))
popupMenu.add(exitMenu);
/**
* If connection closed set offline tray image
*/
SparkManager.getConnection().addConnectionListener(
new ConnectionListener() {
@Override
public void connectionClosed() {
trayIcon.setImage(offlineIcon.getImage());
}
@Override
public void connectionClosedOnError(Exception arg0) {
trayIcon.setImage(offlineIcon.getImage());
}
@Override
public void reconnectingIn(int arg0) {
trayIcon.setImage(connectingIcon.getImage());
}
@Override
public void reconnectionSuccessful() {
trayIcon.setImage(availableIcon.getImage());
}
@Override
public void reconnectionFailed(Exception arg0) {
trayIcon.setImage(offlineIcon.getImage());
}
});
SparkManager.getSessionManager().addPresenceListener(
new PresenceListener() {
@Override
public void presenceChanged(Presence presence) {
if (presence.getMode() == Presence.Mode.available) {
trayIcon.setImage(availableIcon.getImage());
} else if (presence.getMode() == Presence.Mode.away
|| presence.getMode() == Presence.Mode.xa) {
trayIcon.setImage(awayIcon.getImage());
} else if (presence.getMode() == Presence.Mode.dnd) {
trayIcon.setImage(dndIcon.getImage());
} else {
trayIcon.setImage(availableIcon.getImage());
}
}
});
try {
trayIcon = new TrayIcon(availableIcon.getImage(),
Default.getString(Default.APPLICATION_NAME), null);
trayIcon.setImageAutoSize(true);
trayIcon.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent event) {
// if we are using double click on tray icon
if ( (!pref.isUsingSingleTrayClick()
&& event.getButton() == MouseEvent.BUTTON1
&& event.getClickCount() % 2 == 0)
||
// if we using single click on tray icon
(pref.isUsingSingleTrayClick()
&& event.getButton() == MouseEvent.BUTTON1
&& event.getClickCount() == 1)) {
// bring the mainwindow to front
if (SparkManager.getMainWindow().isVisible()) {
SparkManager.getMainWindow().setVisible(false);
} else {
SparkManager.getMainWindow().setVisible(true);
SparkManager.getMainWindow().toFront();
}
} else if (event.getButton() == MouseEvent.BUTTON1) {
SparkManager.getMainWindow().toFront();
// SparkManager.getMainWindow().requestFocus();
} else if (event.getButton() == MouseEvent.BUTTON3) {
if (popupMenu.isVisible()) {
popupMenu.setVisible(false);
} else {
double x = MouseInfo.getPointerInfo()
.getLocation().getX();
double y = MouseInfo.getPointerInfo()
.getLocation().getY();
if (Spark.isMac()) {
popupMenu.setLocation((int) x, (int) y);
} else {
popupMenu.setLocation(event.getX(),
event.getY());
}
popupMenu.setInvoker(popupMenu);
popupMenu.setVisible(true);
}
}
}
@Override
public void mouseEntered(MouseEvent event) {
}
@Override
public void mouseExited(MouseEvent event) {
}
@Override
public void mousePressed(MouseEvent event) {
// on Mac i would want the window to show when i left-click the Icon
if (Spark.isMac() && event.getButton()!=MouseEvent.BUTTON3) {
SparkManager.getMainWindow().setVisible(false);
SparkManager.getMainWindow().setVisible(true);
SparkManager.getMainWindow().requestFocusInWindow();
SparkManager.getMainWindow().bringFrameIntoFocus();
SparkManager.getMainWindow().toFront();
SparkManager.getMainWindow().requestFocus();
}
}
@Override
public void mouseReleased(MouseEvent event) {
}
});
tray.add(trayIcon);
} catch (Exception e) {
// Not Supported
}
} else {
Log.error("Tray don't supports on this platform.");
}
}
public void addStatusMessages() {
StatusBar statusBar = SparkManager.getWorkspace().getStatusBar();
for (Object o : statusBar.getStatusList()) {
final StatusItem statusItem = (StatusItem) o;
final AbstractAction action = new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
StatusBar statusBar = SparkManager.getWorkspace()
.getStatusBar();
SparkManager.getSessionManager().changePresence(
statusItem.getPresence());
statusBar.setStatus(statusItem.getText());
}
};
action.putValue(Action.NAME, statusItem.getText());
action.putValue(Action.SMALL_ICON, statusItem.getIcon());
boolean hasChildren = false;
for (Object aCustom : SparkManager.getWorkspace().getStatusBar()
.getCustomStatusList()) {
final CustomStatusItem cItem = (CustomStatusItem) aCustom;
String type = cItem.getType();
if (type.equals(statusItem.getText())) {
hasChildren = true;
}
}
if (!hasChildren) {
JMenuItem status = new JMenuItem(action);
statusMenu.add(status);
} else {
final JMenu status = new JMenu(action);
statusMenu.add(status);
status.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent mouseEvent) {
action.actionPerformed(null);
popupMenu.setVisible(false);
}
});
for (Object aCustom : SparkManager.getWorkspace()
.getStatusBar().getCustomStatusList()) {
final CustomStatusItem customItem = (CustomStatusItem) aCustom;
String type = customItem.getType();
if (type.equals(statusItem.getText())) {
AbstractAction customAction = new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
StatusBar statusBar = SparkManager
.getWorkspace().getStatusBar();
Presence oldPresence = statusItem.getPresence();
Presence presence = StatusBar
.copyPresence(oldPresence);
presence.setStatus(customItem.getStatus());
presence.setPriority(customItem.getPriority());
SparkManager.getSessionManager()
.changePresence(presence);
statusBar.setStatus(statusItem.getName()
+ " - " + customItem.getStatus());
}
};
customAction.putValue(Action.NAME,
customItem.getStatus());
customAction.putValue(Action.SMALL_ICON,
statusItem.getIcon());
JMenuItem menuItem = new JMenuItem(customAction);
status.add(menuItem);
}
}
}
}
}
@Override
public void shutdown() {
if (SystemTray.isSupported()) {
SystemTray tray = SystemTray.getSystemTray();
tray.remove(trayIcon);
}
ChatManager.getInstance().removeChatMessageHandler(chatMessageHandler);
}
@Override
public void uninstall() {
ChatManager.getInstance().removeChatMessageHandler(chatMessageHandler);
}
// Info on new Messages
@Override
public void flashWindow(Window window) {
if (pref.isSystemTrayNotificationEnabled()) {
trayIcon.setImage(newMessageIcon.getImage());
if (window instanceof JFrame) {
((JFrame) window).setTitle(getCounteredTitle(
((JFrame) window).getTitle(), chatMessageHandler.getUnreadMessages()));
}
newMessage = true;
}
}
private String getCounteredTitle(String title, int counter) {
String stringCounter = String.format("[%s] ", counter);
return counter > 0 ? stringCounter + title.replaceFirst(MESSAGE_COUNTER_REG_EXP, "") : title.replaceFirst(MESSAGE_COUNTER_REG_EXP, "");
}
@Override
public void flashWindowStopWhenFocused(Window window) {
trayIcon.setImage(availableIcon.getImage());
newMessage = false;
chatMessageHandler.clearUnreadMessages();
}
@Override
public boolean handleNotification() {
return true;
}
@Override
public void stopFlashing(Window window) {
trayIcon.setImage(availableIcon.getImage());
newMessage = false;
chatMessageHandler.clearUnreadMessages();
}
// For Typing
@Override
public void processMessage(Chat arg0, Message arg1) {
// Do nothing - stateChanged is in charge
}
@Override
public void stateChanged(Chat chat, ChatState state) {
if (ChatState.composing.equals(state)) {
changeSysTrayIcon();
} else {
if (!newMessage)
trayIcon.setImage(availableIcon.getImage());
else {
trayIcon.setImage(newMessageIcon.getImage());
}
}
}
@Override
public void chatCreated(Chat chat, boolean isLocal) {
chat.addMessageListener(this);
}
private void changeSysTrayIcon() {
if (pref.isTypingNotificationShown()) {
trayIcon.setImage(typingIcon.getImage());
}
}
}