package de.saring.sportstracker.gui.views.listviews; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; import de.saring.sportstracker.gui.views.ViewPrinter; import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.FXML; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableRow; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javax.inject.Inject; import javax.inject.Singleton; import de.saring.sportstracker.core.STOptions; import de.saring.sportstracker.data.Equipment; import de.saring.sportstracker.data.Exercise; import de.saring.sportstracker.data.Exercise.IntensityType; import de.saring.sportstracker.data.SportSubType; import de.saring.sportstracker.data.SportType; import de.saring.sportstracker.gui.STContext; import de.saring.sportstracker.gui.STDocument; import de.saring.util.StringUtils; import de.saring.util.data.IdObject; import de.saring.util.gui.javafx.ColorUtils; import de.saring.util.gui.javafx.FormattedNumberCellFactory; import de.saring.util.gui.javafx.LocalDateCellFactory; import javafx.util.Callback; /** * Controller class of the Exercise List View, which displays all the user exercises * (or a filtered list) in a table view. * * @author Stefan Saring */ @Singleton public class ExerciseListViewController extends AbstractListViewController<Exercise> { @FXML private TableView<Exercise> tvExercises; @FXML private TableColumn<Exercise, LocalDateTime> tcDate; @FXML private TableColumn<Exercise, Object> tcSportType; @FXML private TableColumn<Exercise, Object> tcSportSubtype; @FXML private TableColumn<Exercise, Number> tcDuration; @FXML private TableColumn<Exercise, Exercise.IntensityType> tcIntensity; @FXML private TableColumn<Exercise, Number> tcDistance; @FXML private TableColumn<Exercise, Number> tcAvgSpeed; @FXML private TableColumn<Exercise, Number> tcAvgHeartrate; @FXML private TableColumn<Exercise, Number> tcAscent; @FXML private TableColumn<Exercise, Number> tcEnergy; @FXML private TableColumn<Exercise, Object> tcEquipment; @FXML private TableColumn<Exercise, Object> tcComment; /** * Standard c'tor for dependency injection. * * @param context the SportsTracker UI context * @param document the SportsTracker document / model * @param viewPrinter the printer of the SportsTracker views */ @Inject public ExerciseListViewController(final STContext context, final STDocument document, final ViewPrinter viewPrinter) { super(context, document, viewPrinter); } @Override public ViewType getViewType() { return ViewType.EXERCISE_LIST; } @Override public int getSelectedExerciseCount() { return getSelectedEntryCount(); } @Override public int[] getSelectedExerciseIDs() { return getSelectedEntryIDs(); } @Override public void selectEntry(final IdObject entry) { if (entry != null && entry instanceof Exercise) { selectAndScrollToEntry((Exercise) entry); } } @Override protected String getFxmlFilename() { return "/fxml/views/ExerciseListView.fxml"; } @Override protected TableView<Exercise> getTableView() { return tvExercises; } @Override protected void setupTableColumns() { // setup factories for providing cell values tcDate.setCellValueFactory(new PropertyValueFactory<>("dateTime")); tcSportType.setCellValueFactory(cellData -> { final SportType sportType = cellData.getValue().getSportType(); return new SimpleObjectProperty<>(sportType == null ? null : sportType.getName()); }); tcSportSubtype.setCellValueFactory(cellData -> { final SportSubType sportSubType = cellData.getValue().getSportSubType(); return new SimpleObjectProperty<>(sportSubType == null ? null : sportSubType.getName()); }); tcDuration.setCellValueFactory(new PropertyValueFactory<>("duration")); tcIntensity.setCellValueFactory(new PropertyValueFactory<>("intensity")); tcDistance.setCellValueFactory(new PropertyValueFactory<>("distance")); tcAvgSpeed.setCellValueFactory(new PropertyValueFactory<>("avgSpeed")); tcAvgHeartrate.setCellValueFactory(new PropertyValueFactory<>("avgHeartRate")); tcAscent.setCellValueFactory(new PropertyValueFactory<>("ascent")); tcEnergy.setCellValueFactory(new PropertyValueFactory<>("calories")); tcEquipment.setCellValueFactory(cellData -> { final Equipment equipment = cellData.getValue().getEquipment(); return new SimpleObjectProperty<>(equipment == null ? null : equipment.getName()); }); tcEquipment.setCellValueFactory(cellData -> new SimpleObjectProperty<>( // cellData.getValue().getEquipment() == null ? null : cellData.getValue().getEquipment().getName())); tcComment.setCellValueFactory(cellData -> new SimpleObjectProperty<>( // StringUtils.getFirstLineOfText(cellData.getValue().getComment()))); // setup custom factories for displaying cells tcDate.setCellFactory(new LocalDateCellFactory()); tcDuration.setCellFactory(new FormattedNumberCellFactory<>(value -> // value == null ? null : getContext().getFormatUtils().seconds2TimeString(value.intValue()))); tcIntensity.setCellFactory(new IntensityCellFactory()); tcDistance.setCellFactory(new FormattedNumberCellFactory<>(value -> // value == null ? null : getContext().getFormatUtils().distanceToString(value.doubleValue(), 3))); tcAvgSpeed.setCellFactory(new FormattedNumberCellFactory<>(value -> // value == null ? null : getContext().getFormatUtils().speedToString(value.floatValue(), 2))); tcAvgHeartrate.setCellFactory(new FormattedNumberCellFactory<>(value -> // value == null ? null : getContext().getFormatUtils().heartRateToString(value.intValue()))); tcAscent.setCellFactory(new FormattedNumberCellFactory<>(value -> // value == null ? null : getContext().getFormatUtils().heightToString(value.intValue()))); tcEnergy.setCellFactory(new FormattedNumberCellFactory<>(value -> // value == null ? null : getContext().getFormatUtils().caloriesToString(value.intValue()))); // set initial visibility of optional columns as configured in preferences final STOptions options = getDocument().getOptions(); tcAvgHeartrate.setVisible(options.isListViewShowAvgHeartrate()); tcAscent.setVisible(options.isListViewShowAscent()); tcEnergy.setVisible(options.isListViewShowEnergy()); tcEquipment.setVisible(options.isListViewShowEquipment()); tcComment.setVisible(options.isListViewShowComment()); } @Override protected void setupDefaultSorting() { // default sort order is by date descending tcDate.setSortType(TableColumn.SortType.DESCENDING); tvExercises.getSortOrder().addAll(tcDate); } @Override protected List<Exercise> getTableEntries() { return getDocument().getFilterableExerciseList().stream().collect(Collectors.toList()); } @Override protected void updateTableRowColor(final TableRow<Exercise> tableRow) { // use text color of sport type for the table row (or white when the row is selected and the table is focused) // => tableRow.setTextFill() does not work here, color must be set by a CSS style final Exercise exercise = tableRow.getItem(); if (exercise != null) { final boolean useDefaultColor = tableRow.isSelected() && tvExercises.isFocused(); final String color = useDefaultColor ? "white" : ColorUtils.toRGBCode(exercise.getSportType().getColor()); tableRow.setStyle("-fx-text-background-color: " + color + ";"); } } /** * TableColumn cell factory implementation for displaying the exercise intensity name as localized text inside * table cells. */ public class IntensityCellFactory implements Callback<TableColumn<Exercise, IntensityType>, TableCell<Exercise, IntensityType>> { @Override public TableCell<Exercise, IntensityType> call(final TableColumn<Exercise, IntensityType> column) { return new TableCell<Exercise, IntensityType>() { @Override protected void updateItem(final IntensityType value, final boolean empty) { super.updateItem(value, empty); String text = empty || value == null ? null : getContext().getResources().getString(value.getResourceKey()); setText(text); } }; } } }