]> gitweb.fperrin.net Git - GpsPrune.git/blobdiff - tim/prune/function/SelectTimezoneFunction.java
Version 19, May 2018
[GpsPrune.git] / tim / prune / function / SelectTimezoneFunction.java
diff --git a/tim/prune/function/SelectTimezoneFunction.java b/tim/prune/function/SelectTimezoneFunction.java
new file mode 100644 (file)
index 0000000..8f5f778
--- /dev/null
@@ -0,0 +1,640 @@
+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 && inRegion != 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);
+       }
+}