]> gitweb.fperrin.net Git - GpsPrune.git/blob - src/tim/prune/function/DistanceTimeLimitFunction.java
Version 20.4, May 2021
[GpsPrune.git] / src / tim / prune / function / DistanceTimeLimitFunction.java
1 package tim.prune.function;
2
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;
12
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;
21
22 import tim.prune.App;
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;
32
33 /**
34  * Superclass for functions which act on a defined
35  * distance limit or time limit
36  */
37 public abstract class DistanceTimeLimitFunction extends GenericFunction
38 {
39         /** Dialog */
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;
56
57
58         /**
59          * React to item changes and key presses by enabling / disabling ok button
60          */
61         private class ChangeListener extends KeyAdapter implements ItemListener
62         {
63                 /** Item changed in ItemListener */
64                 public void itemStateChanged(ItemEvent arg0) {
65                         enableOkButton();
66                 }
67
68                 /** Key released in KeyListener */
69                 public void keyReleased(KeyEvent arg0) {
70                         enableOkButton();
71                 }
72         }
73
74         /**
75          * Constructor
76          */
77         public DistanceTimeLimitFunction(App inApp, boolean inShowHalves)
78         {
79                 super(inApp);
80                 _showHalves = inShowHalves;
81         }
82
83         /**
84          * Begin the function
85          */
86         public void begin()
87         {
88                 if (_dialog == null)
89                 {
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());
94                         _dialog.pack();
95                 }
96                 enableOkButton();
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);
100                 if (!hasTimestamps)
101                 {
102                         _distLimitRadio.setSelected(true);
103                 }
104                 // set focus to Cancel button so that pressing "Esc" works
105                 _cancelButton.requestFocus();
106                 _dialog.setVisible(true);
107         }
108
109         /**
110          * Create dialog components
111          * @return Panel containing all gui elements in dialog
112          */
113         private Component makeDialogComponents()
114         {
115                 JPanel dialogPanel = new JPanel();
116                 dialogPanel.setLayout(new BorderLayout(5, 5));
117
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") + ": ");
121                 if (_showHalves) {
122                         _halvesRadio = new JRadioButton(I18nManager.getText("dialog.markers.halves"));
123                 }
124                 ButtonGroup radioGroup = new ButtonGroup();
125                 radioGroup.add(_distLimitRadio);
126                 radioGroup.add(_timeLimitRadio);
127                 if (_showHalves) {
128                         radioGroup.add(_halvesRadio);
129                 }
130
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();
137                 // distance limits
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);
153
154                 // time limit panel
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);
169
170                 // halves
171                 if (_showHalves)
172                 {
173                         grid.add(_halvesRadio);
174                         _halvesRadio.addItemListener(optionsChangedListener);
175                 }
176
177                 dialogPanel.add(limitsPanel, BorderLayout.NORTH);
178
179                 // button panel at bottom
180                 JPanel buttonPanel = new JPanel();
181                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
182                 // OK button
183                 _okButton = new JButton(I18nManager.getText("button.ok"));
184                 _okButton.addActionListener(new ActionListener() {
185                         public void actionPerformed(ActionEvent e) {
186                                 performFunction();
187                         }
188                 });
189                 buttonPanel.add(_okButton);
190                 // Cancel button
191                 _cancelButton = new JButton(I18nManager.getText("button.cancel"));
192                 _cancelButton.addActionListener(new ActionListener() {
193                         public void actionPerformed(ActionEvent e) {
194                                 _dialog.dispose();
195                         }
196                 });
197                 _cancelButton.addKeyListener(new KeyAdapter() {
198                         public void keyPressed(KeyEvent inE) {
199                                 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
200                         }
201                 });
202                 buttonPanel.add(_cancelButton);
203                 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
204                 return dialogPanel;
205         }
206
207         /**
208          * Enable or disable the OK button according to the inputs
209          */
210         private void enableOkButton()
211         {
212                 boolean enabled = false;
213                 if (_distLimitRadio.isSelected()) {
214                         enabled = _distanceField.getValue() > 0;
215                 }
216                 else if (_timeLimitRadio.isSelected()) {
217                         enabled = _limitHourField.getValue() > 0 || _limitMinField.getValue() > 0;
218                 }
219                 else if (_halvesRadio != null && _halvesRadio.isSelected()) {
220                         enabled = true;
221                 }
222                 _okButton.setEnabled(enabled);
223
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());
229         }
230
231         /**
232          * @return selected time limit in seconds, or 0
233          */
234         protected int getTimeLimitInSeconds()
235         {
236                 if (_timeLimitRadio.isSelected()
237                         && (_limitHourField.getValue() > 0 || _limitMinField.getValue() > 0))
238                 {
239                         int timeLimitSeconds = _limitHourField.getValue() * 60 * 60
240                                 + _limitMinField.getValue() * 60;
241                         if (timeLimitSeconds > 0)
242                                 return timeLimitSeconds;
243                 }
244                 return 0;
245         }
246
247         /**
248          * @return selected distance limit in radians, or 0.0
249          */
250         protected double getDistanceLimitRadians()
251         {
252                 if (_distLimitRadio.isSelected()
253                         && _distanceField.getValue() > 0)
254                 {
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;
260                 }
261                 return 0.0;
262         }
263
264         /**
265          * @return selected distance limit in km, or 0.0
266          */
267         protected double getDistanceLimitKilometres()
268         {
269                 return Distance.convertRadiansToDistance(getDistanceLimitRadians(), UnitSetLibrary.UNITS_KILOMETRES);
270         }
271
272         /**
273          * @return true if "halves" option was selected
274          */
275         protected boolean isHalvesSelected() {
276                 return _halvesRadio != null && _halvesRadio.isSelected();
277         }
278
279         /**
280          * The dialog has been completed and OK pressed, so do the corresponding function
281          */
282         protected abstract void performFunction();
283
284         /**
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
288          */
289         protected String createLimitDescription(int inMultiple)
290         {
291                 if (_distLimitRadio.isSelected()
292                         && _distanceField.getValue() > 0)
293                 {
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);
298                 }
299                 else if (_timeLimitRadio.isSelected())
300                 {
301                         final long timeLimitSecs = (long) getTimeLimitInSeconds() * inMultiple;
302                         return new TimeDifference(timeLimitSecs).getDescription();
303                 }
304                 return null;
305         }
306 }