package com.github.pfichtner.jrunalyser.ui.lapinfo; import static com.google.common.collect.Iterables.getLast; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.util.List; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ScrollPaneConstants; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import com.github.pfichtner.jrunalyser.base.data.Distance; import com.github.pfichtner.jrunalyser.base.data.Duration; import com.github.pfichtner.jrunalyser.base.data.LinkedTrackPoint; import com.github.pfichtner.jrunalyser.base.data.Pace; import com.github.pfichtner.jrunalyser.base.data.Speed; import com.github.pfichtner.jrunalyser.base.data.floater.DefaultHighlightableSegment; import com.github.pfichtner.jrunalyser.base.data.segment.Segment; import com.github.pfichtner.jrunalyser.base.data.segmenter.Segmenter; import com.github.pfichtner.jrunalyser.base.data.segmenter.Segmenters; import com.github.pfichtner.jrunalyser.base.data.stat.CombinedStatistics; import com.github.pfichtner.jrunalyser.base.data.stat.DefaultStatistics; import com.github.pfichtner.jrunalyser.base.data.stat.Statistics; import com.github.pfichtner.jrunalyser.base.data.track.Track; import com.github.pfichtner.jrunalyser.di.Inject; import com.github.pfichtner.jrunalyser.ui.base.AbstractUiPlugin; import com.github.pfichtner.jrunalyser.ui.base.GridDataProvider; import com.github.pfichtner.jrunalyser.ui.base.i18n.I18N; import com.github.pfichtner.jrunalyser.ui.dock.ebus.SegmentSelectedMessage; import com.github.pfichtner.jrunalyser.ui.dock.ebus.SegmentationSelected; import com.github.pfichtner.jrunalyser.ui.dock.ebus.TrackLoaded; import com.github.pfichtner.jrunalyser.ui.table.renderers.DistanceRenderer; import com.github.pfichtner.jrunalyser.ui.table.renderers.DurationRenderer; import com.github.pfichtner.jrunalyser.ui.table.renderers.PaceRenderer; import com.github.pfichtner.jrunalyser.ui.table.renderers.PositionRendererDecorator; import com.github.pfichtner.jrunalyser.ui.table.renderers.SpeedRenderer; import com.google.common.collect.Lists; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; public class LapInfoPlugin extends AbstractUiPlugin implements GridDataProvider { private static final int COL_NR = 0; private static final int COL_LAPTIME = 1; private static final int COL_TOTALTIME = 2; private static final int COL_DISTANCE = 3; private static final int COL_TOTALDISTANCE = 4; private static final int COL_SPEED = 5; private static final int COL_PACE = 6; private static final I18N i18n = I18N .builder(LapInfoPlugin.class) .withParent( com.github.pfichtner.jrunalyser.ui.base.UiPlugins.getI18n()) .build(); /** * A PositionRendererDecorator that ignores the latest row if the segment is * incomplete. * * @author Peter Fichtner */ private static final class MinMaxRendererDecorator extends PositionRendererDecorator { public MinMaxRendererDecorator(TableCellRenderer delegate) { super(delegate); } @Override public int getRowCount(TableModel tm) { int rowCount = super.getRowCount(tm); return rowCount > 1 && tm instanceof LapInfoTableModel && getLast( ((LapInfoTableModel) tm).getRowAt(rowCount - 1) .getSegment().getTrackpoints()).getLink() == null ? rowCount - 1 : rowCount; } @Override protected void renderPos(JTable table, Component c, int pos) { if (pos == 0) { renderBest(c); } else if (pos == getRowCount(table.getModel()) - 1) { renderWorst(c); } } private void renderBest(Component c) { c.setBackground(Color.GREEN); } private void renderWorst(Component c) { c.setBackground(Color.RED); } } public static class InfoRow { private final int pos; private final Segment segment; private final Statistics overallStats; public InfoRow(int pos, Segment segment, Statistics overallStats) { this.pos = pos; this.segment = segment; this.overallStats = overallStats; } public int getPos() { return this.pos; } public Segment getSegment() { return this.segment; } public Statistics getOverallStats() { return this.overallStats; } } public static class LapInfoTableModel extends AbstractTableModel { private static final long serialVersionUID = 8199955700069773640L; private final List<InfoRow> data = Lists.newArrayList(); @Override public int getRowCount() { return this.data.size(); } public InfoRow getRowAt(int idx) { return this.data.get(idx); } @Override public int getColumnCount() { return 7; } @Override public Class<?> getColumnClass(int columnIndex) { switch (columnIndex) { case COL_NR: return Integer.class; case COL_LAPTIME: return Duration.class; case COL_TOTALTIME: return Duration.class; case COL_DISTANCE: return Distance.class; case COL_TOTALDISTANCE: return Distance.class; case COL_SPEED: return Speed.class; case COL_PACE: return Pace.class; default: throw new IllegalStateException("Unknown column " + columnIndex); //$NON-NLS-1$ } } @Override public Object getValueAt(int rowIndex, int columnIndex) { InfoRow infoRow = this.data.get(rowIndex); Segment segment = infoRow.getSegment(); switch (columnIndex) { case COL_NR: return Integer.valueOf(infoRow.getPos() + 1); case COL_LAPTIME: return segment.getStatistics().getDuration(); case COL_TOTALTIME: return infoRow.getOverallStats().getDuration(); case COL_DISTANCE: return segment.getStatistics().getDistance(); case COL_TOTALDISTANCE: return infoRow.getOverallStats().getDistance(); case COL_SPEED: case COL_PACE: return segment.getStatistics().getAvgSpeed(); default: throw new IllegalStateException("Unknown column " + columnIndex); //$NON-NLS-1$ } } public void clear() { this.data.clear(); fireTableDataChanged(); } public void addRow(InfoRow trackRow) { int row = this.data.size(); this.data.add(trackRow); fireTableRowsInserted(row, row); } } private Segmenter segmenter = Segmenter.NULL_SEGMENTER; private Track track; private EventBus eventBus; private JPanel panel = new JPanel(new BorderLayout()); private LapInfoTableModel tableModel = new LapInfoTableModel(); private JTable table; public LapInfoPlugin() { this.table = new JTable(this.tableModel); this.table.setColumnModel(createColumnModel(this.tableModel)); this.table.setDefaultRenderer(Distance.class, new DistanceRenderer()); this.table.setDefaultRenderer(Speed.class, new SpeedRenderer()); this.table.setDefaultRenderer(Duration.class, new DurationRenderer()); this.table.setAutoCreateRowSorter(true); this.table.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseMoved(MouseEvent e) { JTable jTable = (JTable) e.getSource(); LapInfoTableModel model = (LapInfoTableModel) jTable.getModel(); int idx = jTable.convertRowIndexToModel(jTable.rowAtPoint(e .getPoint())); Segment segment = model.getRowAt(idx).getSegment(); List<? extends LinkedTrackPoint> swps = segment .getTrackpoints(); Segment hls = new DefaultHighlightableSegment(swps, true, DefaultStatistics.ofWaypoints(swps)); LapInfoPlugin.this.eventBus.post(new SegmentSelectedMessage( idx, hls)); } }); this.panel.add(new JScrollPane(this.table, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED)); } private static TableColumnModel createColumnModel(TableModel tm) { DefaultTableColumnModel columnModel = new DefaultTableColumnModel(); columnModel.addColumn(createColumn(COL_NR, i18n.getText("com.github.pfichtner.jrunalyser.ui.LapInfoPlugin.colNr.tile"))); //$NON-NLS-1$ columnModel.addColumn(createColumn(COL_LAPTIME, i18n.getText("com.github.pfichtner.jrunalyser.ui.LapInfoPlugin.colLaptime.tile"))); //$NON-NLS-1$ columnModel.addColumn(createColumn(COL_TOTALTIME, i18n.getText("com.github.pfichtner.jrunalyser.ui.LapInfoPlugin.colTotaltime.tile"))); //$NON-NLS-1$ columnModel.addColumn(createColumn(COL_DISTANCE, i18n.getText("com.github.pfichtner.jrunalyser.ui.LapInfoPlugin.colDistance.tile"))); //$NON-NLS-1$ columnModel.addColumn(createColumn(COL_TOTALDISTANCE, i18n.getText("com.github.pfichtner.jrunalyser.ui.LapInfoPlugin.coltotalDistance.tile"))); //$NON-NLS-1$ columnModel.addColumn(createColumn(COL_SPEED, i18n.getText("com.github.pfichtner.jrunalyser.ui.LapInfoPlugin.colSpeed.tile"), //$NON-NLS-1$ new MinMaxRendererDecorator(new SpeedRenderer()))); columnModel.addColumn(createColumn(COL_PACE, i18n.getText("com.github.pfichtner.jrunalyser.ui.LapInfoPlugin.colPace.tile"), //$NON-NLS-1$ new MinMaxRendererDecorator(new PaceRenderer()))); return columnModel; } private static TableColumn createColumn(int modeIdx, String value) { TableColumn column = new TableColumn(modeIdx); column.setHeaderValue(value); return column; } private static TableColumn createColumn(int modelIdx, String value, TableCellRenderer cellRenderer) { TableColumn column = createColumn(modelIdx, value); column.setCellRenderer(cellRenderer); return column; } @Override public String getTitle() { return i18n .getText("com.github.pfichtner.jrunalyser.ui.LapInfoPlugin.title"); //$NON-NLS-1$ } @Override public JPanel getPanel() { return this.panel; } @Inject public void setEventBus(EventBus eventBus) { this.eventBus = eventBus; } @Subscribe public void setTrack(TrackLoaded message) { this.track = message.getTrack(); fillTable(); } @Subscribe public void setSegmenter(SegmentationSelected message) { this.segmenter = Segmenters.getSegmenter(message.getSegmentationUnit()); fillTable(); } private void fillTable() { if (this.track != null && this.segmenter != null) { this.tableModel.clear(); int i = 0; Statistics overall = null; for (Segment segment : this.segmenter.segment(this.track) .getSegments()) { Statistics next = segment.getStatistics(); overall = overall == null ? next : DefaultStatistics .copyOf(new CombinedStatistics(overall, next)); this.tableModel.addRow(new InfoRow(i++, segment, overall)); } } } }