1 package tim.prune.function;
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.FlowLayout;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.awt.event.ItemEvent;
9 import java.awt.event.ItemListener;
10 import java.awt.event.KeyAdapter;
11 import java.awt.event.KeyEvent;
13 import javax.swing.BorderFactory;
14 import javax.swing.ButtonGroup;
15 import javax.swing.JButton;
16 import javax.swing.JComboBox;
17 import javax.swing.JDialog;
18 import javax.swing.JLabel;
19 import javax.swing.JPanel;
20 import javax.swing.JRadioButton;
23 import tim.prune.GenericFunction;
24 import tim.prune.I18nManager;
25 import tim.prune.data.Distance;
26 import tim.prune.data.Field;
27 import tim.prune.data.TimeDifference;
28 import tim.prune.data.Unit;
29 import tim.prune.data.UnitSetLibrary;
30 import tim.prune.gui.GuiGridLayout;
31 import tim.prune.gui.WholeNumberField;
34 * Superclass for functions which act on a defined
35 * distance limit or time limit
37 public abstract class DistanceTimeLimitFunction extends GenericFunction
40 protected JDialog _dialog = null;
41 /** Radio buttons for splitting by distance and time */
42 private JRadioButton _distLimitRadio = null, _timeLimitRadio = null;
43 /** Radio button for splitting by fraction (such as half-distance) */
44 private JRadioButton _halvesRadio = null;
45 /** Flag for whether to offer halves or not */
46 private boolean _showHalves = false;
47 /** Dropdown for selecting distance units */
48 private JComboBox<String> _distUnitsDropdown = null;
49 /** Text field for entering distance */
50 private WholeNumberField _distanceField = null;
51 /** Text fields for entering distance */
52 private WholeNumberField _limitHourField = null, _limitMinField = null;
53 /** Ok and cancel buttons */
54 private JButton _okButton = null;
55 private JButton _cancelButton = null;
59 * React to item changes and key presses by enabling / disabling ok button
61 private class ChangeListener extends KeyAdapter implements ItemListener
63 /** Item changed in ItemListener */
64 public void itemStateChanged(ItemEvent arg0) {
68 /** Key released in KeyListener */
69 public void keyReleased(KeyEvent arg0) {
77 public DistanceTimeLimitFunction(App inApp, boolean inShowHalves)
80 _showHalves = inShowHalves;
90 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
91 _dialog.setLocationRelativeTo(_parentFrame);
92 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
93 _dialog.getContentPane().add(makeDialogComponents());
97 // TODO: Maybe set distance units according to current Config setting?
98 final boolean hasTimestamps = _app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP);
99 _timeLimitRadio.setEnabled(hasTimestamps);
102 _distLimitRadio.setSelected(true);
104 // set focus to Cancel button so that pressing "Esc" works
105 _cancelButton.requestFocus();
106 _dialog.setVisible(true);
110 * Create dialog components
111 * @return Panel containing all gui elements in dialog
113 private Component makeDialogComponents()
115 JPanel dialogPanel = new JPanel();
116 dialogPanel.setLayout(new BorderLayout(5, 5));
118 // Make radio buttons for the options
119 _distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit") + ": ");
120 _timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + ": ");
122 _halvesRadio = new JRadioButton(I18nManager.getText("dialog.markers.halves"));
124 ButtonGroup radioGroup = new ButtonGroup();
125 radioGroup.add(_distLimitRadio);
126 radioGroup.add(_timeLimitRadio);
128 radioGroup.add(_halvesRadio);
131 // central panel for limits
132 JPanel limitsPanel = new JPanel();
133 limitsPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
134 GuiGridLayout grid = new GuiGridLayout(limitsPanel, new double[] {0.5, 1.0},
135 new boolean[] {false, false});
136 ChangeListener optionsChangedListener = new ChangeListener();
138 grid.add(_distLimitRadio);
139 _distLimitRadio.setSelected(true);
140 _distLimitRadio.addItemListener(optionsChangedListener);
141 JPanel distLimitPanel = new JPanel();
142 distLimitPanel.setLayout(new FlowLayout());
143 _distanceField = new WholeNumberField(3);
144 _distanceField.addKeyListener(optionsChangedListener);
145 distLimitPanel.add(_distanceField);
146 String[] distUnitsOptions = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.metres"),
147 I18nManager.getText("units.miles")};
148 _distUnitsDropdown = new JComboBox<String>(distUnitsOptions);
149 _distUnitsDropdown.addItemListener(optionsChangedListener);
150 distLimitPanel.add(_distUnitsDropdown);
151 distLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
152 grid.add(distLimitPanel);
155 grid.add(_timeLimitRadio);
156 _timeLimitRadio.addItemListener(optionsChangedListener);
157 JPanel timeLimitPanel = new JPanel();
158 timeLimitPanel.setLayout(new FlowLayout());
159 _limitHourField = new WholeNumberField(2);
160 _limitHourField.addKeyListener(optionsChangedListener);
161 timeLimitPanel.add(_limitHourField);
162 timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.hours")));
163 _limitMinField = new WholeNumberField(3);
164 _limitMinField.addKeyListener(optionsChangedListener);
165 timeLimitPanel.add(_limitMinField);
166 timeLimitPanel.add(new JLabel(I18nManager.getText("units.minutes")));
167 timeLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
168 grid.add(timeLimitPanel);
173 grid.add(_halvesRadio);
174 _halvesRadio.addItemListener(optionsChangedListener);
177 dialogPanel.add(limitsPanel, BorderLayout.NORTH);
179 // button panel at bottom
180 JPanel buttonPanel = new JPanel();
181 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
183 _okButton = new JButton(I18nManager.getText("button.ok"));
184 _okButton.addActionListener(new ActionListener() {
185 public void actionPerformed(ActionEvent e) {
189 buttonPanel.add(_okButton);
191 _cancelButton = new JButton(I18nManager.getText("button.cancel"));
192 _cancelButton.addActionListener(new ActionListener() {
193 public void actionPerformed(ActionEvent e) {
197 _cancelButton.addKeyListener(new KeyAdapter() {
198 public void keyPressed(KeyEvent inE) {
199 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
202 buttonPanel.add(_cancelButton);
203 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
208 * Enable or disable the OK button according to the inputs
210 private void enableOkButton()
212 boolean enabled = false;
213 if (_distLimitRadio.isSelected()) {
214 enabled = _distanceField.getValue() > 0;
216 else if (_timeLimitRadio.isSelected()) {
217 enabled = _limitHourField.getValue() > 0 || _limitMinField.getValue() > 0;
219 else if (_halvesRadio != null && _halvesRadio.isSelected()) {
222 _okButton.setEnabled(enabled);
224 // Also enable/disable the other fields
225 _distanceField.setEnabled(_distLimitRadio.isSelected());
226 _distUnitsDropdown.setEnabled(_distLimitRadio.isSelected());
227 _limitHourField.setEnabled(_timeLimitRadio.isSelected());
228 _limitMinField.setEnabled(_timeLimitRadio.isSelected());
232 * @return selected time limit in seconds, or 0
234 protected int getTimeLimitInSeconds()
236 if (_timeLimitRadio.isSelected()
237 && (_limitHourField.getValue() > 0 || _limitMinField.getValue() > 0))
239 int timeLimitSeconds = _limitHourField.getValue() * 60 * 60
240 + _limitMinField.getValue() * 60;
241 if (timeLimitSeconds > 0)
242 return timeLimitSeconds;
248 * @return selected distance limit in radians, or 0.0
250 protected double getDistanceLimitRadians()
252 if (_distLimitRadio.isSelected()
253 && _distanceField.getValue() > 0)
255 final Unit[] distUnits = {UnitSetLibrary.UNITS_KILOMETRES,
256 UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_MILES};
257 Unit distUnit = distUnits[_distUnitsDropdown.getSelectedIndex()];
258 double distLimitRadians = Distance.convertDistanceToRadians(_distanceField.getValue(), distUnit);
259 return distLimitRadians;
265 * @return selected distance limit in km, or 0.0
267 protected double getDistanceLimitKilometres()
269 return Distance.convertRadiansToDistance(getDistanceLimitRadians(), UnitSetLibrary.UNITS_KILOMETRES);
273 * @return true if "halves" option was selected
275 protected boolean isHalvesSelected() {
276 return _halvesRadio != null && _halvesRadio.isSelected();
280 * The dialog has been completed and OK pressed, so do the corresponding function
282 protected abstract void performFunction();
285 * Create a description for the given multiple of the configured limit
286 * @param inMultiple multiple of selected limit
287 * @return language-specific String description for waypoint names
289 protected String createLimitDescription(int inMultiple)
291 if (_distLimitRadio.isSelected()
292 && _distanceField.getValue() > 0)
294 final int distLimit = inMultiple * _distanceField.getValue();
295 final String[] distUnits = {"kilometres", "metres", "miles"};
296 final String unitKey = "units." + distUnits[_distUnitsDropdown.getSelectedIndex()] + ".short";
297 return "" + distLimit + " " + I18nManager.getText(unitKey);
299 else if (_timeLimitRadio.isSelected())
301 final long timeLimitSecs = (long) getTimeLimitInSeconds() * inMultiple;
302 return new TimeDifference(timeLimitSecs).getDescription();