/**
* 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.AWTException;
import java.awt.Color;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
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.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
/**
* Notifier for multiple Gmail accounts.
* This is the main class of the program.
*/
public class GmailAssistant
extends JFrame
implements ListSelectionListener
{
/** program name */
static final String NAME = "GmailAssistant";
/** program version */
static final String VERSION = "1.1";
/** program date */
static final String DATE = "2008-03-16";
/** URL for homepage */
static final String HOMEPAGE = "http://gmailassistant.sourceforge.net/";
/** program description text, to be shown in "about" dialog */
static final String DESCRIPTION =
GmailAssistant.NAME + " " +
GmailAssistant.VERSION + " (" +
GmailAssistant.DATE + ")\n" +
"Copyright 2008 Zach Scrivena\n" +
"zachscrivena@gmail.com\n" +
GmailAssistant.HOMEPAGE;
/** URL for latest release information */
static final String LATEST_RELEASE_URL =
"http://gmailassistant.sourceforge.net/latest.txt";
/** URL for bug reporting */
static final String BUG_REPORT =
"http://sourceforge.net/tracker/?func=add&group_id=214554&atid=1030145";
/** default filename for profile file */
static final String PROFILE_DEFAULT_FILENAME = "profile";
/** filename extension for profile files */
static final String PROFILE_FILENAME_EXTENSION = "ga";
/** map: key string ---> property */
static final Map<String,Property> KEY_STRING_TO_PROPERTY = new HashMap<String,Property>();
/** delimiter string for separating different property key:value pairs */
static final String PROPERTY_DELIMITER = "\n";
/** email accounts */
final List<Account> accounts = new ArrayList<Account>();
/** table model for table of accounts */
private final AccountsTableModel accountsTableModel = new AccountsTableModel();
/** "Load Profile" form */
private ProfileLoader profileLoader = null;
/** "Save Profile" form */
private ProfileSaver profileSaver = null;
/** "Options" form */
Options options = null;
/** "About" form */
private About about = null;
/** tray icon for program */
final SimpleTrayIcon trayIcon;
/** popup alerter */
final DesktopPopup popup;
/** keyboard LED blinker */
final KeyboardLedBlinker led;
/** chime player */
final ChimePlayer chime;
/** last used account ID */
private int lastId;
/** mutex lock for this.lastId */
private final Object lastIdLock = new Object();
/** program properties */
private final Map<Property,Object> properties;
/**
* Static initialization block.
*/
static
{
/* create map: key string ---> property */
for (Property p : Property.values())
{
if (p.keyString != null)
{
GmailAssistant.KEY_STRING_TO_PROPERTY.put(p.keyString, p);
}
}
}
/**
* Constructor.
*/
public GmailAssistant()
{
/*********************************
* INITIALIZE PROGRAM PROPERTIES *
*********************************/
this.properties = new HashMap<Property,Object>();
for (Property p : Property.values())
{
properties.put(p, p.initialVal);
}
/*********************
* INITIALIZE ALERTS *
*********************/
this.popup = new DesktopPopup(this);
this.chime = new ChimePlayer(this);
this.led = new KeyboardLedBlinker();
/******************************
* INITIALIZE FORM COMPONENTS *
******************************/
initComponents();
/*****************************
* CONFIGURE FORM COMPONENTS *
*****************************/
setTitle(GmailAssistant.NAME);
setAlwaysOnTop((Boolean) getProperty(Property.ALWAYS_ON_TOP));
addWindowListener(new WindowAdapter()
{
@Override
public void windowIconified(WindowEvent e)
{
minimizeProgram();
}
@Override
public void windowDeiconified(WindowEvent e)
{
restoreProgram();
}
@Override
public void windowClosing(WindowEvent e)
{
closeProgram();
}
});
/* tooltips */
ToolTipManager.sharedInstance().setInitialDelay(0);
/* menu item: "Load Profiles..." */
this.loadMenuItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if (GmailAssistant.this.profileLoader == null)
{
GmailAssistant.this.profileLoader = new ProfileLoader(GmailAssistant.this);
}
GmailAssistant.this.profileLoader.showForm();
}
});
/* menu item: "Save Profile..." */
this.saveMenuItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if (GmailAssistant.this.profileSaver == null)
{
GmailAssistant.this.profileSaver = new ProfileSaver(GmailAssistant.this);
}
GmailAssistant.this.profileSaver.showForm();
}
});
/* menu item: "Exit" */
this.exitMenuItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
closeProgram();
}
});
/* menu item: "Options..." */
this.optionsMenuItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if (GmailAssistant.this.options == null)
{
GmailAssistant.this.options = new Options(GmailAssistant.this);
}
GmailAssistant.this.options.setVisible(true);
GmailAssistant.this.options.setExtendedState(JFrame.NORMAL);
GmailAssistant.this.options.toFront();
}
});
/* menu item: "Usage..." */
this.usageMenuItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
try
{
SwingManipulator.showInfoDialog(GmailAssistant.this,
"Usage Information - " + GmailAssistant.NAME,
"Usage information for " + GmailAssistant.NAME,
ResourceManipulator.resourceAsString("/gmailassistant/resources/usage_text.txt"),
10);
}
catch (Exception ee)
{
SwingManipulator.showErrorDialog(GmailAssistant.this, getTitle(),
"(INTERNAL) Failed to read \"Usage\" text.");
}
}
});
/* menu item: "About..." */
this.aboutMenuItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if (GmailAssistant.this.about == null)
{
GmailAssistant.this.about = new About(GmailAssistant.this);
}
GmailAssistant.this.about.setVisible(true);
GmailAssistant.this.about.setExtendedState(JFrame.NORMAL);
GmailAssistant.this.about.toFront();
}
});
/* button: "Add" */
this.addButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
addNewAccount(null);
valueChanged(null);
}
});
/* button: "Remove" */
this.removeButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
removeAccounts();
valueChanged(null);
}
});
/* button: "Edit" */
this.editButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
editAccounts();
valueChanged(null);
}
});
/* button: "Enable" */
this.enableButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
enableAccounts();
valueChanged(null);
}
});
/* button: "Disable" */
this.disableButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
disableAccounts();
valueChanged(null);
}
});
/* key binding: ESCAPE key */
this.accountsPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "ESCAPE_CANCEL_BUTTON");
this.accountsPane.getActionMap().put("ESCAPE_CANCEL_BUTTON", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
GmailAssistant.this.setExtendedState(JFrame.ICONIFIED);
}
});
/* key binding: DELETE key */
this.accountsPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "DELETE_REMOVE_BUTTON");
this.accountsPane.getActionMap().put("DELETE_REMOVE_BUTTON", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
GmailAssistant.this.removeButton.doClick();
}
});
/* table of accounts */
this.accountsTable.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
final TableColumnModel colModel = this.accountsTable.getColumnModel();
colModel.getColumn(0).setMinWidth(0);
colModel.getColumn(1).setMinWidth(0);
colModel.getColumn(2).setMinWidth(0);
colModel.getColumn(3).setMinWidth(0);
colModel.getColumn(0).setPreferredWidth(5);
colModel.getColumn(1).setPreferredWidth(80);
colModel.getColumn(2).setPreferredWidth(40);
colModel.getColumn(3).setPreferredWidth(200);
this.accountsTable.setToolTipText("Mouse-over any row for more information");
this.accountsTable.getSelectionModel().addListSelectionListener(this);
this.accountsTable.setDefaultRenderer(TableCellContent.class, new AccountsTableCellRenderer(
this.accountsTable.getForeground(),
this.accountsTable.getBackground(),
this.accountsTable.getSelectionForeground(),
this.accountsTable.getSelectionBackground()));
/* accounts popup menu (add, remove, edit, enable, disable) */
final JPopupMenu accountsPopupMenu = new JPopupMenu();
/* accounts popup menu: "Add" */
final JMenuItem addMenuItem = new JMenuItem("Add", 'A');
addMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
GmailAssistant.this.addButton.doClick();
}
});
accountsPopupMenu.add(addMenuItem);
/* accounts popup menu: "Remove" */
final JMenuItem removeMenuItem = new JMenuItem("Remove", 'R');
removeMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
GmailAssistant.this.removeButton.doClick();
}
});
accountsPopupMenu.add(removeMenuItem);
/* accounts popup menu: "Edit" */
final JMenuItem editMenuItem = new JMenuItem("Edit", 't');
editMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
GmailAssistant.this.editButton.doClick();
}
});
accountsPopupMenu.add(editMenuItem);
/* accounts popup menu: "Enable" */
final JMenuItem enableMenuItem = new JMenuItem("Enable", 'E');
enableMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
GmailAssistant.this.enableButton.doClick();
}
});
accountsPopupMenu.add(enableMenuItem);
/* accounts popup menu: "Disable" */
final JMenuItem disableMenuItem = new JMenuItem("Disable", 'D');
disableMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
GmailAssistant.this.disableButton.doClick();
}
});
accountsPopupMenu.add(disableMenuItem);
accountsPopupMenu.addSeparator();
/* accounts popup menu: "Select All" */
final JMenuItem selectAllMenuItem = new JMenuItem("Select All", 'S');
selectAllMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
GmailAssistant.this.accountsTable.selectAll();
}
});
accountsPopupMenu.add(selectAllMenuItem);
/* accounts popup menu: "Clear All" */
final JMenuItem clearAllMenuItem = new JMenuItem("Clear All", 'C');
clearAllMenuItem.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
GmailAssistant.this.accountsTable.clearSelection();
}
});
accountsPopupMenu.add(clearAllMenuItem);
this.accountsTable.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())
{
/* display popup menu */
final int i = GmailAssistant.this.accountsTable.rowAtPoint(e.getPoint());
if ((i >= 0) && (!GmailAssistant.this.accountsTable.isRowSelected(i)))
{
GmailAssistant.this.accountsTable.setRowSelectionInterval(i, i);
}
accountsPopupMenu.show(e.getComponent(), e.getX(), e.getY());
}
else if ((e.getClickCount() >= 2) && SwingUtilities.isLeftMouseButton(e))
{
/* open selected account for editing */
final int i = GmailAssistant.this.accountsTable.rowAtPoint(e.getPoint());
if (i >= 0)
{
GmailAssistant.this.accountsTable.setRowSelectionInterval(i, i);
GmailAssistant.this.editButton.doClick();
}
}
}
});
valueChanged(null);
/* center form on the screen */
setLocationRelativeTo(null);
/*************************************************
* SETUP PROGRAM ICON, TRAY ICON, AND POPUP MENU *
*************************************************/
/* setup program icon */
try
{
setIconImage(ImageIO.read(GmailAssistant.class.getResource("/gmailassistant/resources/ga_logo_64.png")));
}
catch (IOException e)
{
throw new TerminatingException("Failed to load program icon image.");
}
/* create popup menu for system tray icon */
final PopupMenu trayMenu = new PopupMenu(GmailAssistant.NAME);
/* popup menu: "Check Mail Now" */
final MenuItem checkItem = new MenuItem("Check Mail Now");
checkItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
checkMailNow();
}
});
trayMenu.add(checkItem);
/* popup menu: "Tell me again..." */
final MenuItem againItem = new MenuItem("Tell me Again...");
againItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
GmailAssistant.this.popup.showAllMessages();
}
});
trayMenu.add(againItem);
/* popup menu: "Reset Alerts" */
final MenuItem resetItem = new MenuItem("Reset Alerts");
resetItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
resetAlerts();
}
});
trayMenu.add(resetItem);
trayMenu.addSeparator();
/* popup menu: "Restore" */
final MenuItem restoreItem = new MenuItem("Restore");
restoreItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
restoreProgram();
}
});
trayMenu.add(restoreItem);
/* popup menu: "Exit" */
final MenuItem exitItem = new MenuItem("Exit");
exitItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
closeProgram();
}
});
trayMenu.add(exitItem);
try
{
this.trayIcon = new SimpleTrayIcon(this);
this.trayIcon.setPopupMenu(trayMenu);
this.trayIcon.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
restoreProgram();
}
});
SystemTray.getSystemTray().add(this.trayIcon);
}
catch (IOException e)
{
throw new TerminatingException("Failed to load system tray icon images.");
}
catch (AWTException e)
{
throw new TerminatingException("Failed to set system tray icon.");
}
}
/**
* Respond to a list selection event on the table of accounts.
*
* @param e
* list selection event
*/
@Override
public void valueChanged(
ListSelectionEvent e)
{
final int[] rows = this.accountsTable.getSelectedRows();
final int numRows = rows.length;
/* updateMessages button states */
if (numRows == 0)
{
this.removeButton.setEnabled(false);
this.editButton.setEnabled(false);
this.enableButton.setEnabled(false);
this.disableButton.setEnabled(false);
}
else
{
this.removeButton.setEnabled(true);
this.editButton.setEnabled(true);
this.enableButton.setEnabled(true);
this.disableButton.setEnabled(true);
}
}
/**
* Check for unread mails in all accounts now.
*/
private void checkMailNow()
{
synchronized (this.accounts)
{
for (Account ac : this.accounts)
{
ac.checkMailNow();
}
}
}
/**
* Reset all alerts.
*/
private void resetAlerts()
{
this.trayIcon.setNormalIcon();
this.popup.cancelAllMessages();
this.chime.cancelAll();
this.chime.stopPeriodicBell();
this.led.cancelAll();
}
/**
* Refresh the specified account on the table.
*
* @param ac
* account to be refreshed
*/
void refreshAccountOnTable(
final Account ac)
{
synchronized (this.accounts)
{
final int i = this.accounts.indexOf(ac);
if (i >= 0)
{
this.accountsTableModel.fireTableRowsUpdated(i, i);
valueChanged(null);
}
}
refreshUnreadMailCount();
}
/**
* Load the specified profile files.
* This method must run on the EDT.
*
* @param files
* profile files to be loaded
*/
private void loadProfiles(
final File[] files)
{
if (this.profileLoader == null)
{
this.profileLoader = new ProfileLoader(this);
}
this.profileLoader.loadProfiles(files);
}
/**
* Add a new account with the specified properties.
* The account form is made visible for editing.
*
* @param properties
* account properties for the new account; if null, the default properties
* will be applied
*/
void addNewAccount(
final Map<Account.Property,Object> properties)
{
this.addButton.setEnabled(false);
/* increment last account ID used */
int id;
synchronized (this.lastIdLock)
{
id = ++this.lastId;
}
/* determine if account form should be displayed for editing */
boolean editAccount = true;
if (properties != null)
{
if (((Boolean) properties.get(Account.Property.ENABLED)) &&
(((char[]) properties.get(Account.Property.PASSWORD)).length == 0))
{
editAccount = true;
properties.put(Account.Property.ENABLED, false);
}
else
{
editAccount = false;
}
}
/* create new account */
final Account ac = new Account(
this,
id,
(properties == null) ? Account.getNewDefaultProperties() : properties);
/* add newly created account to table of accounts */
synchronized (this.accounts)
{
this.accounts.add(ac);
}
this.accountsTableModel.fireTableDataChanged();
valueChanged(null);
refreshUnreadMailCount();
/* make account form visible for editing, if necessary */
if (editAccount)
{
ac.editAccount();
}
this.addButton.setEnabled(true);
}
/**
* Remove the selected accounts.
*/
private void removeAccounts()
{
this.removeButton.setEnabled(false);
synchronized (this.accounts)
{
final List<Account> accountsToRemove = new ArrayList<Account>();
for (int i : this.accountsTable.getSelectedRows())
{
final Account ac = this.accounts.get(this.accountsTable.convertRowIndexToModel(i));
accountsToRemove.add(ac);
}
for (Account ac : accountsToRemove)
{
this.accounts.remove(ac);
ac.removeAccount();
}
}
this.accountsTableModel.fireTableDataChanged();
refreshUnreadMailCount();
this.removeButton.setEnabled(true);
}
/**
* Refresh the total number of unread mails for all accounts.
*/
private void refreshUnreadMailCount()
{
int total = 0;
int totalPopup = 0;
int totalChime = 0;
int totalPeriodicBell = 0;
int totalBlink = 0;
boolean error = false;
synchronized (this.accounts)
{
for (Account ac : this.accounts)
{
final int unreadMails = (Integer) ac.getProperty(Account.Property.UNREAD_MAILS);
final int n = (unreadMails > 0) ? unreadMails