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<String> _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<String>(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<numPoints; i++)
{
currPoint = _app.getTrackInfo().getTrack().getPoint(i);
if (!currPoint.isWaypoint())
{
boolean splitHere = (prevPoint != null)
&& ((checkDistLimit && DataPoint.calculateRadiansBetween(prevPoint, currPoint) > 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);
}
}
}