/*
* Copyright (C) 2015 thirdy
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package net.thirdy.blackmarket.controls;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ImmutableMap;
import io.jexiletools.es.model.ItemType;
import io.jexiletools.es.modsmapping.ModsMapping;
import io.jexiletools.es.modsmapping.ModsMapping.ModMapping;
import io.jexiletools.es.modsmapping.ModsMapping.ModType;
import io.jexiletools.es.modsmapping.ModsMapping.Type;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TextField;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import net.thirdy.blackmarket.domain.RangeOptional;
import net.thirdy.blackmarket.fxcontrols.DoubleTextField;
import net.thirdy.blackmarket.fxcontrols.RangeDoubleTextField;
import net.thirdy.blackmarket.fxcontrols.TableViewPlus;
import net.thirdy.blackmarket.fxcontrols.TriStateButton;
import net.thirdy.blackmarket.fxcontrols.TriStateButton.State;
public class ModSelectionPane extends GridPane implements Consumer<List<ItemType>> {
private TextField filterField;
private DoubleTextField tfMinShouldMatch;
private TableViewPlus<ModMapping> modMappingTable;
private ListView<Mod> modsListView;
private ObservableList<ModMapping> masterData = FXCollections.observableList(new LinkedList<>());
public ModSelectionPane() {
setHgap(5.0);
setMaxHeight(Double.MAX_VALUE);
setMinHeight(560);
setAlignment(Pos.CENTER);
setupModListView();
accept(Collections.emptyList());
setupModMappingTable();
setupFilterTextField();
tfMinShouldMatch = new DoubleTextField("Minimum number of OR modifiers to match");
tfMinShouldMatch.setMinWidth(350);
Button add = addButton();
add.setPrefWidth(150);
HBox hBox = new HBox(5, new Label("Filter: "), filterField, add);
hBox.setAlignment(Pos.CENTER);
VBox.setVgrow(modMappingTable, Priority.ALWAYS);
VBox left = new VBox(10, hBox, modMappingTable);
VBox.setVgrow(modsListView, Priority.ALWAYS);
Label modifiersLbl = new Label("Modifiers");
modifiersLbl.setFont(Font.font("Verdana", FontWeight.MEDIUM, 14));
modifiersLbl.setPadding(new Insets(4));
HBox minShouldMatchHBox = new HBox(3, new Label("Minimum OR Matches:"), tfMinShouldMatch);
minShouldMatchHBox.setAlignment(Pos.CENTER);
VBox right = new VBox(3, new StackPane(modifiersLbl), minShouldMatchHBox, modsListView);
setupGridPaneColumns();
GridPane.setVgrow(left, Priority.ALWAYS);
GridPane.setVgrow(right, Priority.ALWAYS);
add(left, 0, 0);
add(right, 1, 0);
}
private void setupGridPaneColumns() {
ColumnConstraints column1 = new ColumnConstraints();
column1.setPercentWidth(50);
ColumnConstraints column2 = new ColumnConstraints();
column2.setPercentWidth(50);
getColumnConstraints().addAll(column1, column2);
}
private Button addButton() {
Button add = new Button("Add");
add.setOnAction(e -> {
modMappingTable.getSelectionModel().getSelectedItems()
.stream().forEachOrdered(mm -> modsListView.getItems().add(new Mod(mm)));
});
return add;
}
private void setupModMappingTable() {
modMappingTable = new TableViewPlus<>(ImmutableMap.of(
"Item Type", "itemType",
"Type", "modType",
"Mod", "mapping"
),
new double[] {100, 100, 600},
masterData);
modMappingTable.setMaxHeight(Double.MAX_VALUE);
modMappingTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
modMappingTable.setId("modMappingTable");
}
private void setupModListView() {
modsListView = new ListView<>();
modsListView.setEditable(true);
modsListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
modsListView.setMaxHeight(Double.MAX_VALUE);
// remove focus
modsListView.setStyle("-fx-background-color: transparent, -fx-inner-border,"
+ " -fx-body-color; -fx-background-insets: 0, 1, 2; -fx-background-radius: 5, 4, 3;");
modsListView.setCellFactory((listView) -> new ModListCell(listView));
modsListView.setId("modsListView");
}
private void setupFilterTextField() {
filterField = new TextField();
filterField.setMinWidth(320);
filterField.textProperty().addListener((observable, oldValue, newValue) -> {
modMappingTable.getFilteredData().setPredicate(modmapping -> {
// If filter text is empty, display all.
if (newValue == null || newValue.isEmpty()) {
return true;
}
String lowerCaseFilter = newValue.toLowerCase();
String key = modmapping.getKey().toLowerCase();
return key.contains(lowerCaseFilter);
});
});
}
@Override
public void accept(List<ItemType> itemTypes) {
masterData.clear();
List<ModMapping> modMappings = ModsMapping.getInstance().getModMappings();
if (!itemTypes.isEmpty()) {
List<String> itemTypesRaw = itemTypes.stream()
.map(itemType -> itemType.itemType())
.collect(Collectors.toList());
modMappings = modMappings.stream()
.filter(mm -> mm.getModType() == ModType.PSEUDO
|| itemTypesRaw.contains(mm.getItemType()))
.collect(Collectors.toList());
}
Comparator<ModMapping> byModType = (m1, m2) ->
Integer.valueOf(m1.getModType().ordinal()).compareTo(Integer.valueOf(m2.getModType().ordinal()));
Comparator<ModMapping> byModTypeThenKey = byModType.thenComparing((m1, m2) ->
m1.getKey().compareTo(m2.getKey())
);
modMappings.sort(byModTypeThenKey);
masterData.addAll(modMappings);
}
public static class Mod {
ModMapping modMapping;
public Mod(ModMapping modMapping) {
this.modMapping = modMapping;
}
public ObjectProperty<Optional<RangeOptional>> lowerRange = new SimpleObjectProperty();
public ObjectProperty<Optional<RangeOptional>> higherRange = new SimpleObjectProperty();
public ObjectProperty<State> logic = new SimpleObjectProperty<>();
}
private static class ModListCell extends ListCell<Mod> {
private Label lblMod = new Label();
public RangeDoubleTextField lowerRangeDoubleTf = new RangeDoubleTextField();
public RangeDoubleTextField higherRangeDoubleTf = new RangeDoubleTextField();
public TriStateButton logic = new TriStateButton();
private Region spacer = new Region();
public HBox container = new HBox(5.0);
Button remove = new Button("X");
public ModListCell(ListView<Mod> listView) {
remove.setOnAction(e -> listView.getItems().remove(getIndex()));
HBox.setHgrow(spacer, Priority.ALWAYS);
lowerRangeDoubleTf.setPrefWidth(100);
higherRangeDoubleTf.setPrefWidth(100);
container.getChildren().addAll(lblMod, spacer, lowerRangeDoubleTf, higherRangeDoubleTf, logic, remove);
}
@Override
protected void updateItem(Mod item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
Type modType = item.modMapping.getType();
// Label
String key = item.modMapping.getKey();
key = StringUtils.removeEnd(key, ".min");
lblMod.setText(key);
// Lower Range
boolean showLowerRange = modType == Type.DOUBLE || modType == Type.DOUBLE_MIN_MAX;
item.lowerRange.unbind();
container.getChildren().remove(lowerRangeDoubleTf);
if (showLowerRange) {
container.getChildren().add(2, lowerRangeDoubleTf);
item.lowerRange.bind(Bindings.createObjectBinding(() -> {
return lowerRangeDoubleTf.val();
}, lowerRangeDoubleTf.getMin().textProperty(), lowerRangeDoubleTf.getMax().textProperty()));
}
// Higher Range
boolean showHigherRange = modType == Type.DOUBLE_MIN_MAX;
higherRangeDoubleTf.setVisible(showHigherRange);
item.higherRange.unbind();
container.getChildren().remove(higherRangeDoubleTf);
if (showHigherRange) {
container.getChildren().add(3, higherRangeDoubleTf);
item.higherRange.bind(Bindings.createObjectBinding(() -> {
return higherRangeDoubleTf.val();
}, higherRangeDoubleTf.getMin().textProperty(), higherRangeDoubleTf.getMax().textProperty()));
}
item.logic.unbind();
item.logic.bind(logic.stateProperty());
setGraphic(container);
} else {
setGraphic(null);
}
}
}
public Optional<List<Mod>> mods() {
return modsListView.getItems().size() == 0 ? Optional.empty() : Optional.of(modsListView.getItems());
}
public Optional<Double> mininumShouldMatch() {
return tfMinShouldMatch.getOptionalValue();
}
}