package de.saring.sportstracker.gui.views.listviews;
import java.util.List;
import de.saring.sportstracker.gui.views.ViewPrinter;
import javafx.beans.binding.Bindings;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import de.saring.sportstracker.gui.STContext;
import de.saring.sportstracker.gui.STDocument;
import de.saring.sportstracker.gui.views.AbstractEntryViewController;
import de.saring.util.data.IdObject;
/**
* Abstract controller base class of all List Views which are displaying SportsTracker entries in a table.
*
* @param <T> type of list entry
* @author Stefan Saring
*/
public abstract class AbstractListViewController<T extends IdObject> extends AbstractEntryViewController {
/**
* 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
*/
public AbstractListViewController(final STContext context, final STDocument document, final ViewPrinter viewPrinter) {
super(context, document, viewPrinter);
}
@Override
public void updateView() {
getTableView().getItems().setAll(getTableEntries());
// re-sorting must be forced after updating table content
getTableView().sort();
}
@Override
public void removeSelection() {
getTableView().getSelectionModel().clearSelection();
}
/**
* Returns the TableView control of this list view.
*
* @return TableView
*/
protected abstract TableView<T> getTableView();
@Override
protected void setupView() {
setupTableColumns();
setupDefaultSorting();
// The context menu is defined in FXML for the table view, but it needs to be shown for the
// table rows. Otherwise it's displayed for empty rows or the table header. It can't be defined
// for the table rows in FXML, so move it from the table view to the table rows here.
final ContextMenu contextMenu = getTableView().getContextMenu();
getTableView().setContextMenu(null);
setupTableRowFactory(contextMenu);
// user can select multiple entries
getTableView().getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// update controller-actions and the status bar on selection changes
getTableView().getSelectionModel().getSelectedIndices().addListener( //
(ListChangeListener<Integer>) change -> getEventHandler().updateActionsAndStatusBar());
}
/**
* Sets up all the columns of the table. This method is called only once on list view creation.
*/
protected abstract void setupTableColumns();
/**
* Sets up the default sorting of the table. This method is called only once on list view creation.
*/
protected abstract void setupDefaultSorting();
/**
* Returns list of entries to be displayed in the table. This method is called each time
* the view needs to be updated.
*
* @return list of entries
*/
protected abstract List<T> getTableEntries();
/**
* Returns the number of selected table entries.
*
* @return number of selected entries
*/
protected int getSelectedEntryCount() {
return getTableView().getSelectionModel().getSelectedItems().size();
}
/**
* Returns the list of all selected entry IDs.
*
* @return array of IDs (can be empty)
*/
protected int[] getSelectedEntryIDs() {
final List<T> selectedEntries = getTableView().getSelectionModel().getSelectedItems();
final int[] selectedEntryIds = new int[selectedEntries.size()];
for (int i = 0; i < selectedEntryIds.length; i++) {
selectedEntryIds[i] = selectedEntries.get(i).getId();
}
return selectedEntryIds;
}
/**
* Selects the specified entry and ensures its visibility.
*
* @param entry entry, must not be null
*/
protected void selectAndScrollToEntry(final T entry) {
getTableView().getSelectionModel().select(entry);
getTableView().scrollTo(entry);
}
/**
* Called whenever the selection or the item value of a TableRow or when the focus of the
* TableView has been changed. This callback can be used to set custom table row colors.
* The default implementation does nothing.
*
* @param tableRow table row
*/
protected void updateTableRowColor(final TableRow<T> tableRow) {
}
private void setupTableRowFactory(final ContextMenu contextMenu) {
getTableView().setRowFactory(tableView -> {
final TableRow<T> tableRow = new TableRow<>();
// update table row color when the item value, the selection or the focus has been changed
tableRow.itemProperty().addListener((observable, oldValue, newValue) -> updateTableRowColor(tableRow));
tableRow.selectedProperty().addListener( //
(observable, oldValue, newValue) -> updateTableRowColor(tableRow));
getTableView().focusedProperty().addListener( //
(observable, oldValue, newValue) -> updateTableRowColor(tableRow));
// bind context menu to row, but only when the row is not empty
tableRow.contextMenuProperty().bind( //
Bindings.when(tableRow.emptyProperty()) //
.then((ContextMenu) null) //
.otherwise(contextMenu));
// add listener for double clicks for editing the selected entry (ignore in empty rows)
tableRow.setOnMouseClicked(event -> {
if (event.getClickCount() > 1 && getSelectedEntryCount() == 1 && !tableRow.isEmpty()) {
getEventHandler().onEditEntry(null);
}
});
return tableRow;
});
}
/**
* Event handler for the context menu item 'Add Exercise', it delegates the event to the STController.
*
* @param event ActionEvent
*/
@FXML
private void onAddExercise(final ActionEvent event) {
getEventHandler().onAddExercise(event);
}
/**
* Event handler for the context menu item 'Add Note', it delegates the event to the STController.
*
* @param event ActionEvent
*/
@FXML
private void onAddNote(final ActionEvent event) {
getEventHandler().onAddNote(event);
}
/**
* Event handler for the context menu item 'Add Weight', it delegates the event to the STController.
*
* @param event ActionEvent
*/
@FXML
private void onAddWeight(final ActionEvent event) {
getEventHandler().onAddWeight(event);
}
/**
* Event handler for the context menu item 'Edit Entry', it delegates the event to the STController.
*
* @param event ActionEvent
*/
@FXML
private void onEditEntry(final ActionEvent event) {
getEventHandler().onEditEntry(event);
}
/**
* Event handler for the context menu item 'Copy Entry', it delegates the event to the STController.
*
* @param event ActionEvent
*/
@FXML
private void onCopyEntry(final ActionEvent event) {
getEventHandler().onCopyEntry(event);
}
/**
* Event handler for the context menu item 'Delete Entry', it delegates the event to the STController.
*
* @param event ActionEvent
*/
@FXML
private void onDeleteEntry(final ActionEvent event) {
getEventHandler().onDeleteEntry(event);
}
}