+++ /dev/null
-package tim.prune.function;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.FocusAdapter;
-import java.awt.event.FocusEvent;
-import java.awt.event.FocusListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-import java.util.ArrayList;
-import java.util.TimeZone;
-import java.util.TreeSet;
-
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.ButtonGroup;
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JRadioButton;
-import javax.swing.JScrollPane;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-
-import tim.prune.App;
-import tim.prune.DataSubscriber;
-import tim.prune.GenericFunction;
-import tim.prune.I18nManager;
-import tim.prune.UpdateMessageBroker;
-import tim.prune.config.Config;
-import tim.prune.gui.CombinedListAndModel;
-import tim.prune.gui.GuiGridLayout;
-
-/**
- * Class to provide the gui for selecting an alternative timezone
- */
-public class SelectTimezoneFunction extends GenericFunction
-{
- /** Arraylist of timezone infos */
- private ArrayList<TimezoneDetails> _zoneInfo;
- /** Dialog */
- private JDialog _dialog = null;
- /** Radio button to select system timezone instead of using listboxes */
- private JRadioButton _systemRadio = null;
- /** Radio button to select timezone using listboxes */
- private JRadioButton _customRadio = null;
- /** Array of list boxes */
- private CombinedListAndModel[] _listBoxes = null;
- /** Label for selected zone */
- private JLabel _selectedZoneLabel = null;
- /** Label for offset of selected zone */
- private JLabel _selectedOffsetLabel = null;
- /** OK button for finishing */
- private JButton _okButton = null;
-
- private static final int LIST_REGIONS = 0;
- private static final int LIST_OFFSETS = 1;
- private static final int LIST_GROUPS = 2;
- private static final int LIST_NAMES = 3;
-
- /**
- * Inner class for listening to list clicks
- */
- class ListListener implements ListSelectionListener
- {
- private int _key = 0;
- /** Constructor */
- ListListener(int inKey) {_key = inKey;}
- /** Listen for selection changes */
- public void valueChanged(ListSelectionEvent inEvent) {
- if (!inEvent.getValueIsAdjusting()) {
- processListClick(_key);
- }
- }
- }
-
- /** Inner class to hold categorisation info for a timezone */
- class TimezoneDetails
- {
- public String _id;
- public String _region;
- public int _offset;
- public String _group;
- public String _name;
- }
-
- /**
- * Constructor
- * @param inApp App object
- */
- public SelectTimezoneFunction(App inApp)
- {
- super(inApp);
- }
-
- /** Get the name key */
- public String getNameKey() {
- return "function.selecttimezone";
- }
-
- /**
- * Begin the function
- */
- public void begin()
- {
- if (_dialog == null)
- {
- _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
- _dialog.setLocationRelativeTo(_parentFrame);
- _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
- _dialog.getContentPane().add(makeDialogComponents());
- _dialog.pack();
- }
- collectTimezoneInfo();
- _systemRadio.setText(I18nManager.getText("dialog.settimezone.system") + " ("
- + TimeZone.getDefault().getID() + ")");
- // Set up dialog according to current config
- String selectedTimezone = Config.getConfigString(Config.KEY_TIMEZONE_ID);
- if (selectedTimezone == null || selectedTimezone.equals(""))
- {
- _systemRadio.setSelected(true);
- }
- else
- {
- _customRadio.setSelected(true);
- }
- _dialog.setVisible(true);
- }
-
- /**
- * Create dialog components
- * @return Panel containing all gui elements in dialog
- */
- private Component makeDialogComponents()
- {
- JPanel dialogPanel = new JPanel();
- dialogPanel.setLayout(new BorderLayout(5, 5));
- // Listener for radio buttons
- ActionListener radioListener = new ActionListener() {
- public void actionPerformed(ActionEvent inEvent) {
- radioSelected(_systemRadio.isSelected());
- }
- };
- FocusListener radioFocusListener = new FocusAdapter() {
- public void focusGained(FocusEvent inEvent) {
- radioSelected(_systemRadio.isSelected());
- }
- };
-
- // Panel at top
- JPanel topPanel = new JPanel();
- topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
- JLabel topLabel = new JLabel(I18nManager.getText("dialog.settimezone.intro"));
- topLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
- topPanel.add(topLabel);
- _systemRadio = new JRadioButton(I18nManager.getText("dialog.settimezone.system"));
- _systemRadio.addActionListener(radioListener);
- _systemRadio.addFocusListener(radioFocusListener);
- topPanel.add(_systemRadio);
- _customRadio = new JRadioButton(I18nManager.getText("dialog.settimezone.custom"));
- _customRadio.addActionListener(radioListener);
- _customRadio.addFocusListener(radioFocusListener);
- topPanel.add(_customRadio);
- ButtonGroup radioGroup = new ButtonGroup();
- radioGroup.add(_systemRadio); radioGroup.add(_customRadio);
- dialogPanel.add(topPanel, BorderLayout.NORTH);
-
- // Main panel with box layout, list Panel with four lists in a grid
- JPanel mainPanel = new JPanel();
- mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
-
- JPanel listsPanel = new JPanel();
- listsPanel.setLayout(new GridLayout(1, 4));
- _listBoxes = new CombinedListAndModel[4];
- // First list for regions
- _listBoxes[LIST_REGIONS] = new CombinedListAndModel(0);
- // Add listener for list selection changes
- _listBoxes[LIST_REGIONS].addListSelectionListener(new ListListener(LIST_REGIONS));
- JScrollPane scrollPane = new JScrollPane(_listBoxes[LIST_REGIONS]);
- scrollPane.setPreferredSize(new Dimension(100, 200));
- scrollPane.setMinimumSize(new Dimension(100, 200));
- scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
- listsPanel.add(scrollPane);
-
- // second list for offsets
- _listBoxes[LIST_OFFSETS] = new CombinedListAndModel(1);
- _listBoxes[LIST_OFFSETS].setMaxNumEntries(24);
- _listBoxes[LIST_OFFSETS].addListSelectionListener(new ListListener(LIST_OFFSETS));
- scrollPane = new JScrollPane(_listBoxes[LIST_OFFSETS]);
- scrollPane.setPreferredSize(new Dimension(100, 200));
- scrollPane.setMinimumSize(new Dimension(100, 200));
- scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
- listsPanel.add(scrollPane);
-
- // third list for groups
- _listBoxes[LIST_GROUPS] = new CombinedListAndModel(2);
- _listBoxes[LIST_GROUPS].setMaxNumEntries(20);
- _listBoxes[LIST_GROUPS].addListSelectionListener(new ListListener(LIST_GROUPS));
- scrollPane = new JScrollPane(_listBoxes[LIST_GROUPS]);
- scrollPane.setPreferredSize(new Dimension(100, 200));
- scrollPane.setMinimumSize(new Dimension(100, 200));
- scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
- listsPanel.add(scrollPane);
-
- // fourth list for names
- _listBoxes[LIST_NAMES] = new CombinedListAndModel(3);
- _listBoxes[LIST_NAMES].setMaxNumEntries(20);
- _listBoxes[LIST_NAMES].addListSelectionListener(new ListListener(LIST_NAMES));
- scrollPane = new JScrollPane(_listBoxes[LIST_NAMES]);
- scrollPane.setPreferredSize(new Dimension(100, 200));
- scrollPane.setMinimumSize(new Dimension(100, 200));
- scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
- listsPanel.add(scrollPane);
- mainPanel.add(listsPanel);
-
- // Details labels underneath lists - description and offset
- JPanel detailsPanel = new JPanel();
- GuiGridLayout grid = new GuiGridLayout(detailsPanel);
- grid.add(new JLabel(I18nManager.getText("dialog.settimezone.selectedzone") + " :"));
- _selectedZoneLabel = new JLabel("");
- grid.add(_selectedZoneLabel);
- grid.add(new JLabel(I18nManager.getText("dialog.settimezone.offsetfromutc") + " :"));
- _selectedOffsetLabel = new JLabel("");
- grid.add(_selectedOffsetLabel);
- mainPanel.add(detailsPanel);
- dialogPanel.add(mainPanel, BorderLayout.CENTER);
-
- // close window if escape pressed
- KeyAdapter escListener = new KeyAdapter() {
- public void keyReleased(KeyEvent inE) {
- if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {
- _dialog.dispose();
- }
- }
- };
- _listBoxes[LIST_REGIONS].addKeyListener(escListener);
- _listBoxes[LIST_OFFSETS].addKeyListener(escListener);
-
- // button panel at bottom
- JPanel buttonPanel = new JPanel();
- buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
- // OK button
- _okButton = new JButton(I18nManager.getText("button.ok"));
- _okButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- finishSelectTimezone();
- }
- });
- buttonPanel.add(_okButton);
- _okButton.addKeyListener(escListener);
- // Cancel button
- JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
- cancelButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- _dialog.dispose();
- }
- });
- cancelButton.addKeyListener(new KeyAdapter() {
- public void keyPressed(KeyEvent inE) {
- if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
- }
- });
- buttonPanel.add(cancelButton);
- dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
- return dialogPanel;
- }
-
- /**
- * React to changes in the radio buttons
- * @param inUseSystem true for system, false for custom
- */
- private void radioSelected(boolean inUseSystem)
- {
- for (int i=0; i<_listBoxes.length; i++)
- {
- if (inUseSystem)
- {
- _listBoxes[i].clear();
- }
- _listBoxes[i].setEnabled(!inUseSystem);
- }
- if (!inUseSystem)
- {
- populateTimezoneRegions();
- populateTimezoneOffsets(null);
- preselectTimezone(Config.getConfigString(Config.KEY_TIMEZONE_ID));
- }
- showTimezoneDetails();
- }
-
- /**
- * React to a selection change on one of our lists
- * @param inKey key of list which was clicked
- */
- private void processListClick(int inKey)
- {
- final boolean offsetSelected = _listBoxes[LIST_OFFSETS].getSelectedItem() != null;
- final boolean groupSelected = _listBoxes[LIST_GROUPS].getSelectedItem() != null;
- // Update offsets?
- if (inKey == LIST_REGIONS)
- {
- populateTimezoneOffsets(_listBoxes[LIST_REGIONS].getSelectedItem());
- }
- // Update groups?
- if (inKey == LIST_OFFSETS
- || (inKey == LIST_REGIONS && !offsetSelected))
- {
- populateTimezoneGroups(_listBoxes[LIST_REGIONS].getSelectedItem(), _listBoxes[LIST_OFFSETS].getSelectedItem());
- }
- // Update names?
- if (inKey == LIST_GROUPS
- || (inKey <= LIST_OFFSETS && !groupSelected))
- {
- populateTimezoneNames(_listBoxes[LIST_REGIONS].getSelectedItem(), _listBoxes[LIST_OFFSETS].getSelectedItem(),
- _listBoxes[LIST_GROUPS].getSelectedItem());
- }
- // Show the details of the selected timezone
- showTimezoneDetails();
- }
-
- /**
- * Use the system information to populate the list of available timezones
- */
- private void collectTimezoneInfo()
- {
- _zoneInfo = new ArrayList<TimezoneDetails>();
- for (String id : TimeZone.getAvailableIDs())
- {
- String region = getRegion(id);
- if (region != null)
- {
- TimeZone tz = TimeZone.getTimeZone(id);
- TimezoneDetails details = new TimezoneDetails();
- details._id = id;
- details._region = region;
- details._offset = tz.getOffset(System.currentTimeMillis()) / 1000 / 60;
- details._group = tz.getDisplayName();
- details._name = getNameWithoutRegion(id);
- _zoneInfo.add(details);
- }
- }
- }
-
- /**
- * Populate the timezone regions into the region list
- */
- private void populateTimezoneRegions()
- {
- _listBoxes[LIST_REGIONS].clear();
- TreeSet<String> regions = new TreeSet<String>();
- for (TimezoneDetails currZone : _zoneInfo)
- {
- regions.add(currZone._region);
- }
- for (String region : regions)
- {
- _listBoxes[LIST_REGIONS].addItem(region);
- }
- }
-
- /**
- * Extract the timezone region from the id
- */
- private static String getRegion(String inId)
- {
- final int slashPos = (inId == null ? -1 : inId.indexOf('/'));
- if (slashPos > 0)
- {
- return inId.substring(0, slashPos);
- }
- return null;
- }
-
- /**
- * Populate the second listbox with the offsets for the given region
- * @param inRegion selected region, or null if none selected
- */
- private void populateTimezoneOffsets(String inRegion)
- {
- _listBoxes[LIST_OFFSETS].clear();
- TreeSet<Integer> offsetsinMinutes = new TreeSet<Integer>();
- for (TimezoneDetails currZone : _zoneInfo)
- {
- String region = currZone._region;
- if (inRegion == null || region.equals(inRegion))
- {
- offsetsinMinutes.add(currZone._offset);
- }
- }
- for (Integer offset : offsetsinMinutes)
- {
- _listBoxes[LIST_OFFSETS].addItem(makeOffsetString(offset));
- }
- }
-
- /**
- * @return String containing offset for display
- */
- private static String makeOffsetString(int inOffsetInMinutes)
- {
- if (inOffsetInMinutes == 0) return "0";
- final boolean isWholeHours = (inOffsetInMinutes % 60) == 0;
- if (isWholeHours)
- {
- return (inOffsetInMinutes > 0 ? "+" : "") + (inOffsetInMinutes / 60);
- }
- final double numHours = inOffsetInMinutes / 60.0;
- return (inOffsetInMinutes > 0 ? "+" : "") + numHours;
- }
-
- /**
- * Populate the group list using the specified region and offset
- * @param inRegion selected region (if any) from the first list
- * @param inOffset selected offset (if any) from the second list
- */
- private void populateTimezoneGroups(String inRegion, String inOffset)
- {
- _listBoxes[LIST_GROUPS].clear();
- // Convert given offset string (in hours) into numeric offset (in minutes)
- final int offsetMins = convertToMinutes(inOffset);
-
- TreeSet<String> zoneGroups = new TreeSet<String>();
- for (TimezoneDetails currZone : _zoneInfo)
- {
- if (inRegion == null || currZone._region.equals(inRegion))
- {
- if (offsetMins == -1 || offsetMins == currZone._offset)
- {
- zoneGroups.add(currZone._group);
- }
- }
- }
- // If the region and offset were given, then list is unlimited
- _listBoxes[LIST_GROUPS].setUnlimited(inRegion != null && inOffset != null);
- // Add all the found names to the listbox
- for (String group : zoneGroups)
- {
- _listBoxes[LIST_GROUPS].addItem(group);
- }
- }
-
- /**
- * Populate the group list using the specified region, offset and group
- * @param inRegion selected region (if any) from the first list
- * @param inOffset selected offset (if any) from the second list
- * @param inGroup selected group (if any) from the third list
- */
- private void populateTimezoneNames(String inRegion, String inOffset, String inGroup)
- {
- CombinedListAndModel nameList = _listBoxes[LIST_NAMES];
- nameList.clear();
- // Convert given offset string (in hours) into numeric offset (in minutes)
- final int offsetMins = convertToMinutes(inOffset);
-
- TreeSet<String> zoneNames = new TreeSet<String>();
- for (TimezoneDetails currZone : _zoneInfo)
- {
- if ((inRegion == null || currZone._region.equals(inRegion))
- && (offsetMins == -1 || currZone._offset == offsetMins)
- && (inGroup == null || currZone._group.equals(inGroup)))
- {
- zoneNames.add(currZone._name);
- }
- }
- // If the region and offset were given, then list is unlimited
- nameList.setUnlimited(inRegion != null && inOffset != null);
- // Add all the found names to the listbox
- for (String name : zoneNames)
- {
- nameList.addItem(name);
- }
- }
-
- /**
- * Convert the given String from hours to minutes
- * @param inOffsetInHours String from listbox in +/- hours
- * @return offset in minutes, or -1
- */
- private static int convertToMinutes(String inOffsetInHours)
- {
- int offsetMins = -1;
- try {
- offsetMins = (int) (60 * Double.parseDouble(inOffsetInHours));
- }
- catch (NumberFormatException nfe) {} // offset stays -1
- catch (NullPointerException npe) {} // offset stays -1
- return offsetMins;
- }
-
- /**
- * Remove the timezone region from the id to just leave the name after the slash
- */
- private static String getNameWithoutRegion(String inId)
- {
- final int slashPos = (inId == null ? -1 : inId.indexOf('/'));
- if (slashPos > 0)
- {
- return inId.substring(slashPos + 1);
- }
- return null;
- }
-
- /**
- * Get the selected timezone, or null if none selected
- */
- private TimeZone getSelectedTimezone()
- {
- if (_systemRadio.isSelected())
- {
- return TimeZone.getDefault();
- }
-
- String chosenRegion = _listBoxes[LIST_REGIONS].getSelectedItem();
- // Convert given offset string (in hours) into numeric offset (in minutes)
- final int offsetMins = convertToMinutes(_listBoxes[LIST_OFFSETS].getSelectedItem());
- String chosenGroup = _listBoxes[LIST_GROUPS].getSelectedItem();
- String chosenName = _listBoxes[LIST_NAMES].getSelectedItem();
-
- TreeSet<String> zoneIds = new TreeSet<String>();
- for (TimezoneDetails currZone : _zoneInfo)
- {
- if ((chosenRegion == null || currZone._region.equals(chosenRegion))
- && (offsetMins == -1 || currZone._offset == offsetMins)
- && (chosenGroup == null || currZone._group.equals(chosenGroup))
- && (chosenName == null || currZone._name.equals(chosenName)))
- {
- zoneIds.add(currZone._id);
- if (zoneIds.size() > 1) {
- break; // exit loop now, we've got too many
- }
- }
- }
- // Should have exactly one result now
- if (zoneIds.size() == 1)
- {
- return TimeZone.getTimeZone(zoneIds.first());
- }
-
- // none selected (yet)
- return null;
- }
-
- /**
- * Show the details of the selected timezone
- */
- private void showTimezoneDetails()
- {
- TimeZone selectedTimezone = getSelectedTimezone();
- if (selectedTimezone == null)
- {
- // Clear details labels
- _selectedZoneLabel.setText("");
- _selectedOffsetLabel.setText("");
- }
- else
- {
- // Fill results in labels
- String desc = selectedTimezone.getID() + " - " + selectedTimezone.getDisplayName();
- _selectedZoneLabel.setText(desc);
- String offsets = getOffsetDescription(selectedTimezone);
- _selectedOffsetLabel.setText(offsets);
- }
- _okButton.setEnabled(selectedTimezone != null);
- }
-
- /**
- * @param inTimezone selected timezone
- * @return String describing the time offset(s) of this zone including winter/summer time
- */
- private static String getOffsetDescription(TimeZone inTimezone)
- {
- if (inTimezone == null)
- {
- return "";
- }
- TreeSet<Integer> offsetsinMinutes = new TreeSet<Integer>();
- long testTimeMillis = System.currentTimeMillis();
- final long testPeriodInMillis = 1000L * 60 * 60 * 24 * 30 * 2;
- for (int i=0; i<5; i++)
- {
- offsetsinMinutes.add(inTimezone.getOffset(testTimeMillis) / 1000 / 60);
- testTimeMillis += testPeriodInMillis;
- }
- // Make String describing the sorted set
- StringBuffer buff = new StringBuffer();
- for (Integer offset : offsetsinMinutes)
- {
- if (buff.length() > 0)
- {
- buff.append(" / ");
- }
- buff.append(makeOffsetString(offset));
- }
- return buff.toString();
- }
-
- /**
- * On entry to the dialog, select the items in each listbox
- * according to the given preselected timezone id
- * @param zoneId id of zone to select
- */
- private void preselectTimezone(String zoneId)
- {
- TimeZone tz = (zoneId == null ? TimeZone.getDefault() : TimeZone.getTimeZone(zoneId));
- if (tz != null)
- {
- _listBoxes[LIST_REGIONS].selectItem(getRegion(zoneId));
- _listBoxes[LIST_OFFSETS].selectItem(makeOffsetString(tz.getOffset(System.currentTimeMillis()) / 1000 / 60));
- _listBoxes[LIST_GROUPS].selectItem(tz.getDisplayName());
- _listBoxes[LIST_NAMES].selectItem(getNameWithoutRegion(zoneId));
- }
- }
-
- /**
- * Finish the dialog by setting the config according to the selected zone
- */
- private void finishSelectTimezone()
- {
- TimeZone selectedTimezone = getSelectedTimezone();
- if (_systemRadio.isSelected() || selectedTimezone == null)
- {
- // Clear config, use default system timezone instead
- Config.setConfigString(Config.KEY_TIMEZONE_ID, null);
- }
- else
- {
- // Get selected timezone, set in config
- Config.setConfigString(Config.KEY_TIMEZONE_ID, selectedTimezone.getID());
- }
- _dialog.dispose();
- // Make sure listeners know to update themselves
- UpdateMessageBroker.informSubscribers(DataSubscriber.UNITS_CHANGED);
- }
-}