Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PopupComparableFilter and ComparableParser #1533

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -26,13 +26,6 @@
*/
package org.controlsfx.samples.tableview2;

import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
Expand Down Expand Up @@ -82,11 +75,23 @@
import org.controlsfx.control.tableview2.cell.ComboBox2TableCell;
import org.controlsfx.control.tableview2.cell.TextField2TableCell;
import org.controlsfx.control.tableview2.filter.filtereditor.SouthFilter;
import org.controlsfx.control.tableview2.filter.popupfilter.PopupComparableFilter;
import org.controlsfx.control.tableview2.filter.popupfilter.PopupFilter;
import org.controlsfx.control.tableview2.filter.popupfilter.PopupNumberFilter;
import org.controlsfx.control.tableview2.filter.popupfilter.PopupStringFilter;
import org.controlsfx.samples.Utils;

import java.time.LocalDate;
import java.time.Period;
import java.time.chrono.ChronoLocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.FormatStyle;
import java.time.temporal.ChronoField;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
*
* Build the UI and launch the Application
Expand Down Expand Up @@ -276,7 +281,7 @@ protected void updateItem(Number item, boolean empty) {
if (item != null && ! empty) {
setText(null);
circle.setFill(getIndex() % 5 == 0 ? Color.RED : Color.BLUE);
label.setText("" + table.getItems().get(getIndex()).getBirthday().getYear() + " " + String.valueOf(item));
label.setText("" + table.getItems().get(getIndex()).getBirthday().get(ChronoField.YEAR) + " " + String.valueOf(item));
box.setAlignment(Pos.CENTER);
setGraphic(box);
} else {
Expand Down Expand Up @@ -324,12 +329,31 @@ private Node buildFilteredTableViewControl() {

private class FilteredTableViewSample extends FilteredTableView<Person> {

private final StringConverter<ChronoLocalDate> chronoLocalDateConverter = new StringConverter<>() {
@Override
public String toString(ChronoLocalDate date) {
if (date == null) {
return "";
}
return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(date);
}

@Override
public ChronoLocalDate fromString(String string) {
try {
return LocalDate.parse(string, DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM));
} catch (DateTimeParseException ex) {
return null;
}
}
};

private final FilteredTableColumn<Person, String> firstName = new FilteredTableColumn<>("First Name");
private final FilteredTableColumn<Person, String> lastName = new FilteredTableColumn<>("Last Name");
private final FilteredTableColumn<Person, Integer> age = new FilteredTableColumn<>("Age");
private final FilteredTableColumn<Person, Color> color = new FilteredTableColumn<>("Color");
private final FilteredTableColumn<Person, String> city = new FilteredTableColumn<>("City");
private final FilteredTableColumn<Person, LocalDate> birthday = new FilteredTableColumn<>("Birthday");
private final FilteredTableColumn<Person, ChronoLocalDate> birthday = new FilteredTableColumn<>("Birthday");
private final FilteredTableColumn<Person, Boolean> active = new FilteredTableColumn<>("Active");
private SouthFilter<Person, String> editorFirstName;
private SouthFilter<Person, String> editorLastName;
Expand Down Expand Up @@ -392,21 +416,7 @@ protected void updateItem(Color item, boolean empty) {

birthday.setCellValueFactory(p -> p.getValue().birthdayProperty());
birthday.setPrefWidth(100);
birthday.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<LocalDate>() {
@Override
public String toString(LocalDate date) {
if (date == null) {
return "" ;
}
return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(date);
}

@Override
public LocalDate fromString(String string) {
return LocalDate.now();
}

}));
birthday.setCellFactory(TextFieldTableCell.forTableColumn(chronoLocalDateConverter));

active.setText("Active");
active.setCellValueFactory(p -> p.getValue().activeProperty());
Expand Down Expand Up @@ -617,6 +627,9 @@ public Color fromString(String string) {
}
});
color.setOnFilterAction(e -> popupColorFilter.showPopup());

PopupComparableFilter<Person, ChronoLocalDate> popupBirthdayFilter = new PopupComparableFilter<>(birthday, chronoLocalDateConverter);
birthday.setOnFilterAction(e -> popupBirthdayFilter.showPopup());
}
}

