/*
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.base.Wgs84Position;
import slash.navigation.common.BoundingBox;
import slash.navigation.common.DistanceAndTime;
import slash.navigation.common.NavigationPosition;
import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static javax.swing.event.TableModelEvent.ALL_COLUMNS;
import static slash.navigation.base.RouteCharacteristics.Route;
import static slash.navigation.base.RouteCharacteristics.Track;
import static slash.navigation.converter.gui.models.PositionColumns.DISTANCE_COLUMN_INDEX;
import static slash.navigation.converter.gui.models.PositionColumns.ELEVATION_ASCEND_COLUMN_INDEX;
import static slash.navigation.converter.gui.models.PositionColumns.ELEVATION_DESCEND_COLUMN_INDEX;
import static slash.navigation.converter.gui.models.PositionColumns.ELEVATION_DIFFERENCE_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.PHOTO_COLUMN_INDEX;
import static slash.navigation.gui.helpers.ImageHelper.resize;
/**
* Caches {@link DistanceAndTime}, {@link ImageAndFile} for a {@link PositionsModelImpl}.
*
* @author Christian Pesch
*/
public class OverlayPositionsModel implements PositionsModel {
private static final int IMAGE_HEIGHT_FOR_IMAGE_COLUMN = 200;
private final PositionsModel delegate;
private Map<Integer, ImageAndFile> indexToImageAndFile = new HashMap<>();
private final Map<Integer, DistanceAndTime> indexToDistanceAndTime = new HashMap<>();
private double[] distancesFromStart = null;
public OverlayPositionsModel(PositionsModel delegate) {
this.delegate = delegate;
delegate.addTableModelListener(new TableModelListener() {
public void tableChanged(TableModelEvent e) {
// clear overlay for updates on columns that have an effect on the distance
if (e.getColumn() == LONGITUDE_COLUMN_INDEX ||
e.getColumn() == LATITUDE_COLUMN_INDEX ||
e.getColumn() == ALL_COLUMNS)
clearOverlay();
}
});
}
public OverlayPositionsModel(PositionsModel delegate, CharacteristicsModel characteristicsModel) {
this(delegate);
characteristicsModel.addListDataListener(new ListDataListener() {
public void intervalAdded(ListDataEvent e) {
}
public void intervalRemoved(ListDataEvent e) {
}
public void contentsChanged(ListDataEvent e) {
// clear overlay when route characteristics is changed
clearOverlay();
}
});
}
private void clearOverlay() {
indexToDistanceAndTime.clear();
distancesFromStart = null;
indexToImageAndFile.clear();
delegate.fireTableRowsUpdated(0, getRoute().getPositionCount() - 1, DISTANCE_COLUMN_INDEX);
}
// 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 void setValueAt(Object aValue, int rowIndex, int columnIndex) {
delegate.setValueAt(aValue, rowIndex, columnIndex);
}
public void edit(int rowIndex, PositionColumnValues columnToValues, boolean fireEvent, boolean trackUndo) {
delegate.edit(rowIndex, columnToValues, fireEvent, trackUndo);
}
public void addTableModelListener(TableModelListener l) {
delegate.addTableModelListener(l);
}
public void removeTableModelListener(TableModelListener l) {
delegate.removeTableModelListener(l);
}
public boolean isContinousRange() {
return delegate.isContinousRange();
}
// 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);
}
public void add(int rowIndex, Double longitude, Double latitude, Double elevation, Double speed, CompactCalendar time, String description) {
delegate.add(rowIndex, longitude, latitude, elevation, speed, time, description);
}
public void add(int rowIndex, BaseRoute<BaseNavigationPosition, BaseNavigationFormat> route) throws IOException {
delegate.add(rowIndex, route);
}
public void add(int rowIndex, List<BaseNavigationPosition> positions) {
delegate.add(rowIndex, positions);
}
public void remove(int firstIndex, int lastIndex) {
delegate.remove(firstIndex, lastIndex);
}
public void remove(int[] rowIndices) {
delegate.remove(rowIndices);
}
public void sort(Comparator<NavigationPosition> comparator) {
delegate.sort(comparator);
}
public void revert() {
delegate.revert();
}
public void top(int[] rowIndices) {
delegate.top(rowIndices);
}
public void up(int[] rowIndices, int delta) {
delegate.up(rowIndices, delta);
}
public void down(int[] rowIndices, int delta) {
delegate.down(rowIndices, delta);
}
public void bottom(int[] rowIndices) {
delegate.bottom(rowIndices);
}
// Overlay operations
public Object getValueAt(int rowIndex, int columnIndex) {
switch (columnIndex) {
case PHOTO_COLUMN_INDEX:
return getImageAndFile(rowIndex);
case DISTANCE_COLUMN_INDEX:
return getDistance(rowIndex);
case ELEVATION_ASCEND_COLUMN_INDEX:
return getRoute().getElevationAscend(0, rowIndex);
case ELEVATION_DESCEND_COLUMN_INDEX:
return getRoute().getElevationDescend(0, rowIndex);
case ELEVATION_DIFFERENCE_COLUMN_INDEX:
return getRoute().getElevationDelta(rowIndex);
}
return delegate.getValueAt(rowIndex, columnIndex);
}
private ImageAndFile getImageAndFile(int rowIndex) {
ImageAndFile imageAndFile = indexToImageAndFile.get(rowIndex);
if (imageAndFile == null) {
NavigationPosition position = getPosition(rowIndex);
if (position instanceof Wgs84Position) {
Wgs84Position wgs84Position = Wgs84Position.class.cast(position);
File file = wgs84Position.getOrigin(File.class);
if (file != null && file.exists()) {
BufferedImage resize = resize(file, IMAGE_HEIGHT_FOR_IMAGE_COLUMN);
if(resize != null) {
imageAndFile = new ImageAndFile(new ImageIcon(resize), file);
indexToImageAndFile.put(rowIndex, imageAndFile);
}
}
}
}
return imageAndFile;
}
private Double getDistance(int rowIndex) {
if(getRoute().getCharacteristics().equals(Track)) {
if (distancesFromStart == null)
distancesFromStart = getRoute().getDistancesFromStart(0, getRoute().getPositionCount() - 1);
return distancesFromStart[rowIndex];
}
if (getRoute().getCharacteristics().equals(Route)) {
DistanceAndTime distanceAndTime = indexToDistanceAndTime.get(rowIndex);
return distanceAndTime != null ? distanceAndTime.getDistance() : null;
}
return null;
}
public void calculatedDistanceFromRouting(Map<Integer, DistanceAndTime> indexToRoutedDistanceAndTime) {
this.indexToDistanceAndTime.putAll(indexToRoutedDistanceAndTime);
int firstIndex = getRowCount() - 1;
int lastIndex = 0;
for (Integer index : this.indexToDistanceAndTime.keySet()) {
if (index < firstIndex)
firstIndex = index;
else if (index > lastIndex)
lastIndex = index;
}
delegate.fireTableRowsUpdated(firstIndex, lastIndex, DISTANCE_COLUMN_INDEX);
}
public void fireTableRowsUpdated(int firstIndex, int lastIndex, int columnIndex) {
delegate.fireTableRowsUpdated(firstIndex, lastIndex, columnIndex);
}
}