package tim.prune.function.sew; import java.awt.BorderLayout; import java.awt.Component; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import tim.prune.App; import tim.prune.GenericFunction; import tim.prune.I18nManager; import tim.prune.UpdateMessageBroker; import tim.prune.data.DataPoint; import tim.prune.data.Distance; import tim.prune.data.Field; import tim.prune.data.Unit; import tim.prune.data.UnitSetLibrary; import tim.prune.gui.WholeNumberField; import tim.prune.undo.UndoSplitSegments; /** * Function to split a track into segments using * either a distance limit or a time limit */ public class SplitSegmentsFunction extends GenericFunction { /** Dialog */ private JDialog _dialog = null; /** Radio buttons for splitting by distance and time */ private JRadioButton _distLimitRadio = null, _timeLimitRadio = null; /** Dropdown for selecting distance units */ private JComboBox _distUnitsDropdown = null; /** Text field for entering distance */ private WholeNumberField _distanceField = null; /** Text fields for entering distance */ private WholeNumberField _limitHourField = null, _limitMinField = null; /** Ok button */ private JButton _okButton = null; /** * React to item changes and key presses */ private abstract class ChangeListener extends KeyAdapter implements ItemListener { /** Method to be implemented */ public abstract void optionsChanged(); /** Item changed in ItemListener */ public void itemStateChanged(ItemEvent arg0) { optionsChanged(); } /** Key released in KeyListener */ public void keyReleased(KeyEvent arg0) { optionsChanged(); } } /** * Constructor */ public SplitSegmentsFunction(App inApp) { super(inApp); } /** * @return name key */ public String getNameKey() { return "function.splitsegments"; } /** * 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(); } enableOkButton(); // TODO: Maybe set distance units according to current Config setting? final boolean hasTimestamps = _app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP); _timeLimitRadio.setEnabled(hasTimestamps); _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)); // Make radio buttons for three different options _distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit") + ": "); _timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + ": "); ButtonGroup radioGroup = new ButtonGroup(); radioGroup.add(_distLimitRadio); radioGroup.add(_timeLimitRadio); // central panel for limits JPanel limitsPanel = new JPanel(); limitsPanel.setLayout(new BoxLayout(limitsPanel, BoxLayout.Y_AXIS)); limitsPanel.add(Box.createVerticalStrut(8)); ChangeListener optionsChangedListener = new ChangeListener() { public void optionsChanged() { enableOkButton(); } }; // distance limits JPanel distLimitPanel = new JPanel(); distLimitPanel.setLayout(new FlowLayout()); _distLimitRadio.setSelected(true); _distLimitRadio.addItemListener(optionsChangedListener); distLimitPanel.add(_distLimitRadio); _distanceField = new WholeNumberField(3); _distanceField.addKeyListener(optionsChangedListener); distLimitPanel.add(_distanceField); String[] distUnitsOptions = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.metres"), I18nManager.getText("units.miles")}; _distUnitsDropdown = new JComboBox(distUnitsOptions); _distUnitsDropdown.addItemListener(optionsChangedListener); distLimitPanel.add(_distUnitsDropdown); distLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT); limitsPanel.add(distLimitPanel); // time limit panel JPanel timeLimitPanel = new JPanel(); timeLimitPanel.setLayout(new FlowLayout()); _timeLimitRadio.addItemListener(optionsChangedListener); timeLimitPanel.add(_timeLimitRadio); _limitHourField = new WholeNumberField(2); _limitHourField.addKeyListener(optionsChangedListener); timeLimitPanel.add(_limitHourField); timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.hours"))); _limitMinField = new WholeNumberField(3); _limitMinField.addKeyListener(optionsChangedListener); timeLimitPanel.add(_limitMinField); timeLimitPanel.add(new JLabel(I18nManager.getText("units.minutes"))); timeLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT); limitsPanel.add(timeLimitPanel); dialogPanel.add(limitsPanel, BorderLayout.NORTH); // 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) { performSplit(); } }); buttonPanel.add(_okButton); // 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; } /** * Enable or disable the OK button according to the inputs */ private void enableOkButton() { boolean enabled = false; if (_distLimitRadio.isSelected()) { enabled = _distanceField.getValue() > 0; } else if (_timeLimitRadio.isSelected()) { enabled = _limitHourField.getValue() > 0 || _limitMinField.getValue() > 0; } _okButton.setEnabled(enabled); // Also enable/disable the other fields _distanceField.setEnabled(_distLimitRadio.isSelected()); _distUnitsDropdown.setEnabled(_distLimitRadio.isSelected()); _limitHourField.setEnabled(_timeLimitRadio.isSelected()); _limitMinField.setEnabled(_timeLimitRadio.isSelected()); } /** * The dialog has been completed and OK pressed, so do the split */ private void performSplit() { // Split either by distance or time boolean checkTimeLimit = _timeLimitRadio.isSelected() && (_limitHourField.getValue() > 0 || _limitMinField.getValue() > 0); int timeLimitSeconds = 0; if (checkTimeLimit) { timeLimitSeconds = _limitHourField.getValue() * 60 * 60 + _limitMinField.getValue() * 60; if (timeLimitSeconds <= 0) {checkTimeLimit = false;} } double distLimitRadians = 0.0; final boolean checkDistLimit = _distLimitRadio.isSelected() && _distanceField.getValue() > 0; if (checkDistLimit) { final Unit[] distUnits = {UnitSetLibrary.UNITS_KILOMETRES, UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_MILES}; Unit distUnit = distUnits[_distUnitsDropdown.getSelectedIndex()]; distLimitRadians = Distance.convertDistanceToRadians(_distanceField.getValue(), distUnit); } if (!checkTimeLimit && !checkDistLimit) { return; // neither option selected } // Make undo object UndoSplitSegments undo = new UndoSplitSegments(_app.getTrackInfo().getTrack()); final int numPoints = _app.getTrackInfo().getTrack().getNumPoints(); DataPoint currPoint = null, prevPoint = null; int numSplitsMade = 0; // Now actually do it, looping through the points in the track for (int i=0; i distLimitRadians) || (checkTimeLimit && currPoint.hasTimestamp() && prevPoint.hasTimestamp() && currPoint.getTimestamp().getSecondsSince(prevPoint.getTimestamp()) > timeLimitSeconds)); if (splitHere && !currPoint.getSegmentStart()) { currPoint.setSegmentStart(true); numSplitsMade++; } prevPoint = currPoint; } } if (numSplitsMade > 0) { _app.completeFunction(undo, I18nManager.getTextWithNumber("confirm.splitsegments", numSplitsMade)); UpdateMessageBroker.informSubscribers(); _dialog.dispose(); } else { // Complain that no split was made JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.tracksplit.nosplit"), I18nManager.getText("error.function.noop.title"), JOptionPane.WARNING_MESSAGE); } } }