Expand All @@ -640,18 +653,18 @@ class Person {
private final IntegerProperty age = new SimpleIntegerProperty();
private final StringProperty city = new SimpleStringProperty();
private final BooleanProperty active = new SimpleBooleanProperty();
private final ObjectProperty<LocalDate> birthday = new SimpleObjectProperty<>();
private final ObjectProperty<ChronoLocalDate> birthday = new SimpleObjectProperty<>();
private final ObjectProperty<Color> color = new SimpleObjectProperty<>();

public final LocalDate getBirthday() {
public final ChronoLocalDate getBirthday() {
return birthday.get();
}

public final void setBirthday(LocalDate value) {
public final void setBirthday(ChronoLocalDate value) {
birthday.set(value);
}

public final ObjectProperty<LocalDate> birthdayProperty() {
public final ObjectProperty<ChronoLocalDate> birthdayProperty() {
return birthday;
}

Expand Down
@@ -0,0 +1,171 @@
/**
* Copyright (c) 2023 ControlsFX
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package impl.org.controlsfx.tableview2.filter.parser.comparable;

import impl.org.controlsfx.tableview2.filter.parser.Operation;
import impl.org.controlsfx.tableview2.filter.parser.aggregate.AggregatorsParser;
import javafx.util.StringConverter;
import org.controlsfx.control.tableview2.filter.parser.Parser;

import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static impl.org.controlsfx.i18n.Localization.asKey;
import static impl.org.controlsfx.i18n.Localization.localize;

public class ComparableParser<T extends Comparable<T>> implements Parser<T> {
private final ComparableOperation EQUALS = new ComparableOperation("text.equals", "symbol.equals") {
@Override
public Predicate<T> operate(T obj) {
return obj::equals;
}
};

private final ComparableOperation NOT_EQUALS = new ComparableOperation("text.notequals", "symbol.notequals") {
@Override
public Predicate<T> operate(T obj) {
return t -> !obj.equals(t);
}
};

private final ComparableOperation GREATER_THAN_EQUALS = new ComparableOperation("text.greaterthanequals", "symbol.greaterthanequals") {
@Override
public Predicate<T> operate(T obj) {
return t -> t != null && t.compareTo(obj) >= 0;
}
};

private final ComparableOperation GREATER_THAN = new ComparableOperation("text.greaterthan", "symbol.greaterthan") {
@Override
public Predicate<T> operate(T obj) {
return t -> t != null && t.compareTo(obj) > 0;
}
};

private final ComparableOperation LESS_THAN_EQUALS = new ComparableOperation("text.lessthanequals", "symbol.lessthanequals") {
@Override
public Predicate<T> operate(T obj) {
return t -> t != null && t.compareTo(obj) <= 0;
}
};

private final ComparableOperation LESS_THAN = new ComparableOperation("text.lessthan", "symbol.lessthan") {
@Override
public Predicate<T> operate(T obj) {
return t -> t != null && t.compareTo(obj) < 0;
}
};

private final List<ComparableOperation> operations = List.of(EQUALS, NOT_EQUALS, GREATER_THAN_EQUALS, GREATER_THAN, LESS_THAN_EQUALS, LESS_THAN);

private final StringConverter<T> stringConverter;

private String errorString = "";

public ComparableParser(StringConverter<T> stringConverter) {
this.stringConverter = stringConverter;
}

@Override
public Predicate<T> parse(String text) {
errorString = "";
Predicate<T> aggregation = aggregate(text);
if (aggregation == null) {
Optional<ComparableOperation> optionalOperation = operations.stream()
.filter(opr -> text.startsWith(opr.get()))
.filter(opr -> text.length() > opr.length())
.findFirst();
if (optionalOperation.isPresent()) {
ComparableOperation operation = optionalOperation.get();
String objText = text.substring(operation.length()).trim();
T obj = stringConverter.fromString(objText);
if (obj == null) {
errorString = localize(asKey("parser.text.error.comparable.input"));
return null;
}
return operation.operate(obj);
}
}
return aggregation;
}

@Override
public List<String> operators() {
return Stream.concat(
operations.stream().map(Operation::get),
AggregatorsParser.getStrings()
).collect(Collectors.toList());
}

@Override
public String getSymbol(String text) {
return operations.stream().filter(op -> op.get().equals(text)).map(Operation::getSymbol).findFirst().orElse(i18nString("symbol.default"));
}

@Override
public boolean isValid(String text) {
parse(text);
return errorString.isEmpty();
}

@Override
public String getErrorMessage() {
return errorString;
}

private abstract class ComparableOperation implements Operation<T, T> {
private final String opr;
private final String symbol;

ComparableOperation(String opr, String symbol) {
this.opr = i18nString(opr);
this.symbol = i18nString(symbol);
}

@Override
public int length() {
return opr.length();
}

@Override
public String get() {
return opr;
}

@Override
public String getSymbol() {
return symbol;
}
}

private static String i18nString(String key) {
return localize(asKey("parser.text.operator." + key));
}
}
@@ -0,0 +1,72 @@
/**
* Copyright (c) 2023 ControlsFX
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ControlsFX, any associated website, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.controlsfx.control.tableview2.filter.popupfilter;

import impl.org.controlsfx.tableview2.filter.parser.comparable.ComparableParser;
import javafx.util.StringConverter;
import org.controlsfx.control.tableview2.FilteredTableColumn;
import org.controlsfx.control.tableview2.filter.parser.Parser;

import java.util.List;

public class PopupComparableFilter<S, T extends Comparable<T>> extends PopupFilter<S, T> {

private final ComparableParser<T> comparableParser;

/**
* Creates a new instance of PopupComparableFilter.
* @param tableColumn TableColumn associated with this PopupFilter.
*/
public PopupComparableFilter(FilteredTableColumn<S, T> tableColumn, StringConverter<T> stringConverter) {
super(tableColumn);
comparableParser = new ComparableParser<>(stringConverter);

text.addListener((obs, ov, nv) -> {
if (nv == null || nv.isEmpty()) {
tableColumn.setPredicate(null);
} else {
tableColumn.setPredicate(getParser().parse(nv));
}
});
}

/**
* {@inheritDoc}
*/
@Override
public List<String> getOperations() {
return comparableParser.operators();
}

/**
* {@inheritDoc}
*/
@Override
public Parser<T> getParser() {
return comparableParser;
}
}
1 change: 1 addition & 0 deletions controlsfx/src/main/resources/controlsfx.properties
Expand Up @@ -97,6 +97,7 @@ popup.filter.case.sensitive.disable = Disable case-sensitive comparision
### Parser ###
parser.text.error.start.operator = Condition should start with an operator
parser.text.error.number.input = Input must be a number
parser.text.error.comparable.input = Malformed input
parser.text.error.string.start = should start with a "
parser.text.error.string.end = should end with a "

Expand Down