/* 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.undo; 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.NavigationPosition; import slash.navigation.converter.gui.models.PositionColumnValues; import slash.navigation.converter.gui.models.PositionsModel; import slash.navigation.converter.gui.models.PositionsModelImpl; import slash.navigation.gui.events.ContinousRange; import slash.navigation.gui.events.Range; import slash.navigation.gui.events.RangeOperation; import slash.navigation.gui.undo.UndoManager; import javax.swing.event.TableModelListener; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import static java.lang.Integer.MAX_VALUE; import static java.util.Collections.singletonList; import static slash.common.io.Transfer.trim; /** * Implements a undo/redo-supporting {@link PositionsModel} for the positions of a {@link BaseRoute}. * * @author Christian Pesch */ public class UndoPositionsModel implements PositionsModel { private final PositionsModelImpl delegate = new PositionsModelImpl(); private final UndoManager undoManager; public UndoPositionsModel(UndoManager undoManager) { this.undoManager = undoManager; } // TableModel public int getRowCount() { return delegate.getRowCount(); } public int getColumnCount() { return delegate.getColumnCount(); } public String getColumnName(int columnIndex) { return delegate.getColumnName(columnIndex); } public Class<?> getColumnClass(int columnIndex) { return delegate.getColumnClass(columnIndex); } public boolean isCellEditable(int rowIndex, int columnIndex) { return delegate.isCellEditable(rowIndex, columnIndex); } public Object getValueAt(int rowIndex, int columnIndex) { return delegate.getValueAt(rowIndex, columnIndex); } public void setValueAt(Object aValue, int rowIndex, int columnIndex) { edit(rowIndex, new PositionColumnValues(columnIndex, aValue), true, true); } private String getStringAt(int rowIndex, int columnIndex) { return delegate.getStringAt(rowIndex, columnIndex); } public void edit(int rowIndex, PositionColumnValues columnToValues, boolean fireEvent, boolean trackUndo) { if (rowIndex == getRowCount()) return; if(trackUndo) { List<Object> previousValues = new ArrayList<>(columnToValues.getColumnIndices().size()); for (int columnIndex : columnToValues.getColumnIndices()) { previousValues.add(trim(getStringAt(rowIndex, columnIndex))); } columnToValues.setPreviousValues(previousValues); } delegate.edit(rowIndex, columnToValues, fireEvent, trackUndo); if (trackUndo) undoManager.addEdit(new EditPosition(this, rowIndex, columnToValues)); } public void addTableModelListener(TableModelListener l) { delegate.addTableModelListener(l); } public void removeTableModelListener(TableModelListener l) { delegate.removeTableModelListener(l); } private static final int CONTINOUS_RANGE_FINAL_EVENT = -2; public boolean isContinousRange() { return delegate.isContinousRange(); } public void fireTableRowsUpdated(int firstIndex, int lastIndex, int columnIndex) { delegate.fireTableRowsUpdated(firstIndex, lastIndex, columnIndex); } // PositionsModel public BaseRoute getRoute() { return delegate.getRoute(); } public void setRoute(BaseRoute route) { delegate.setRoute(route); } public NavigationPosition getPosition(int rowIndex) { return delegate.getPosition(rowIndex); } public int getIndex(NavigationPosition position) { return delegate.getIndex(position); } public List<NavigationPosition> getPositions(int[] rowIndices) { return delegate.getPositions(rowIndices); } public List<NavigationPosition> getPositions(int firstIndex, int lastIndex) { return delegate.getPositions(firstIndex, lastIndex); } public int[] getContainedPositions(BoundingBox boundingBox) { return delegate.getContainedPositions(boundingBox); } public int[] getPositionsWithinDistanceToPredecessor(double distance) { return delegate.getPositionsWithinDistanceToPredecessor(distance); } public int[] getInsignificantPositions(double threshold) { return delegate.getInsignificantPositions(threshold); } public int getClosestPosition(double longitude, double latitude, double threshold) { return delegate.getClosestPosition(longitude, latitude, threshold); } public int getClosestPosition(CompactCalendar time, long threshold) { return delegate.getClosestPosition(time, threshold); } // Undoable operations 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)); } public void add(int rowIndex, BaseRoute<BaseNavigationPosition, BaseNavigationFormat> route) throws IOException { List<BaseNavigationPosition> positions = delegate.createPositions(route); add(rowIndex, positions); } public void add(int rowIndex, List<BaseNavigationPosition> positions) { add(rowIndex, new ArrayList<NavigationPosition>(positions), true, true); } @SuppressWarnings("unchecked") void add(int row, List<NavigationPosition> positions, boolean fireEvent, boolean trackUndo) { for (int i = positions.size() - 1; i >= 0; i--) { NavigationPosition position = positions.get(i); getRoute().add(row, (BaseNavigationPosition) position); } if (fireEvent) delegate.fireTableRowsInserted(row, row - 1 + positions.size()); if (trackUndo) undoManager.addEdit(new AddPositions(this, row, positions)); } public void remove(int firstIndex, int lastIndex) { remove(firstIndex, lastIndex, true, true); } public void remove(int[] rowIndices) { remove(rowIndices, true, true); } void remove(int from, int to, boolean fireEvent, boolean trackUndo) { int[] rows = delegate.createRowIndices(from, to); remove(rows, fireEvent, trackUndo); } void remove(int[] rows, final boolean fireEvent, final boolean trackUndo) { final RemovePositions edit = new RemovePositions(this); new ContinousRange(rows, new RangeOperation() { private List<NavigationPosition> removed = new ArrayList<>(); public void performOnIndex(int index) { removed.add(0, getRoute().remove(index)); } public void performOnRange(int firstIndex, int lastIndex) { if (fireEvent) delegate.fireTableRowsDeletedInContinousRange(firstIndex, lastIndex); if (trackUndo) edit.add(firstIndex, removed); removed = new ArrayList<>(); } public boolean isInterrupted() { return false; } }).performMonotonicallyDecreasing(); if (fireEvent) fireTableRowsUpdated(0, MAX_VALUE, CONTINOUS_RANGE_FINAL_EVENT); if (trackUndo) undoManager.addEdit(edit); } public void sort(Comparator<NavigationPosition> comparator) { sort(comparator, true); } void sort(Comparator<NavigationPosition> comparator, boolean trackUndo) { @SuppressWarnings("unchecked") List<NavigationPosition> original = getRoute().getPositions(); List<NavigationPosition> positions = new ArrayList<>(original); delegate.sort(comparator); if (trackUndo) undoManager.addEdit(new SortPositions(this, comparator, positions)); } public void order(List<NavigationPosition> positions) { delegate.order(positions); } public void revert() { revert(true); } void revert(boolean trackUndo) { delegate.revert(); if (trackUndo) undoManager.addEdit(new RevertPositions(this)); } public void top(int[] rowIndices) { top(rowIndices, true); } void top(int[] rows, boolean trackUndo) { delegate.top(rows); if (trackUndo) undoManager.addEdit(new TopPositions(this, rows)); } void topDown(int[] rows) { delegate.topDown(rows); } public void up(int[] rowIndices, int delta) { up(rowIndices, delta, true); } void up(int[] rows, int delta, boolean trackUndo) { delegate.up(rows, delta); if (trackUndo) undoManager.addEdit(new UpPositions(this, rows, delta)); } public void down(int[] rowIndices, int delta) { down(rowIndices, delta, true); } void down(int[] rows, int delta, boolean trackUndo) { delegate.down(rows, delta); if (trackUndo) undoManager.addEdit(new DownPositions(this, Range.revert(rows), delta)); } public void bottom(int[] rowIndices) { bottom(rowIndices, true); } void bottom(int[] rows, boolean trackUndo) { delegate.bottom(rows); if (trackUndo) undoManager.addEdit(new BottomPositions(this, Range.revert(rows))); } void bottomUp(int[] rows) { delegate.bottomUp(rows); } }