/* This file is part of RouteConverter. RouteConverter is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. RouteConverter is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RouteConverter; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Copyright (C) 2007 Christian Pesch. All Rights Reserved. */ package slash.navigation.converter.gui.models; import slash.common.type.CompactCalendar; import slash.navigation.base.BaseNavigationFormat; import slash.navigation.base.BaseNavigationPosition; import slash.navigation.base.BaseRoute; import slash.navigation.common.BoundingBox; import slash.navigation.common.DegreeFormat; import slash.navigation.common.NavigationPosition; import slash.navigation.common.UnitSystem; import slash.navigation.converter.gui.RouteConverter; import slash.navigation.converter.gui.helpers.PositionHelper; import slash.navigation.gui.events.ContinousRange; import slash.navigation.gui.events.Range; import slash.navigation.gui.events.RangeOperation; import javax.swing.event.TableModelEvent; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableModel; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import static java.util.Collections.singletonList; import static javax.swing.event.TableModelEvent.ALL_COLUMNS; import static javax.swing.event.TableModelEvent.DELETE; import static javax.swing.event.TableModelEvent.UPDATE; import static slash.common.io.Transfer.parseDouble; import static slash.common.io.Transfer.trim; import static slash.navigation.base.NavigationFormatConverter.convertPositions; import static slash.navigation.common.UnitConversion.ddmm2latitude; import static slash.navigation.common.UnitConversion.ddmm2longitude; import static slash.navigation.common.UnitConversion.ddmmss2latitude; import static slash.navigation.common.UnitConversion.ddmmss2longitude; import static slash.navigation.converter.gui.helpers.PositionHelper.extractDateTime; import static slash.navigation.converter.gui.helpers.PositionHelper.extractElevation; import static slash.navigation.converter.gui.helpers.PositionHelper.extractSpeed; import static slash.navigation.converter.gui.helpers.PositionHelper.extractTime; import static slash.navigation.converter.gui.helpers.PositionHelper.formatDate; import static slash.navigation.converter.gui.helpers.PositionHelper.formatLatitude; import static slash.navigation.converter.gui.helpers.PositionHelper.formatLongitude; import static slash.navigation.converter.gui.models.PositionColumns.DATE_COLUMN_INDEX; import static slash.navigation.converter.gui.models.PositionColumns.DATE_TIME_COLUMN_INDEX; import static slash.navigation.converter.gui.models.PositionColumns.DESCRIPTION_COLUMN_INDEX; import static slash.navigation.converter.gui.models.PositionColumns.ELEVATION_COLUMN_INDEX; import static slash.navigation.converter.gui.models.PositionColumns.LATITUDE_COLUMN_INDEX; import static slash.navigation.converter.gui.models.PositionColumns.LONGITUDE_COLUMN_INDEX; import static slash.navigation.converter.gui.models.PositionColumns.SPEED_COLUMN_INDEX; import static slash.navigation.converter.gui.models.PositionColumns.TIME_COLUMN_INDEX; /** * Implements the {@link PositionsModel} for the positions of a {@link BaseRoute}. * * @author Christian Pesch */ public class PositionsModelImpl extends AbstractTableModel implements PositionsModel { private BaseRoute route; public BaseRoute getRoute() { return route; } public void setRoute(BaseRoute route) { this.route = route; fireTableDataChanged(); } public int getRowCount() { return getRoute() != null ? getRoute().getPositionCount() : 0; } public int getColumnCount() { throw new IllegalArgumentException("This is determined by the PositionsTableColumnModel"); } public String getStringAt(int rowIndex, int columnIndex) { NavigationPosition position = getPosition(rowIndex); switch (columnIndex) { case DESCRIPTION_COLUMN_INDEX: return position.getDescription(); case DATE_TIME_COLUMN_INDEX: return extractDateTime(position); case TIME_COLUMN_INDEX: return extractTime(position); case LONGITUDE_COLUMN_INDEX: return formatLongitude(position.getLongitude()); case LATITUDE_COLUMN_INDEX: return formatLatitude(position.getLatitude()); case ELEVATION_COLUMN_INDEX: return extractElevation(position); case SPEED_COLUMN_INDEX: return extractSpeed(position); } throw new IllegalArgumentException("Row " + rowIndex + ", column " + columnIndex + " does not exist"); } public Object getValueAt(int rowIndex, int columnIndex) { return getPosition(rowIndex); } public NavigationPosition getPosition(int rowIndex) { return getRoute().getPosition(rowIndex); } @SuppressWarnings({"unchecked"}) public int getIndex(NavigationPosition position) { return getRoute().getIndex((BaseNavigationPosition) position); } public List<NavigationPosition> getPositions(int[] rowIndices) { List<NavigationPosition> result = new ArrayList<>(rowIndices.length); for (int rowIndex : rowIndices) result.add(getPosition(rowIndex)); return result; } public List<NavigationPosition> getPositions(int firstIndex, int lastIndex) { List<NavigationPosition> result = new ArrayList<>(lastIndex - firstIndex); for (int i = firstIndex; i < lastIndex; i++) result.add(getPosition(i)); return result; } public int[] getContainedPositions(BoundingBox boundingBox) { return getRoute().getContainedPositions(boundingBox); } public int[] getPositionsWithinDistanceToPredecessor(double distance) { return getRoute().getPositionsWithinDistanceToPredecessor(distance); } public int[] getInsignificantPositions(double threshold) { return getRoute().getInsignificantPositions(threshold); } public int getClosestPosition(double longitude, double latitude, double threshold) { return getRoute().getClosestPosition(longitude, latitude, threshold); } public int getClosestPosition(CompactCalendar time, long threshold) { return getRoute().getClosestPosition(time, threshold); } public boolean isCellEditable(int rowIndex, int columnIndex) { switch (columnIndex) { case DESCRIPTION_COLUMN_INDEX: case DATE_TIME_COLUMN_INDEX: case TIME_COLUMN_INDEX: case LONGITUDE_COLUMN_INDEX: case LATITUDE_COLUMN_INDEX: case ELEVATION_COLUMN_INDEX: case SPEED_COLUMN_INDEX: return true; default: return false; } } public void setValueAt(Object aValue, int rowIndex, int columnIndex) { edit(rowIndex, new PositionColumnValues(columnIndex, aValue), true, true); } public void edit(int rowIndex, PositionColumnValues columnToValues, boolean fireEvent, boolean trackUndo) { if (rowIndex == getRowCount()) return; if (columnToValues.getNextValues() != null) { for (int i = 0; i < columnToValues.getColumnIndices().size(); i++) { int columnIndex = columnToValues.getColumnIndices().get(i); editCell(rowIndex, columnIndex, columnToValues.getNextValues().get(i)); } } if (fireEvent) { if (columnToValues.getColumnIndices().size() > 1) fireTableRowsUpdated(rowIndex, rowIndex); else fireTableRowsUpdated(rowIndex, rowIndex, columnToValues.getColumnIndices().get(0)); } } private void editCell(int rowIndex, int columnIndex, Object value) { NavigationPosition position = getPosition(rowIndex); String string = value != null ? trim(value.toString()) : null; switch (columnIndex) { case DESCRIPTION_COLUMN_INDEX: position.setDescription(string); break; case DATE_TIME_COLUMN_INDEX: position.setTime(parseDateTime(value, string)); break; case DATE_COLUMN_INDEX: position.setTime(parseDate(value, string)); break; case TIME_COLUMN_INDEX: position.setTime(parseTime(value, string, position.getTime())); break; case LONGITUDE_COLUMN_INDEX: position.setLongitude(parseLongitude(value, string)); break; case LATITUDE_COLUMN_INDEX: position.setLatitude(parseLatitude(value, string)); break; case ELEVATION_COLUMN_INDEX: position.setElevation(parseElevation(value, string)); break; case SPEED_COLUMN_INDEX: position.setSpeed(parseSpeed(value, string)); break; default: throw new IllegalArgumentException("Row " + rowIndex + ", column " + columnIndex + " does not exist"); } } private Double parseLongitude(Object objectValue, String stringValue) { if (objectValue == null || objectValue instanceof Double) return (Double) objectValue; DegreeFormat degreeFormat = RouteConverter.getInstance().getUnitSystemModel().getDegreeFormat(); switch (degreeFormat) { case Degrees: return parseDouble(stringValue); case Degrees_Minutes: return ddmm2longitude(stringValue); case Degrees_Minutes_Seconds: return ddmmss2longitude(stringValue); default: throw new IllegalArgumentException("Degree format " + degreeFormat + " does not exist"); } } private Double parseLatitude(Object objectValue, String stringValue) { if (objectValue == null || objectValue instanceof Double) return (Double) objectValue; DegreeFormat degreeFormat = RouteConverter.getInstance().getUnitSystemModel().getDegreeFormat(); switch (degreeFormat) { case Degrees: return parseDouble(stringValue); case Degrees_Minutes: return ddmm2latitude(stringValue); case Degrees_Minutes_Seconds: return ddmmss2latitude(stringValue); default: throw new IllegalArgumentException("Degree format " + degreeFormat + " does not exist"); } } private Double parseDegrees(Object objectValue, String stringValue, String replaceAll) { if (objectValue == null || objectValue instanceof Double) return (Double) objectValue; if (replaceAll != null && stringValue != null) stringValue = stringValue.replaceAll(replaceAll, ""); return parseDouble(stringValue); } private Double parseElevation(Object objectValue, String stringValue) { UnitSystem unitSystem = RouteConverter.getInstance().getUnitSystemModel().getUnitSystem(); Double value = parseDegrees(objectValue, stringValue, unitSystem.getElevationName()); return unitSystem.valueToDefault(value); } private Double parseSpeed(Object objectValue, String stringValue) { UnitSystem unitSystem = RouteConverter.getInstance().getUnitSystemModel().getUnitSystem(); Double value = parseDegrees(objectValue, stringValue, unitSystem.getSpeedName()); return unitSystem.distanceToDefault(value); } private CompactCalendar parseDateTime(Object objectValue, String stringValue) { if (objectValue == null || objectValue instanceof CompactCalendar) { return (CompactCalendar) objectValue; } else if (stringValue != null) { try { return PositionHelper.parseDateTime(stringValue); } catch (ParseException e) { // intentionally left empty } } return null; } private CompactCalendar parseDate(Object objectValue, String stringValue) { if (objectValue == null || objectValue instanceof CompactCalendar) { return (CompactCalendar) objectValue; } else if (stringValue != null) { try { return PositionHelper.parseDate(stringValue); } catch (ParseException e) { // intentionally left empty } } return null; } private CompactCalendar parseTime(Object objectValue, String stringValue, CompactCalendar positionTime) { if (objectValue == null || objectValue instanceof CompactCalendar) { return (CompactCalendar) objectValue; } else if (stringValue != null) { try { if (positionTime != null) return PositionHelper.parseDateTime(formatDate(positionTime) + " " + stringValue); else return PositionHelper.parseTime(stringValue); } catch (ParseException e) { // intentionally left empty } } return null; } public void add(int rowIndex, Double longitude, Double latitude, Double elevation, Double speed, CompactCalendar time, String description) { BaseNavigationPosition position = getRoute().createPosition(longitude, latitude, elevation, speed, time, description); add(rowIndex, singletonList(position)); } @SuppressWarnings("unchecked") public List<BaseNavigationPosition> createPositions(BaseRoute<BaseNavigationPosition, BaseNavigationFormat> route) throws IOException { BaseNavigationFormat targetFormat = getRoute().getFormat(); return convertPositions((List) route.getPositions(), targetFormat); } public void add(int rowIndex, BaseRoute<BaseNavigationPosition, BaseNavigationFormat> route) throws IOException { List<BaseNavigationPosition> positions = createPositions(route); add(rowIndex, positions); } @SuppressWarnings({"unchecked"}) public void add(int rowIndex, List<BaseNavigationPosition> positions) { for (int i = positions.size() - 1; i >= 0; i--) { BaseNavigationPosition position = positions.get(i); getRoute().add(rowIndex, position); } fireTableRowsInserted(rowIndex, rowIndex - 1 + positions.size()); } public int[] createRowIndices(int from, int to) { int[] rows = new int[to - from]; int count = 0; for (int i = to - 1; i >= from; i--) rows[count++] = i; return rows; } public void remove(int firstIndex, int lastIndex) { remove(createRowIndices(firstIndex, lastIndex)); } public void remove(int[] rowIndices) { remove(rowIndices, true); } public void remove(int[] rows, final boolean fireEvent) { new ContinousRange(rows, new RangeOperation() { public void performOnIndex(int index) { getRoute().remove(index); } public void performOnRange(int firstIndex, int lastIndex) { if (fireEvent) fireTableRowsDeleted(firstIndex, lastIndex); } public boolean isInterrupted() { return false; } }).performMonotonicallyDecreasing(); } @SuppressWarnings("unchecked") public void sort(Comparator<NavigationPosition> comparator) { getRoute().sort(comparator); // since fireTableDataChanged(); is ignored in FormatAndRoutesModel#setModified(true) logic fireTableRowsUpdated(-1, -1); } @SuppressWarnings("unchecked") public void order(List<NavigationPosition> positions) { getRoute().order(positions); // since fireTableDataChanged(); is ignored in FormatAndRoutesModel#setModified(true) logic fireTableRowsUpdated(-1, -1); } public void revert() { getRoute().revert(); // since fireTableDataChanged(); is ignored in FormatAndRoutesModel#setModified(true) logic fireTableRowsUpdated(-1, -1); } public void top(int[] rowIndices) { Arrays.sort(rowIndices); for (int i = 0; i < rowIndices.length; i++) { getRoute().top(rowIndices[i], i); } fireTableRowsUpdated(0, rowIndices[rowIndices.length - 1]); } public void topDown(int[] rows) { int[] reverted = Range.revert(rows); for (int i = 0; i < reverted.length; i++) { getRoute().down(reverted.length - i - 1, reverted[i]); } fireTableRowsUpdated(0, reverted[0]); } public void up(int[] rowIndices, int delta) { Arrays.sort(rowIndices); for (int row : rowIndices) { getRoute().up(row, row - delta); fireTableRowsUpdated(row - delta, row); } } public void down(int[] rowIndices, int delta) { int[] reverted = Range.revert(rowIndices); for (int row : reverted) { getRoute().down(row, row + delta); fireTableRowsUpdated(row, row + delta); } } public void bottom(int[] rowIndices) { int[] reverted = Range.revert(rowIndices); for (int i = 0; i < reverted.length; i++) { getRoute().bottom(reverted[i], i); fireTableRowsUpdated(reverted[i], getRowCount() - 1 - i); } } public void bottomUp(int[] rows) { Arrays.sort(rows); for (int i = 0; i < rows.length; i++) { getRoute().up(getRowCount() - rows.length + i, rows[i]); } fireTableRowsUpdated(rows[0], getRowCount() - 1); } private TableModelEvent currentEvent; public void fireTableChanged(TableModelEvent e) { this.currentEvent = e; super.fireTableChanged(e); this.currentEvent = null; } public boolean isContinousRange() { return currentEvent != null && currentEvent instanceof ContinousRangeTableModelEvent; } public void fireTableRowsDeletedInContinousRange(int firstRow, int lastRow) { fireTableChanged(new ContinousRangeTableModelEvent(this, firstRow, lastRow, ALL_COLUMNS, DELETE)); } private static class ContinousRangeTableModelEvent extends TableModelEvent { @SuppressWarnings("MagicConstant") public ContinousRangeTableModelEvent(TableModel source, int firstRow, int lastRow, int column, int type) { super(source, firstRow, lastRow, column, type); } } public void fireTableRowsUpdated(int firstIndex, int lastIndex, int columnIndex) { fireTableChanged(new TableModelEvent(this, firstIndex, lastIndex, columnIndex, UPDATE)); } }