]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/function/sew/SplitSegmentsFunction.java
91a7d526d5fd4b0655fa5b9f8144ad12e5f60256
[GpsPrune.git] / tim / prune / function / sew / SplitSegmentsFunction.java
1 package tim.prune.function.sew;
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.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;
23
24 import tim.prune.App;
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;
35
36 /**
37  * Function to split a track into segments using
38  * either a distance limit or a time limit
39  */
40 public class SplitSegmentsFunction extends GenericFunction
41 {
42         /** Dialog */
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;
52         /** Ok button */
53         private JButton _okButton = null;
54
55
56         /**
57          * React to item changes and key presses
58          */
59         private abstract class ChangeListener extends KeyAdapter implements ItemListener
60         {
61                 /** Method to be implemented */
62                 public abstract void optionsChanged();
63
64                 /** Item changed in ItemListener */
65                 public void itemStateChanged(ItemEvent arg0) {
66                         optionsChanged();
67                 }
68
69                 /** Key released in KeyListener */
70                 public void keyReleased(KeyEvent arg0) {
71                         optionsChanged();
72                 }
73         }
74
75         /**
76          * Constructor
77          */
78         public SplitSegmentsFunction(App inApp) {
79                 super(inApp);
80         }
81
82         /**
83          * @return name key
84          */
85         public String getNameKey() {
86                 return "function.splitsegments";
87         }
88
89         /**
90          * Begin the function
91          */
92         public void begin()
93         {
94                 if (_dialog == null)
95                 {
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());
100                         _dialog.pack();
101                 }
102                 enableOkButton();
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);
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 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);
124
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() {
131                                 enableOkButton();
132                         }
133                 };
134                 // distance limits
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);
150
151                 // time limit panel
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);
166
167                 dialogPanel.add(limitsPanel, BorderLayout.NORTH);
168
169                 // button panel at bottom
170                 JPanel buttonPanel = new JPanel();
171                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
172                 // OK button
173                 _okButton = new JButton(I18nManager.getText("button.ok"));
174                 _okButton.addActionListener(new ActionListener() {
175                         public void actionPerformed(ActionEvent e) {
176                                 performSplit();
177                         }
178                 });
179                 buttonPanel.add(_okButton);
180                 // Cancel button
181                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
182                 cancelButton.addActionListener(new ActionListener() {
183                         public void actionPerformed(ActionEvent e) {
184                                 _dialog.dispose();
185                         }
186                 });
187                 cancelButton.addKeyListener(new KeyAdapter() {
188                         public void keyPressed(KeyEvent inE) {
189                                 if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
190                         }
191                 });
192                 buttonPanel.add(cancelButton);
193                 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
194                 return dialogPanel;
195         }
196
197         /**
198          * Enable or disable the OK button according to the inputs
199          */
200         private void enableOkButton()
201         {
202                 boolean enabled = false;
203                 if (_distLimitRadio.isSelected()) {
204                         enabled = _distanceField.getValue() > 0;
205                 }
206                 else if (_timeLimitRadio.isSelected()) {
207                         enabled = _limitHourField.getValue() > 0 || _limitMinField.getValue() > 0;
208                 }
209                 _okButton.setEnabled(enabled);
210
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());
216         }
217
218         /**
219          * The dialog has been completed and OK pressed, so do the split
220          */
221         private void performSplit()
222         {
223                 // Split either by distance or time
224                 boolean checkTimeLimit = _timeLimitRadio.isSelected()
225                         && (_limitHourField.getValue() > 0 || _limitMinField.getValue() > 0);
226                 int timeLimitSeconds = 0;
227                 if (checkTimeLimit)
228                 {
229                         timeLimitSeconds = _limitHourField.getValue() * 60 * 60
230                                 + _limitMinField.getValue() * 60;
231                         if (timeLimitSeconds <= 0) {checkTimeLimit = false;}
232                 }
233                 double distLimitRadians = 0.0;
234                 final boolean checkDistLimit = _distLimitRadio.isSelected()
235                         && _distanceField.getValue() > 0;
236                 if (checkDistLimit)
237                 {
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);
242                 }
243                 if (!checkTimeLimit && !checkDistLimit) {
244                         return; // neither option selected
245                 }
246
247                 // Make undo object
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;
252
253                 // Now actually do it, looping through the points in the track
254                 for (int i=0; i<numPoints; i++)
255                 {
256                         currPoint = _app.getTrackInfo().getTrack().getPoint(i);
257                         if (!currPoint.isWaypoint())
258                         {
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())
264                                 {
265                                         currPoint.setSegmentStart(true);
266                                         numSplitsMade++;
267                                 }
268                                 prevPoint = currPoint;
269                         }
270                 }
271
272                 if (numSplitsMade > 0)
273                 {
274                         _app.completeFunction(undo, I18nManager.getTextWithNumber("confirm.splitsegments", numSplitsMade));
275                         UpdateMessageBroker.informSubscribers();
276                         _dialog.dispose();
277                 }
278                 else
279                 {
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);
283                 }
284         }
285 }