package de.saring.exerciseviewer.gui.panels; import de.saring.exerciseviewer.data.EVExercise; import de.saring.exerciseviewer.data.Lap; import de.saring.exerciseviewer.gui.EVDocument; import de.saring.util.gui.javafx.NumberCellFactory; import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import de.saring.exerciseviewer.gui.EVContext; import de.saring.util.gui.javafx.FormattedNumberCellFactory; /** * Controller (MVC) class of the "Laps" panel, which displays all recorded laps of the exercise in a table. * * @author Stefan Saring */ public class LapPanelController extends AbstractPanelController { @FXML private TableView<LapRow> tvLaps; @FXML private TableColumn<LapRow, Number> tcLap; @FXML private TableColumn<LapRow, Number> tcLapTime; @FXML private TableColumn<LapRow, Number> tcSplitTime; @FXML private TableColumn<LapRow, Number> tcHeartrate; @FXML private TableColumn<LapRow, Number> tcAvgHeartrate; @FXML private TableColumn<LapRow, Number> tcMaxHeartrate; @FXML private TableColumn<LapRow, Number> tcEndSpeed; @FXML private TableColumn<LapRow, Number> tcAvgSpeed; @FXML private TableColumn<LapRow, Number> tcDistance; @FXML private TableColumn<LapRow, Number> tcCadence; @FXML private TableColumn<LapRow, Number> tcAltitude; @FXML private TableColumn<LapRow, Number> tcAscent; @FXML private TableColumn<LapRow, Number> tcTemperature; /** * Standard c'tor for dependency injection. * * @param context the ExerciseViewer UI context * @param document the ExerciseViewer document / model */ public LapPanelController(final EVContext context, final EVDocument document) { super(context, document); } @Override protected String getFxmlFilename() { return "/fxml/panels/LapPanel.fxml"; } @Override protected void setupPanel() { // setup table columns tcLap.setCellValueFactory(new PropertyValueFactory<>("lapNumber")); tcLapTime.setCellValueFactory(new PropertyValueFactory<>("lapTime")); tcSplitTime.setCellValueFactory(new PropertyValueFactory<>("splitTime")); tcHeartrate.setCellValueFactory(new PropertyValueFactory<>("heartrate")); tcAvgHeartrate.setCellValueFactory(new PropertyValueFactory<>("avgHeartrate")); tcMaxHeartrate.setCellValueFactory(new PropertyValueFactory<>("maxHeartrate")); tcEndSpeed.setCellValueFactory(new PropertyValueFactory<>("endSpeed")); tcAvgSpeed.setCellValueFactory(new PropertyValueFactory<>("avgSpeed")); tcDistance.setCellValueFactory(new PropertyValueFactory<>("distance")); tcCadence.setCellValueFactory(new PropertyValueFactory<>("cadence")); tcAltitude.setCellValueFactory(new PropertyValueFactory<>("altitude")); tcAscent.setCellValueFactory(new PropertyValueFactory<>("ascent")); tcTemperature.setCellValueFactory(new PropertyValueFactory<>("temperature")); // setup custom number cell factories for all table columns tcLap.setCellFactory(new NumberCellFactory<>()); tcLapTime.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().tenthSeconds2TimeString(value.intValue()))); tcSplitTime.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().tenthSeconds2TimeString(value.intValue()))); tcHeartrate.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().heartRateToString(value.intValue()))); tcAvgHeartrate.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().heartRateToString(value.intValue()))); tcMaxHeartrate.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().heartRateToString(value.intValue()))); tcEndSpeed.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().speedToString(value.floatValue(), 2))); tcAvgSpeed.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().speedToString(value.floatValue(), 2))); tcDistance.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().distanceToString(value.intValue() / 1000d, 3))); tcCadence.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().cadenceToString(value.intValue()))); tcAltitude.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().heightToString(value.intValue()))); tcAscent.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().heightToString(value.intValue()))); tcTemperature.setCellFactory(new FormattedNumberCellFactory<>(value -> value == null ? null : getContext().getFormatUtils().temperatureToString(value.shortValue()))); // set table data tvLaps.setPlaceholder(new Label(getContext().getResources().getString("pv.info.no_data_available"))); final LapRow[] lapRows = LapRow.createLapRows(getDocument().getExercise()); tvLaps.setItems(FXCollections.observableArrayList(lapRows)); // default sort is the time column tvLaps.getSortOrder().add(tcLap); } /** * Container class which contains the data of one row in the Laps table. It contains attributes for * each column of the Laps table. This class is needed for easy mapping by the PropertyValueFactory, * the data of many columns is not provided directly from the Lap class. */ public static final class LapRow { private int lapNumber; private Integer lapTime; private Integer splitTime; private Integer heartrate; private Integer avgHeartrate; private Integer maxHeartrate; private Float endSpeed; private Float avgSpeed; private Integer distance; private Integer cadence; private Integer altitude; private Integer ascent; private Integer temperature; private LapRow() { } /** * Creates an array of LapRow objects for the laps in the specified exercise. * * @param exercise exercise with laps * @return array of LapRows */ public static LapRow[] createLapRows(final EVExercise exercise) { final Lap[] laps = exercise.getLapList(); if (laps == null || laps.length == 0) { return new LapRow[0]; } final LapRow[] lapRows = new LapRow[laps.length]; for (int rowNr = 0; rowNr < laps.length; rowNr++) { final Lap lap = laps[rowNr]; final LapRow lapRow = new LapRow(); lapRows[rowNr] = lapRow; lapRow.lapNumber = rowNr + 1; // lap time (= split time of current - split time of previous lap) int previousLapSplitTime = 0; if (rowNr > 0) { previousLapSplitTime = laps[rowNr - 1].getTimeSplit(); } lapRow.lapTime = lap.getTimeSplit() - previousLapSplitTime; lapRow.splitTime = lap.getTimeSplit(); lapRow.heartrate = Integer.valueOf(lap.getHeartRateSplit()); lapRow.avgHeartrate = Integer.valueOf(lap.getHeartRateAVG()); lapRow.maxHeartrate = Integer.valueOf(lap.getHeartRateMax()); if (lap.getSpeed() != null) { lapRow.endSpeed = lap.getSpeed().getSpeedEnd(); lapRow.avgSpeed = lap.getSpeed().getSpeedAVG(); lapRow.distance = lap.getSpeed().getDistance(); if (exercise.getRecordingMode().isCadence()) { lapRow.cadence = Integer.valueOf(lap.getSpeed().getCadence()); } } if (lap.getAltitude() != null) { lapRow.altitude = Integer.valueOf(lap.getAltitude().getAltitude()); // ascent at lap split (lap ascent can't be displayed for HRM files) if (exercise.getFileType() != EVExercise.ExerciseFileType.HRM) { lapRow.ascent = lap.getAltitude().getAscent(); } } if (lap.getTemperature() != null) { lapRow.temperature = Integer.valueOf(lap.getTemperature().getTemperature()); } } return lapRows; } /** * Returns the appropriate column value. * * @return lap number */ public int getLapNumber() { return lapNumber; } /** * Returns the appropriate column value. * * @return time (duration) of this lap */ public Integer getLapTime() { return lapTime; } /** * Returns the appropriate column value. * * @return time (duration) at lap end */ public Integer getSplitTime() { return splitTime; } /** * Returns the appropriate column value. * * @return heartrate at lap end */ public Integer getHeartrate() { return heartrate; } /** * Returns the appropriate column value. * * @return avg heartrate in lap */ public Integer getAvgHeartrate() { return avgHeartrate; } /** * Returns the appropriate column value. * * @return max heartrate in lap */ public Integer getMaxHeartrate() { return maxHeartrate; } /** * Returns the appropriate column value. * * @return speed at lap end */ public Float getEndSpeed() { return endSpeed; } /** * Returns the appropriate column value. * * @return avg speed in lap */ public Float getAvgSpeed() { return avgSpeed; } /** * Returns the appropriate column value. * * @return distance at lap end */ public Integer getDistance() { return distance; } /** * Returns the appropriate column value. * * @return cadence at lap end */ public Integer getCadence() { return cadence; } /** * Returns the appropriate column value. * * @return altitude at lap end */ public Integer getAltitude() { return altitude; } /** * Returns the appropriate column value. * * @return ascent in lap */ public Integer getAscent() { return ascent; } /** * Returns the appropriate column value. * * @return temperature at lap end */ public Integer getTemperature() { return temperature; } } }