/**
* GmailAssistant 1.1 (2008-03-16)
* Copyright 2008 Zach Scrivena
* zachscrivena@gmail.com
* http://gmailassistant.sourceforge.net/
*
* Notifier for multiple Gmail accounts.
*
* TERMS AND CONDITIONS:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package gmailassistant;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.TrayIcon;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
/**
* Represent a simple system tray icon.
*/
class SimpleTrayIcon
extends TrayIcon
{
/** timer refresh interval */
private static final long TIMER_REFRESH_INTERVAL_MILLISECONDS = 200L;
/** number of iterations to blink */
private static final int BLINK_ITERATIONS = 5;
/** blinking interval in milliseconds */
private static final long BLINK_INTERVAL_MILLISECONDS = 200L;
/** parent frame */
private final JFrame parent;
/** timer for displaying the system tray icon */
private final Timer timer;
/** is the timer cancelled? */
private volatile boolean timerCancelled = false;
/** queued actions to be performed by the timer */
private final Queue<ActionType> actions = new ArrayDeque<ActionType>();
/**
* Constructor.
*
* @param parent
* parent GmailAssistant object
* @throws java.io.IOException
* if an I/O error occurs while reading the icon image resources
*/
SimpleTrayIcon(
final JFrame parent)
throws IOException
{
super(ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/blank.png")));
this.parent = parent;
setToolTip(GmailAssistant.NAME);
setImageAutoSize(true);
final Dimension d = getSize();
final int w = Math.max(d.height, d.width);
final String suffix = (w <= 16) ? "16" : "24";
final Image blankIcon = ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/blank.png"));
final Image normalIcon = ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/ga_tray_normal_" + suffix + ".png"));
final Image hotIcon = ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/ga_tray_hot_" + suffix + ".png"));
final Image normalErrorIcon = ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/ga_tray_normal_error_" + suffix + ".png"));
final Image hotErrorIcon = ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/ga_tray_hot_error_" + suffix + ".png"));
setNormalIcon();
/***************************
* INITIALIZE TIMER THREAD *
***************************/
this.timer = new Timer("Simple-Tray-Icon-Timer", true);
this.timer.schedule(new TimerTask()
{
/** temporary icon used for blinking effect */
private Image tempIcon;
/** "normal" state of icon */
private boolean normalState = true;
/** "error" state of icon */
private boolean errorState = false;
@Override
public void run()
{
ActionType action;
synchronized (SimpleTrayIcon.this.actions)
{
action = SimpleTrayIcon.this.actions.poll();
}
if (action == ActionType.NORMAL)
{
normalState = true;
}
else if (action == ActionType.HOT)
{
normalState = false;
}
else if (action == ActionType.SET_ERROR)
{
errorState = true;
}
else if (action == ActionType.CLEAR_ERROR)
{
errorState = false;
}
else if (action == ActionType.BLINK)
{
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
tempIcon = SimpleTrayIcon.this.getImage();
}
});
for (int i = 0; i < SimpleTrayIcon.BLINK_ITERATIONS; i++)
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
SimpleTrayIcon.this.setImage(blankIcon);
}
});
Thread.sleep(SimpleTrayIcon.BLINK_INTERVAL_MILLISECONDS);
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
SimpleTrayIcon.this.setImage(tempIcon);
}
});
Thread.sleep(SimpleTrayIcon.BLINK_INTERVAL_MILLISECONDS);
}
}
catch (Exception e)
{
/* ignore */
}
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
SimpleTrayIcon.this.setImage(tempIcon);
}
});
}
catch (Exception e)
{
/* ignore */
}
}
else if (action == ActionType.TERMINATE)
{
SimpleTrayIcon.this.timer.cancel();
SimpleTrayIcon.this.timerCancelled = true;
}
if ((action == ActionType.NORMAL) ||
(action == ActionType.HOT) ||
(action == ActionType.SET_ERROR) ||
(action == ActionType.CLEAR_ERROR))
{
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
SimpleTrayIcon.this.setImage(pickIcon(normalState, errorState));
}
});
}
catch (Exception e)
{
/* ignore */
}
}
}
/**
* Return the icon corresponding to the specified "normal" and "error" states.
*/
private Image pickIcon(
final boolean normal,
final boolean error)
{
if (normal)
{
return (error) ? normalErrorIcon : normalIcon;
}
else
{
return (error) ? hotErrorIcon : hotIcon;
}
}
}, 0L, SimpleTrayIcon.TIMER_REFRESH_INTERVAL_MILLISECONDS);
}
/**
* Set the tray icon to the "normal" state.
*/
void setNormalIcon()
{
synchronized (this.actions)
{
this.actions.add(ActionType.NORMAL);
}
}
/**
* Set the tray icon to the "hot" state.
*/
void setHotIcon()
{
synchronized (this.actions)
{
this.actions.add(ActionType.HOT);
}
}
/**
* Set the tray icon to the "error" state.
*/
void setErrorIcon()
{
synchronized (this.actions)
{
this.actions.add(ActionType.SET_ERROR);
}
}
/**
* Clear the "error" state of the tray icon.
*/
void clearErrorIcon()
{
synchronized (this.actions)
{
this.actions.add(ActionType.CLEAR_ERROR);
}
}
/**
* Blink the tray icon.
*/
void blinkIcon()
{
synchronized (this.actions)
{
this.actions.add(ActionType.BLINK);
}
}
/**
* Terminate the timer thread.
* This method blocks until the thread is cancelled.
*/
void terminate()
{
synchronized (this.actions)
{
this.actions.clear();
}
do
{
synchronized (this.actions)
{
this.actions.add(ActionType.TERMINATE);
}
if (this.timerCancelled)
{
return;
}
try
{
Thread.sleep(SimpleTrayIcon.TIMER_REFRESH_INTERVAL_MILLISECONDS / 10);
}
catch (Exception e)
{
/* ignore */
}
}
while (true);
}
/*****************
* INNER CLASSES *
*****************/
/**
* Types of action.
*/
private enum ActionType
{
NORMAL,
HOT,
SET_ERROR,
CLEAR_ERROR,
BLINK,
TERMINATE
}
}