/**
* ThisTime 2.0 (2008-03-30)
* Copyright 2007 Zach Scrivena
* zachscrivena@gmail.com
* http://thistime.sourceforge.net/
*
* Simple clock and timer program.
*
* 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 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) 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, see <http://www.gnu.org/licenses/>.
*/
package thistime;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
/**
* Clock unit.
*/
class ClockUnit
extends JFrame
{
/** timer refresh interval */
private static final long TIMER_REFRESH_INTERVAL_MILLISECONDS = 200L;
/** default "always on top" mode */
private static final boolean DEFAULT_ALWAYS_ON_TOP_MODE = true;
/** tooltip for clock display */
private static final String DISPLAY_TOOLTIP = "Right-click for clock options";
/** parent ThisTime object */
private final ThisTime parent;
/** clock display for this clock unit */
private final ClockDisplay display;
/** clock value to be displayed */
private final ClockValue value;
/** timer for refreshing clock value */
private final Timer timer;
/** true for 24-hour time format; false for 12-hour time format */
private volatile boolean hour24;
/** time zone for this clock */
private volatile TimeZone timeZone = TimeZone.getDefault();
/** "minimize to tray" mode (default is true) */
private boolean minimizeToTray = true;
/** tray icon for this clock unit */
private TrayIcon trayIcon;
/** "Time Zone Selector" form */
private TimeZoneSelector timeZoneSelector = null;
/**
* Constructor.
* This method must run on the EDT.
*
* @param parent
* parent ThisTime object
* @param title
* title of this clock unit
* @param hour24
* true for 24-hour time format; false for 12-hour time format
*/
ClockUnit(
final ThisTime parent,
final String title,
final boolean hour24)
{
/*********************
* INITIALIZE FIELDS *
*********************/
this.parent = parent;
this.hour24 = hour24;
value = new ClockValue();
display = new ClockDisplay(value);
/*****************************
* CONFIGURE FORM COMPONENTS *
*****************************/
setTitle(title);
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
closeClock();
}
@Override
public void windowDeiconified(WindowEvent e)
{
restoreClock();
}
@Override
public void windowIconified(WindowEvent e)
{
minimizeClock();
}
});
/* set "always on top" mode */
try
{
setAlwaysOnTop(DEFAULT_ALWAYS_ON_TOP_MODE);
}
catch (Exception e)
{
/* ignore */
}
/* set frame icon */
Image iconImage = null;
try
{
iconImage = ImageIO.read(ClockUnit.class.getResource("/thistime/resources/clock_red.png"));
setIconImage(iconImage);
}
catch (Exception e)
{
SwingManipulator.showErrorDialog(
null,
"Initialization Error - " + getTitle(),
"Failed to load clock icon (" + e + ").\n" +
"This clock will proceed to run without displaying the clock icon.");
}
/* prepare tray icon */
try
{
trayIcon = new TrayIcon(iconImage, getTitle());
trayIcon.setImageAutoSize(true);
trayIcon.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
restoreClock();
}
});
}
catch (Exception e)
{
trayIcon = null;
SwingManipulator.showErrorDialog(
null,
"Initialization Error - " + getTitle(),
"Failed to load clock tray icon (" + e + ").\n" +
"This clock will proceed to run without displaying the clock tray icon.");
}
/* popup menu (Set Title, Enable Alarm, Show Buttons, Always on Top, Minimize to Tray) */
final JPopupMenu popupMenu = new JPopupMenu();
/* popup menu: "Set Title..." */
final JMenuItem titleMenuItem = new JMenuItem("Set Title...", 't');
titleMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
String newTitle = JOptionPane.showInputDialog(
ClockUnit.this,
"Clock title:",
"Set Title - " + getTitle(),
JOptionPane.QUESTION_MESSAGE);
if (newTitle == null)
{
return;
}
newTitle = newTitle.trim();
if (newTitle.isEmpty())
{
newTitle = title;
}
setTitle(newTitle);
if (trayIcon != null)
{
trayIcon.setToolTip(newTitle);
}
}
});
popupMenu.add(titleMenuItem);
/* popup menu: "24-hour Format" */
final JMenuItem hour24MenuItem = new JCheckBoxMenuItem("24-hour Format", this.hour24);
hour24MenuItem.setMnemonic('f');
hour24MenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
ClockUnit.this.hour24 = hour24MenuItem.isSelected();
}
});
popupMenu.add(hour24MenuItem);
/* popup menu: "Select Time Zone..." */
final JMenuItem zoneMenuItem = new JMenuItem("Select Time Zone...");
zoneMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if (timeZoneSelector == null)
{
timeZoneSelector = new TimeZoneSelector(ClockUnit.this);
}
timeZoneSelector.setVisible(true);
timeZoneSelector.setExtendedState(JFrame.NORMAL);
timeZoneSelector.toFront();
}
});
popupMenu.add(zoneMenuItem);
popupMenu.addSeparator();
/* popup menu: "Always on Top" */
final JMenuItem topMenuItem = new JCheckBoxMenuItem("Always on Top", isAlwaysOnTop());
topMenuItem.setMnemonic('t');
topMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
final boolean alwaysOnTop = topMenuItem.isSelected();
setAlwaysOnTop(alwaysOnTop);
if (timeZoneSelector != null)
{
timeZoneSelector.setAlwaysOnTop(alwaysOnTop);
}
}
});
popupMenu.add(topMenuItem);
/* popup menu: "Minimize to Tray" */
final JMenuItem trayMenuItem = new JCheckBoxMenuItem("Minimize to Tray", minimizeToTray);
trayMenuItem.setMnemonic('t');
trayMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
minimizeToTray = trayMenuItem.isSelected();
}
});
popupMenu.add(trayMenuItem);
/* add popup menu to clock display */
display.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
processMouseEvent(e);
}
@Override
public void mouseReleased(MouseEvent e)
{
processMouseEvent(e);
}
private void processMouseEvent(
final MouseEvent e)
{
if (e.isPopupTrigger())
{
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
});
/* display tooltip */
display.setToolTipText(DISPLAY_TOOLTIP);
/* key binding: ESCAPE key */
display.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "ESCAPE_MINIMIZE");
display.getActionMap().put("ESCAPE_MINIMIZE", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
minimizeClock();
}
});
/* layout frame components */
setLayout(new BorderLayout());
final Container c = getContentPane();
c.add(display, BorderLayout.CENTER);
setPreferredSize(new Dimension(350, 200));
pack();
/* center form on the screen */
setLocationRelativeTo(null);
/***************************
* INITIALIZE TIMER THREAD *
***************************/
timer = new Timer("Clock-Unit-Timer", true);
timer.schedule(new TimerTask()
{
/** new clock value computed during refreshing */
private final ClockValue newValue = new ClockValue();
/**
* Refresh clock value.
*/
@Override
public void run()
{
/* compute new clock value */
final Calendar calendar = Calendar.getInstance(timeZone, Locale.ENGLISH);
newValue.hour24 = ClockUnit.this.hour24;
newValue.h = calendar.get(Calendar.HOUR_OF_DAY);
newValue.m = calendar.get(Calendar.MINUTE);
newValue.s = calendar.get(Calendar.SECOND);
newValue.am = (newValue.h < 12);
if (!newValue.hour24)
{
if (newValue.h == 0)
{
newValue.h = 12;
}
else if (newValue.h > 12)
{
newValue.h -= 12;
}
}
/* compare against displayed clock value */
synchronized (value)
{
if (!value.equals(newValue))
{
value.hour24 = newValue.hour24;
value.am = newValue.am;
value.h = newValue.h;
value.m = newValue.m;
value.s = newValue.s;
display.repaint();
}
}
}
}, 0L, TIMER_REFRESH_INTERVAL_MILLISECONDS);
}
/**
* Set the time zone for this clock unit.
*/
void setTimeZone(
final TimeZone tz)
{
timeZone = tz;
}
/**
* Close this clock unit.
*/
private void closeClock()
{
if (timeZoneSelector != null)
{
timeZoneSelector.setVisible(false);
timeZoneSelector.dispose();
}
parent.removeClock(this);
setVisible(false);
timer.cancel();
dispose();
}
/**
* Minimize this clock unit.
*/
void minimizeClock()
{
setExtendedState(JFrame.ICONIFIED);
if (minimizeToTray && (trayIcon != null))
{
try
{
final SystemTray systemTray = SystemTray.getSystemTray();
boolean alreadyInTray = false;
for (TrayIcon t : systemTray.getTrayIcons())
{
if (t.equals(trayIcon))
{
alreadyInTray = true;
break;
}
}
if (!alreadyInTray)
{
systemTray.add(trayIcon);
}
setVisible(false);
}
catch (Exception e)
{
SwingManipulator.showErrorDialog(
this,
"Error - " + getTitle(),
"Failed to minimize clock to tray (" + e + ").\n" +
"This clock will continue to stay on the desktop.");
}
}
}
/**
* Restore this clock unit.
*/
void restoreClock()
{
setVisible(true);
setExtendedState(JFrame.NORMAL);
toFront();
if (trayIcon != null)
{
try
{
SystemTray.getSystemTray().remove(trayIcon);
}
catch (Exception e)
{
/* ignore */
}
}
}
}