/**
* 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.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Pattern;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/**
* "Time Zone Selector" form.
*/
class TimeZoneSelector
extends JFrame
{
/** map: timezone string description ---> TimeZone object */
private static final Map<String,TimeZone> TIMEZONE_STRING_TO_OBJECT_MAP = new HashMap<String,TimeZone>();
/** list of timezone string descriptions */
private static final String[] TIMEZONE_STRINGS;
/** parent ClockUnit object */
private final ClockUnit parent;
/** "quick find" list model */
private final QuickFindListModel model;
/**
* Static initialization block.
*/
static
{
/* normalized custom ID format for a TimeZone object */
final Pattern normalizedCustomIdFormat = Pattern.compile("GMT[\\+\\-][0-9]{2}\\:[0-9]{2}");
final Map<Integer,List<TimeZone>> rawOffsetToDifferentRulesTimeZonesMap = new HashMap<Integer,List<TimeZone>>();
final Map<TimeZone,List<TimeZone>> sameRulesTimeZonesMap = new HashMap<TimeZone,List<TimeZone>>();
for (String id : TimeZone.getAvailableIDs())
{
final TimeZone tz = TimeZone.getTimeZone(id);
if (normalizedCustomIdFormat.matcher(tz.getDisplayName()).matches())
{
/* this time zone does not have a "meaningful" name */
continue;
}
final int rawOffset = tz.getRawOffset();
List<TimeZone> rawOffsetTimeZones = rawOffsetToDifferentRulesTimeZonesMap.get(rawOffset);
if (rawOffsetTimeZones == null)
{
rawOffsetTimeZones = new ArrayList<TimeZone>();
rawOffsetToDifferentRulesTimeZonesMap.put(rawOffset, rawOffsetTimeZones);
}
boolean differentRules = true;
for (TimeZone t : rawOffsetTimeZones)
{
if (tz.hasSameRules(t))
{
differentRules = false;
sameRulesTimeZonesMap.get(t).add(tz);
break;
}
}
if (differentRules)
{
rawOffsetTimeZones.add(tz);
final List<TimeZone> sameRulesTimeZones = new ArrayList<TimeZone>();
sameRulesTimeZones.add(tz);
sameRulesTimeZonesMap.put(tz, sameRulesTimeZones);
}
}
/* populate map: timezone string description ---> TimeZone object */
for (Map.Entry<Integer,List<TimeZone>> me : rawOffsetToDifferentRulesTimeZonesMap.entrySet())
{
final int rawOffset = me.getKey();
/* determine GMT offset string */
int offsetMinutes = rawOffset / (60 * 1000);
boolean negative = false;
if (offsetMinutes < 0)
{
negative = true;
offsetMinutes = -offsetMinutes;
}
final int h = offsetMinutes / 60;
final int m = offsetMinutes - h * 60;
final String gmtOffset = "GMT" +
((negative) ? "-" : "+") +
((h < 10) ? "0" : "") + h + ":" +
((m < 10) ? "0" : "") + m;
for (TimeZone tz : me.getValue())
{
final List<String> sameRulesIds = new ArrayList<String>();
for (TimeZone t : sameRulesTimeZonesMap.get(tz))
{
/* extract the city name (if any) from the ID */
final String id = t.getID();
sameRulesIds.add(id.substring(id.lastIndexOf("/") + 1).replace('_', ' '));
}
Collections.sort(sameRulesIds);
final StringBuilder sb = new StringBuilder("(" + gmtOffset + ") ");
String prev = null;
for (String s : sameRulesIds)
{
if (s.equals(prev))
{
/* skip duplicates */
continue;
}
sb.append(s);
sb.append(", ");
prev = s;
}
sb.delete(sb.length() - 2, sb.length());
TIMEZONE_STRING_TO_OBJECT_MAP.put(sb.toString(), tz);
}
}
/* populate list of timezone string descriptions, and sort in order of GMT offset */
final List<Pair<Integer,String>> descriptions = new ArrayList<Pair<Integer,String>>();
for (Map.Entry<String,TimeZone> me : TIMEZONE_STRING_TO_OBJECT_MAP.entrySet())
{
descriptions.add(new Pair<Integer,String>(me.getValue().getRawOffset(), me.getKey()));
}
Collections.sort(descriptions);
TIMEZONE_STRINGS = new String[descriptions.size()];
for (int i = 0; i < descriptions.size(); i++)
{
TIMEZONE_STRINGS[i] = descriptions.get(i).e2;
}
}
/**
* Constructor.
*
* @param parent
* parent ClockUnit object
*/
TimeZoneSelector(
final ClockUnit parent)
{
/*********************
* INITIALIZE FIELDS *
*********************/
this.parent = parent;
this.model = new QuickFindListModel(TIMEZONE_STRINGS);
/******************************
* INITIALIZE FORM COMPONENTS *
******************************/
initComponents();
/*****************************
* CONFIGURE FORM COMPONENTS *
*****************************/
setTitle("Select Time Zone - " + this.parent.getTitle());
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
TimeZoneSelector.this.cancelButton.doClick();
}
});
/* inherit "always on top" mode from parent */
try
{
setAlwaysOnTop(this.parent.isAlwaysOnTop());
}
catch (Exception e)
{
/* ignore */
}
/* inherit program icon of parent */
final List<Image> icons = this.parent.getIconImages();
if (!icons.isEmpty())
{
setIconImage(icons.get(0));
}
/* field: "Quick find term" */
findField.getDocument().addDocumentListener(new DocumentListenerAdapter()
{
@Override
public void insertUpdate(DocumentEvent e)
{
TimeZoneSelector.this.model.quickFind(SwingManipulator.getTextJTextField(findField));
}
@Override
public void removeUpdate(DocumentEvent e)
{
TimeZoneSelector.this.model.quickFind(SwingManipulator.getTextJTextField(findField));
}
@Override
public void changedUpdate(DocumentEvent e)
{
TimeZoneSelector.this.model.quickFind(SwingManipulator.getTextJTextField(findField));
}
});
SwingManipulator.addStandardEditingPopupMenu(new JTextField[] {findField});
/* list of timezones */
list.setModel(model);
list.clearSelection();
list.addListSelectionListener(new ListSelectionListener()
{
public void valueChanged(ListSelectionEvent e)
{
okButton.setEnabled(!list.isSelectionEmpty());
}
});
model.addListDataListener(new ListDataListener()
{
public void intervalAdded(ListDataEvent e)
{
list.clearSelection();
}
public void intervalRemoved(ListDataEvent e)
{
list.clearSelection();
}
public void contentsChanged(ListDataEvent e)
{
list.clearSelection();
}
});
/* button: "OK" */
okButton.setEnabled(!list.isSelectionEmpty());
okButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
final String tz = (String) TimeZoneSelector.this.list.getSelectedValue();
if (tz != null)
{
TimeZoneSelector.this.parent.setTimeZone(TIMEZONE_STRING_TO_OBJECT_MAP.get(tz));
}
TimeZoneSelector.this.setVisible(false);
}
});
/* button: "Cancel" */
cancelButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
TimeZoneSelector.this.setVisible(false);
}
});
/* key binding: ENTER key */
for (JComponent c : new JComponent[] {findField, list})
{
c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "ENTER_OK_BUTTON");
c.getActionMap().put("ENTER_OK_BUTTON", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
okButton.doClick();
}
});
}
/* key binding: ESCAPE key */
for (JComponent c : new JComponent[] {findField, list, buttonsPanel})
{
c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "ESCAPE_CANCEL_BUTTON");
c.getActionMap().put("ESCAPE_CANCEL_BUTTON", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
cancelButton.doClick();
}
});
}
/* key binding: arrow up/down keys */
for (int key : new int[] {KeyEvent.VK_UP, KeyEvent.VK_DOWN})
{
findField.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(key, 0), "ARROW_" + key + "_TO_LIST");
findField.getActionMap().put("ARROW_" + key + "_TO_LIST", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
list.requestFocus();
}
});
}
/* center form on the screen */
setLocationRelativeTo(this.parent);
}
/***************************
* NETBEANS-GENERATED CODE *
***************************/
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents()
{
findLabel = new javax.swing.JLabel();
findField = new javax.swing.JTextField();
scrollPane = new javax.swing.JScrollPane();
list = new javax.swing.JList();
buttonsPanel = new javax.swing.JPanel();
okButton = new javax.swing.JButton();
cancelButton = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
findLabel.setDisplayedMnemonic('f');
findLabel.setLabelFor(findField);
findLabel.setText("Find:");
list.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
scrollPane.setViewportView(list);
buttonsPanel.setLayout(new java.awt.GridLayout(1, 0));
okButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/thistime/resources/tick.png"))); // NOI18N
okButton.setMnemonic('o');
okButton.setText("OK");
buttonsPanel.add(okButton);
cancelButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/thistime/resources/cross.png"))); // NOI18N
cancelButton.setMnemonic('c');
cancelButton.setText("Cancel");
buttonsPanel.add(cancelButton);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(scrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 280, Short.MAX_VALUE)
.addComponent(buttonsPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 280, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(findLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(findField, javax.swing.GroupLayout.DEFAULT_SIZE, 252, Short.MAX_VALUE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(findLabel)
.addComponent(findField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 200, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(buttonsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 34, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
);
pack();
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel buttonsPanel;
private javax.swing.JButton cancelButton;
private javax.swing.JTextField findField;
private javax.swing.JLabel findLabel;
private javax.swing.JList list;
private javax.swing.JButton okButton;
private javax.swing.JScrollPane scrollPane;
// End of variables declaration//GEN-END:variables
}