/* * Copyright 2010-2020 Australian Signals Directorate * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package au.gov.asd.tac.constellation.views.analyticview.visualisation; import au.gov.asd.tac.constellation.utilities.color.ConstellationColor; import au.gov.asd.tac.constellation.views.analyticview.results.AnalyticData; import au.gov.asd.tac.constellation.views.analyticview.results.AnalyticResult; import au.gov.asd.tac.constellation.views.analyticview.results.AnalyticResult.ResultListener; import au.gov.asd.tac.constellation.views.analyticview.translators.AbstractTableTranslator; import java.util.HashMap; import java.util.List; import java.util.Map; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener.Change; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import javafx.geometry.Insets; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.SelectionMode; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.VBox; /** * * @author cygnus_x-1 * @param <C> */ public class TableVisualisation<C extends AnalyticData> extends InternalVisualisation implements ResultListener<C> { private final AbstractTableTranslator<? extends AnalyticResult<?>, C> translator; private final VBox tableVisualisation; private final TextField tableFilter; private final TableView<C> table; private final Map<String, TableColumn<C, Object>> tableColumns = new HashMap<>(); private ListChangeListener<C> currentListener = null; public TableVisualisation(final AbstractTableTranslator<? extends AnalyticResult<?>, C> translator) { this.translator = translator; this.tableVisualisation = new VBox(); this.tableFilter = new TextField(); tableFilter.setPromptText("Type here to filter results"); this.table = new TableView<>(); table.setPlaceholder(new Label("No results")); table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); table.setId("table-visualisation"); table.setPadding(new Insets(5)); tableVisualisation.getChildren().addAll(tableFilter, table); } public void addColumn(final String columnName, final int percentWidth) { final TableColumn<C, Object> column = new TableColumn<>(columnName); tableColumns.put(columnName, column); column.prefWidthProperty().bind(table.widthProperty().multiply(percentWidth / 100.0)); column.setCellValueFactory(cellData -> new SimpleObjectProperty<>(translator.getCellData(cellData.getValue(), columnName))); column.setCellFactory(columnData -> { return new TableCell<C, Object>() { @Override public void updateItem(final Object item, final boolean empty) { super.updateItem(item, empty); if (item != null) { this.setText(translator.getCellText(this.getTableRow().getItem(), item, columnName)); final ConstellationColor color = translator.getCellColor(this.getTableRow().getItem(), item, columnName); this.setBackground(new Background(new BackgroundFill(color.getJavaFXColor(), CornerRadii.EMPTY, Insets.EMPTY))); } } }; }); column.setSortable(true); table.getColumns().add(column); } public void populateTable(final List<C> items) { final ObservableList<C> tableData = FXCollections.observableArrayList(items); final FilteredList<C> filteredData = new FilteredList<>(tableData, predicate -> true); filteredData.addListener((Change<? extends C> change) -> table.refresh()); tableFilter.textProperty().addListener((observable, oldValue, newValue) -> { filteredData.setPredicate(item -> { if (newValue == null || newValue.isEmpty()) { return true; } final String lowerCaseFilter = newValue.toLowerCase(); return item.getIdentifier().toLowerCase().contains(lowerCaseFilter); }); }); final SortedList<C> sortedData = new SortedList<>(filteredData); sortedData.comparatorProperty().bind(table.comparatorProperty()); table.setItems(sortedData); } public List<C> getSelectedItems() { return table.getSelectionModel().getSelectedItems(); } public void setSelectionModelListener(final ListChangeListener<C> listener) { if (currentListener != null) { table.getSelectionModel().getSelectedItems().removeListener(currentListener); } if (listener != null) { table.getSelectionModel().getSelectedItems().addListener(listener); } currentListener = listener; } @Override public String getName() { return "Table"; } @Override public Node getVisualisation() { return tableVisualisation; } @Override public void resultChanged(final List<C> selectedItems, final List<C> ignoredItems) { Platform.runLater(() -> { // remove the selection change listener final ListChangeListener<C> listener = currentListener; setSelectionModelListener(null); // add items from the ignored list which are currently selected final int[] selectionIndices = new int[selectedItems.size() + ignoredItems.size()]; if (!ignoredItems.isEmpty()) { final List<C> currentSelection = table.getSelectionModel().getSelectedItems(); ignoredItems.forEach(item -> { if (currentSelection.contains(item)) { selectionIndices[selectedItems.size() + ignoredItems.indexOf(item)] = table.getItems().indexOf(item); } }); } // add all items from the selected list if (!selectedItems.isEmpty()) { selectedItems.forEach(item -> { selectionIndices[selectedItems.indexOf(item)] = table.getItems().indexOf(item); }); } // clear the table selection and then make the new selection table.getSelectionModel().clearSelection(); if (selectionIndices.length > 0) { table.getSelectionModel().selectIndices(selectionIndices[0], selectionIndices); } // add the selection change listener back setSelectionModelListener(listener); }); } }