/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.impl.osdependent.jdic;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.net.*;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
import javax.swing.event.*;
import net.java.sip.communicator.impl.osdependent.*;
import net.java.sip.communicator.impl.osdependent.systemtray.SystemTray;
import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon;
import net.java.sip.communicator.impl.osdependent.windows.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.systray.*;
import net.java.sip.communicator.service.systray.event.*;
import net.java.sip.communicator.util.Logger;
import org.apache.commons.lang3.tuple.Pair;
import org.jitsi.util.*;
import org.osgi.framework.*;
import com.apple.eawt.*;
/**
* The <tt>Systray</tt> provides a Icon and the associated <tt>TrayMenu</tt>
* in the system tray using the Jdic library.
*
* @author Nicolas Chamouard
* @author Yana Stamcheva
* @author Lyubomir Marinov
* @author Symphorien Wanko
* @author Pawel Domas
*/
public class SystrayServiceJdicImpl
extends AbstractSystrayService
{
/**
* The systray.
*/
private final SystemTray systray;
/**
* The icon in the system tray.
*/
private TrayIcon trayIcon;
/**
* The menu that spring with a right click.
*/
private Object menu;
/**
* The <tt>Logger</tt> used by the <tt>SystrayServiceJdicImpl</tt> class and
* its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(SystrayServiceJdicImpl.class);
/**
* The various icons used on the systray
*/
private ImageIcon currentIcon;
private ImageIcon logoIcon;
private ImageIcon logoIconOffline;
private ImageIcon logoIconAway;
private ImageIcon logoIconExtendedAway;
private ImageIcon logoIconFFC;
private ImageIcon logoIconDND;
private ImageIcon logoIconWhite;
/**
* The dock Icons used only in Mac version
*/
private URL dockIconOnline;
private URL dockIconOffline;
private URL dockIconAway;
private URL dockIconExtendedAway;
private URL dockIconFFC;
private URL dockIconDND;
private Image originalDockImage = null;
private boolean initialized = false;
/**
* The listener which gets notified about pop-up message events (e.g. clicks
* on the pop-up).
*/
private final SystrayPopupMessageListener popupMessageListener
= new SystrayPopupMessageListenerImpl();
/**
* Initializes a new <tt>SystrayServiceJdicImpl</tt> instance.
*/
public SystrayServiceJdicImpl()
{
super(OsDependentActivator.bundleContext);
SystemTray systray;
try
{
systray = SystemTray.getSystemTray();
}
catch (Throwable t)
{
if (t instanceof ThreadDeath)
throw (ThreadDeath) t;
else
{
systray = null;
if (!GraphicsEnvironment.isHeadless())
logger.error("Failed to create a systray!", t);
}
}
this.systray = systray;
if (this.systray != null)
{
initSystray();
}
}
@Override
public Map<String, String> getSystrayModes()
{
return new HashMap<String, String>()
{{
put("disabled", "service.systray.mode.DISABLED");
if (java.awt.SystemTray.isSupported())
{
put("native", "service.systray.mode.NATIVE");
}
if (!OSUtils.IS_MAC && !OSUtils.IS_WINDOWS)
{
put("appindicator",
"service.systray.mode.APPINDICATOR");
put("appindicator_static",
"service.systray.mode.APPINDICATOR_STATIC");
}
}};
}
@Override
public String getActiveSystrayMode()
{
return SystemTray.getSystemTrayMode();
}
/**
* Initializes the systray icon and related listeners.
*/
private void initSystray()
{
UIService uiService = OsDependentActivator.getUIService();
if (uiService == null)
{
/*
* Delay the call to the #initSystray() method until the UIService
* implementation becomes available.
*/
try
{
OsDependentActivator.bundleContext.addServiceListener(
new DelayedInitSystrayServiceListener(),
'('
+ Constants.OBJECTCLASS
+ '='
+ UIService.class.getName()
+ ')');
}
catch (InvalidSyntaxException ise)
{
/*
* Oh, it should not really happen. Besides, it is not clear at
* the time of this writing what is supposed to happen in the
* case of such an exception here.
*/
}
return;
}
Pair<Object, Object> createdMenu = TrayMenuFactory.createTrayMenu(
this,
systray.useSwingPopupMenu(),
systray.supportsDynamicMenu());
menu = createdMenu.getLeft();
boolean isMac = OSUtils.IS_MAC;
logoIcon = Resources.getImage("service.systray.TRAY_ICON_WINDOWS");
logoIconOffline = Resources.getImage(
"service.systray.TRAY_ICON_WINDOWS_OFFLINE");
logoIconAway = Resources.getImage(
"service.systray.TRAY_ICON_WINDOWS_AWAY");
logoIconExtendedAway = Resources.getImage(
"service.systray.TRAY_ICON_WINDOWS_EXTENDED_AWAY");
logoIconFFC = Resources.getImage(
"service.systray.TRAY_ICON_WINDOWS_FFC");
logoIconDND = Resources.getImage(
"service.systray.TRAY_ICON_WINDOWS_DND");
// If we're running under Mac OS X, we use special black and white
// icons without background.
if (isMac)
{
logoIcon = Resources.getImage("service.systray.TRAY_ICON_MACOSX");
logoIconWhite = Resources.getImage(
"service.systray.TRAY_ICON_MACOSX_WHITE");
}
/*
* Default to set offline , if any protocols become online will set it
* to online.
*/
currentIcon = isMac ? logoIcon : logoIconOffline;
trayIcon
= systray.createTrayIcon(
currentIcon,
Resources.getApplicationString(
"service.gui.APPLICATION_NAME"),
menu);
trayIcon.setIconAutoSize(true);
if (isMac)
{
// init dock Icons
dockIconOnline = Resources.getImageURL(
"service.systray.DOCK_ICON_ONLINE");
dockIconOffline = Resources.getImageURL(
"service.systray.DOCK_ICON_OFFLINE");
dockIconAway = Resources.getImageURL(
"service.systray.DOCK_ICON_AWAY");
dockIconExtendedAway = Resources.getImageURL(
"service.systray.DOCK_ICON_EXTENDED_AWAY");
dockIconFFC =
Resources.getImageURL("service.systray.DOCK_ICON_FFC");
dockIconDND =
Resources.getImageURL("service.systray.DOCK_ICON_DND");
}
//Show/hide the contact list when user clicks on the systray.
final Object defaultActionItem;
if (systray.useSwingPopupMenu())
{
defaultActionItem = ((JMenuItem) createdMenu.getRight());
}
else
{
defaultActionItem = ((MenuItem) createdMenu.getRight());
}
/*
* Change the Mac OS X icon with the white one when the pop-up menu
* appears.
*/
if (isMac)
{
TrayMenuFactory.addPopupMenuListener(
menu,
new PopupMenuListener()
{
public void popupMenuWillBecomeVisible(PopupMenuEvent e)
{
ImageIcon newIcon = logoIconWhite;
trayIcon.setIcon(newIcon);
currentIcon = newIcon;
}
public void popupMenuWillBecomeInvisible(
PopupMenuEvent e)
{
ImageIcon newIcon = logoIcon;
getTrayIcon().setIcon(newIcon);
currentIcon = newIcon;
}
public void popupMenuCanceled(PopupMenuEvent e)
{
popupMenuWillBecomeInvisible(e);
}
});
}
PopupMessageHandler pmh = null;
if (!isMac)
{
pmh = new PopupMessageHandlerTrayIconImpl(trayIcon);
addPopupHandler(pmh);
OsDependentActivator.bundleContext.registerService(
PopupMessageHandler.class.getName(),
pmh,
null);
}
initHandlers();
/*
* Either we have an incorrect configuration value or the default pop-up
* handler is not available yet. We will use the available pop-up
* handler and will automatically switch to the configured one when it
* becomes available. We will be aware of it since we listen for new
* registered services in the BundleContext.
*/
if ((getActivePopupMessageHandler() == null) && (pmh != null))
setActivePopupMessageHandler(pmh);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
systray.addTrayIcon(trayIcon);
trayIcon.setDefaultAction(defaultActionItem);
}
});
initialized = true;
uiService.setMainWindowCanHide(true);
}
/**
* Sets a new Systray icon.
*
* @param imageType the type of the image to set.
*/
public void setSystrayIcon(int imageType)
{
if (!checkInitialized())
return;
boolean isMac = OSUtils.IS_MAC;
ImageIcon systrayIconToSet = null;
switch (imageType)
{
case SystrayService.SC_IMG_TYPE:
systrayIconToSet
= (isMac && TrayMenuFactory.isVisible(menu))
? logoIconWhite
: logoIcon;
break;
case SystrayService.SC_IMG_OFFLINE_TYPE:
if (!isMac)
systrayIconToSet = logoIconOffline;
break;
case SystrayService.SC_IMG_AWAY_TYPE:
if (!isMac)
systrayIconToSet = logoIconAway;
break;
case SystrayService.SC_IMG_EXTENDED_AWAY_TYPE:
if (!isMac)
systrayIconToSet = logoIconExtendedAway;
break;
case SystrayService.SC_IMG_FFC_TYPE:
if (!isMac)
systrayIconToSet = logoIconFFC;
break;
case SystrayService.SC_IMG_DND_TYPE:
if (!isMac)
systrayIconToSet = logoIconDND;
break;
}
if (systrayIconToSet != null)
{
this.trayIcon.setIcon(systrayIconToSet);
this.currentIcon = systrayIconToSet;
}
if (isMac)
{
URL dockIconURLToSet;
switch (imageType)
{
case SystrayService.SC_IMG_TYPE:
dockIconURLToSet = dockIconOnline;
break;
case SystrayService.SC_IMG_OFFLINE_TYPE:
dockIconURLToSet = dockIconOffline;
break;
case SystrayService.SC_IMG_AWAY_TYPE:
dockIconURLToSet = dockIconAway;
break;
case SystrayService.SC_IMG_EXTENDED_AWAY_TYPE:
dockIconURLToSet = dockIconExtendedAway;
break;
case SystrayService.SC_IMG_FFC_TYPE:
dockIconURLToSet = dockIconFFC;
break;
case SystrayService.SC_IMG_DND_TYPE:
dockIconURLToSet = dockIconDND;
break;
default:
dockIconURLToSet = null;
break;
}
try
{
Application application = Application.getApplication();
if (originalDockImage == null)
originalDockImage = application.getDockIconImage();
if (dockIconURLToSet != null)
{
application.setDockIconImage(
Toolkit.getDefaultToolkit().getImage(
dockIconURLToSet));
}
else if (originalDockImage != null)
{
application.setDockIconImage(originalDockImage);
}
}
catch (Exception e)
{
logger.error("failed to change dock icon", e);
}
}
}
@Override
public boolean checkInitialized()
{
return initialized;
}
/**
* Set the number of pending notifications to the the application icon
* (Dock on OSX, TaskBar on Windows, nothing on Linux currently).
*/
@Override
public void setNotificationCount(int count)
{
if (OSUtils.IS_MAC)
{
Application application = Application.getApplication();
application.setDockIconBadge(new Integer(count).toString());
}
else if (OSUtils.IS_WINDOWS)
{
UIService uiService = OsDependentActivator.getUIService();
if (uiService == null)
{
return;
}
ExportedWindow mainWindow =
uiService.getExportedWindow(ExportedWindow.MAIN_WINDOW);
if (mainWindow == null
|| !(mainWindow.getSource() instanceof Component))
{
return;
}
BufferedImage img = null;
if (count > 0)
{
img = createOverlayImage(new Integer(count).toString());
}
try
{
TaskBarList3.getInstance().SetOverlayIcon(
(Component) mainWindow.getSource(), img, null);
}
catch (Exception ex)
{
logger.error("Could not set the notification count.", ex);
}
}
}
private BufferedImage createOverlayImage(String text)
{
int size = 16;
BufferedImage image =
new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//background
g.setPaint(new Color(0, 0, 0, 102));
g.fillRoundRect(0, 0, size, size, size, size);
//filling
int mainRadius = 14;
g.setPaint(new Color(255, 98, 89));
g.fillRoundRect(size / 2 - mainRadius / 2, size / 2 - mainRadius / 2,
mainRadius, mainRadius, size, size);
//text
Font font = g.getFont();
g.setFont(new Font(font.getName(), Font.BOLD, 9));
FontMetrics fontMetrics = g.getFontMetrics();
int textWidth = fontMetrics.stringWidth(text);
g.setColor(Color.white);
g.drawString(text, size / 2 - textWidth / 2,
size / 2 - fontMetrics.getHeight() / 2 + fontMetrics.getAscent());
return image;
}
/**
* @return the trayIcon
*/
public TrayIcon getTrayIcon()
{
return trayIcon;
}
/**
* Set the handler which will be used for popup message
* @param newHandler the handler to set. providing a null handler is like
* disabling popup.
* @return the previously used popup handler
*/
public PopupMessageHandler setActivePopupMessageHandler(
PopupMessageHandler newHandler)
{
PopupMessageHandler oldHandler = getActivePopupHandler();
if (oldHandler != null)
oldHandler.removePopupMessageListener(popupMessageListener);
if (newHandler != null)
newHandler.addPopupMessageListener(popupMessageListener);
return super.setActivePopupMessageHandler(newHandler);
}
/** our listener for popup message click */
private static class SystrayPopupMessageListenerImpl
implements SystrayPopupMessageListener
{
/**
* Handles a user click on a systray popup message. If the popup
* notification was the result of an incoming message from a contact,
* the chat window with that contact will be opened, if not already, and
* brought to front.
*
* @param evt the event triggered when user clicks on a systray popup
* message
*/
public void popupMessageClicked(SystrayPopupMessageEvent evt)
{
Object o = evt.getTag();
if (o instanceof Contact)
OsDependentActivator.getUIService().
getChat((Contact) o).setChatVisible(true);
}
}
/**
* Implements a <tt>ServiceListener</tt> which waits for an
* <tt>UIService</tt> implementation to become available, invokes
* {@link #initSystray()} and unregisters itself.
*/
private class DelayedInitSystrayServiceListener
implements ServiceListener
{
public void serviceChanged(ServiceEvent serviceEvent)
{
if (serviceEvent.getType() == ServiceEvent.REGISTERED)
{
UIService uiService = OsDependentActivator.getUIService();
if (uiService != null)
{
/*
* This ServiceListener has successfully waited for an
* UIService implementation to become available so it no
* longer need to listen.
*/
OsDependentActivator.bundleContext.removeServiceListener(
this);
if (!initialized && systray != null)
initSystray();
}
}
}
}
}