1 package tim.prune.function.sew;
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.Box;
14 import javax.swing.BoxLayout;
15 import javax.swing.ButtonGroup;
16 import javax.swing.JButton;
17 import javax.swing.JComboBox;
18 import javax.swing.JDialog;
19 import javax.swing.JLabel;
20 import javax.swing.JOptionPane;
21 import javax.swing.JPanel;
22 import javax.swing.JRadioButton;
25 import tim.prune.GenericFunction;
26 import tim.prune.I18nManager;
27 import tim.prune.UpdateMessageBroker;
28 import tim.prune.data.DataPoint;
29 import tim.prune.data.Distance;
30 import tim.prune.data.Field;
31 import tim.prune.data.Unit;
32 import tim.prune.data.UnitSetLibrary;
33 import tim.prune.gui.WholeNumberField;
34 import tim.prune.undo.UndoSplitSegments;
37 * Function to split a track into segments using
38 * either a distance limit or a time limit
40 public class SplitSegmentsFunction extends GenericFunction
43 private JDialog _dialog = null;
44 /** Radio buttons for splitting by distance and time */
45 private JRadioButton _distLimitRadio = null, _timeLimitRadio = null;
46 /** Dropdown for selecting distance units */
47 private JComboBox<String> _distUnitsDropdown = null;
48 /** Text field for entering distance */
49 private WholeNumberField _distanceField = null;
50 /** Text fields for entering distance */
51 private WholeNumberField _limitHourField = null, _limitMinField = null;
53 private JButton _okButton = null;
57 * React to item changes and key presses
59 private abstract class ChangeListener extends KeyAdapter implements ItemListener
61 /** Method to be implemented */
62 public abstract void optionsChanged();
64 /** Item changed in ItemListener */
65 public void itemStateChanged(ItemEvent arg0) {
69 /** Key released in KeyListener */
70 public void keyReleased(KeyEvent arg0) {
78 public SplitSegmentsFunction(App inApp) {
85 public String getNameKey() {
86 return "function.splitsegments";
96 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
97 _dialog.setLocationRelativeTo(_parentFrame);
98 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
99 _dialog.getContentPane().add(makeDialogComponents());
103 // TODO: Maybe set distance units according to current Config setting?
104 final boolean hasTimestamps = _app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP);
105 _timeLimitRadio.setEnabled(hasTimestamps);
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 three different options
119 _distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit") + ": ");
120 _timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + ": ");
121 ButtonGroup radioGroup = new ButtonGroup();
122 radioGroup.add(_distLimitRadio);
123 radioGroup.add(_timeLimitRadio);
125 // central panel for limits
126 JPanel limitsPanel = new JPanel();
127 limitsPanel.setLayout(new BoxLayout(limitsPanel, BoxLayout.Y_AXIS));
128 limitsPanel.add(Box.createVerticalStrut(8));
129 ChangeListener optionsChangedListener = new ChangeListener() {
130 public void optionsChanged() {
135 JPanel distLimitPanel = new JPanel();
136 distLimitPanel.setLayout(new FlowLayout());
137 _distLimitRadio.setSelected(true);
138 _distLimitRadio.addItemListener(optionsChangedListener);
139 distLimitPanel.add(_distLimitRadio);
140 _distanceField = new WholeNumberField(3);
141 _distanceField.addKeyListener(optionsChangedListener);
142 distLimitPanel.add(_distanceField);
143 String[] distUnitsOptions = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.metres"),
144 I18nManager.getText("units.miles")};
145 _distUnitsDropdown = new JComboBox<String>(distUnitsOptions);
146 _distUnitsDropdown.addItemListener(optionsChangedListener);
147 distLimitPanel.add(_distUnitsDropdown);
148 distLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
149 limitsPanel.add(distLimitPanel);
152 JPanel timeLimitPanel = new JPanel();
153 timeLimitPanel.setLayout(new FlowLayout());
154 _timeLimitRadio.addItemListener(optionsChangedListener);
155 timeLimitPanel.add(_timeLimitRadio);
156 _limitHourField = new WholeNumberField(2);
157 _limitHourField.addKeyListener(optionsChangedListener);
158 timeLimitPanel.add(_limitHourField);
159 timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.hours")));
160 _limitMinField = new WholeNumberField(3);
161 _limitMinField.addKeyListener(optionsChangedListener);
162 timeLimitPanel.add(_limitMinField);
163 timeLimitPanel.add(new JLabel(I18nManager.getText("units.minutes")));
164 timeLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
165 limitsPanel.add(timeLimitPanel);
167 dialogPanel.add(limitsPanel, BorderLayout.NORTH);
169 // button panel at bottom
170 JPanel buttonPanel = new JPanel();
171 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
173 _okButton = new JButton(I18nManager.getText("button.ok"));
174 _okButton.addActionListener(new ActionListener() {
175 public void actionPerformed(ActionEvent e) {
179 buttonPanel.add(_okButton);
181 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
182 cancelButton.addActionListener(new ActionListener() {
183 public void actionPerformed(ActionEvent e) {
187 cancelButton.addKeyListener(new KeyAdapter() {
188 public void keyPressed(KeyEvent inE) {
189 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
192 buttonPanel.add(cancelButton);
193 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
198 * Enable or disable the OK button according to the inputs
200 private void enableOkButton()
202 boolean enabled = false;
203 if (_distLimitRadio.isSelected()) {
204 enabled = _distanceField.getValue() > 0;
206 else if (_timeLimitRadio.isSelected()) {
207 enabled = _limitHourField.getValue() > 0 || _limitMinField.getValue() > 0;
209 _okButton.setEnabled(enabled);
211 // Also enable/disable the other fields
212 _distanceField.setEnabled(_distLimitRadio.isSelected());
213 _distUnitsDropdown.setEnabled(_distLimitRadio.isSelected());
214 _limitHourField.setEnabled(_timeLimitRadio.isSelected());
215 _limitMinField.setEnabled(_timeLimitRadio.isSelected());
219 * The dialog has been completed and OK pressed, so do the split
221 private void performSplit()
223 // Split either by distance or time
224 boolean checkTimeLimit = _timeLimitRadio.isSelected()
225 && (_limitHourField.getValue() > 0 || _limitMinField.getValue() > 0);
226 int timeLimitSeconds = 0;
229 timeLimitSeconds = _limitHourField.getValue() * 60 * 60
230 + _limitMinField.getValue() * 60;
231 if (timeLimitSeconds <= 0) {checkTimeLimit = false;}
233 double distLimitRadians = 0.0;
234 final boolean checkDistLimit = _distLimitRadio.isSelected()
235 && _distanceField.getValue() > 0;
238 final Unit[] distUnits = {UnitSetLibrary.UNITS_KILOMETRES,
239 UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_MILES};
240 Unit distUnit = distUnits[_distUnitsDropdown.getSelectedIndex()];
241 distLimitRadians = Distance.convertDistanceToRadians(_distanceField.getValue(), distUnit);
243 if (!checkTimeLimit && !checkDistLimit) {
244 return; // neither option selected
248 UndoSplitSegments undo = new UndoSplitSegments(_app.getTrackInfo().getTrack());
249 final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
250 DataPoint currPoint = null, prevPoint = null;
251 int numSplitsMade = 0;
253 // Now actually do it, looping through the points in the track
254 for (int i=0; i<numPoints; i++)
256 currPoint = _app.getTrackInfo().getTrack().getPoint(i);
257 if (!currPoint.isWaypoint())
259 boolean splitHere = (prevPoint != null)
260 && ((checkDistLimit && DataPoint.calculateRadiansBetween(prevPoint, currPoint) > distLimitRadians)
261 || (checkTimeLimit && currPoint.hasTimestamp() && prevPoint.hasTimestamp()
262 && currPoint.getTimestamp().getSecondsSince(prevPoint.getTimestamp()) > timeLimitSeconds));
263 if (splitHere && !currPoint.getSegmentStart())
265 currPoint.setSegmentStart(true);
268 prevPoint = currPoint;
272 if (numSplitsMade > 0)
274 _app.completeFunction(undo, I18nManager.getTextWithNumber("confirm.splitsegments", numSplitsMade));
275 UpdateMessageBroker.informSubscribers();
280 // Complain that no split was made
281 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.tracksplit.nosplit"),
282 I18nManager.getText("error.function.noop.title"), JOptionPane.WARNING_MESSAGE);