package com.revolsys.swing.map.layer.record.component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;
import org.jdesktop.swingx.JXSearchField;
import com.revolsys.awt.WebColors;
import com.revolsys.datatype.DataType;
import com.revolsys.datatype.DataTypes;
import com.revolsys.io.BaseCloseable;
import com.revolsys.logging.Logs;
import com.revolsys.record.Record;
import com.revolsys.record.code.CodeTable;
import com.revolsys.record.query.BinaryCondition;
import com.revolsys.record.query.Column;
import com.revolsys.record.query.Condition;
import com.revolsys.record.query.ILike;
import com.revolsys.record.query.IsNotNull;
import com.revolsys.record.query.IsNull;
import com.revolsys.record.query.Not;
import com.revolsys.record.query.Q;
import com.revolsys.record.query.QueryValue;
import com.revolsys.record.query.RightUnaryCondition;
import com.revolsys.record.query.Value;
import com.revolsys.record.schema.FieldDefinition;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.swing.SwingUtil;
import com.revolsys.swing.field.AbstractRecordQueryField;
import com.revolsys.swing.field.ComboBox;
import com.revolsys.swing.field.DateField;
import com.revolsys.swing.field.Field;
import com.revolsys.swing.field.QueryWhereConditionField;
import com.revolsys.swing.field.TextField;
import com.revolsys.swing.layout.GroupLayouts;
import com.revolsys.swing.map.layer.AbstractLayer;
import com.revolsys.swing.map.layer.record.AbstractRecordLayer;
import com.revolsys.swing.map.layer.record.RecordDefinitionSqlFilter;
import com.revolsys.swing.map.layer.record.renderer.AbstractRecordLayerRenderer;
import com.revolsys.swing.map.layer.record.table.model.RecordLayerTableModel;
import com.revolsys.swing.parallel.Invoke;
import com.revolsys.swing.table.TablePanel;
import com.revolsys.util.Property;
import com.revolsys.value.GlobalBooleanValue;
public class FieldFilterPanel extends JComponent
implements ActionListener, ItemListener, DocumentListener, PropertyChangeListener {
private static final long serialVersionUID = 1L;
private CodeTable codeTable;
private final ComboBox<String> codeTableOperatorField = ComboBox.newComboBox("operator", "=",
"<>", "IS NULL", "IS NOT NULL");
private final ComboBox<String> dateOperatorField = ComboBox.newComboBox("operator", "=", "<>",
"IS NULL", "IS NOT NULL", "<", "<=", ">", ">=");
private FieldDefinition field;
private final List<String> fieldNames;
private final ComboBox<String> generalOperatorField = ComboBox.newComboBox("operator", "=", "<>",
"Like", "IS NULL", "IS NOT NULL");
private Object lastValue = null;
private final AbstractRecordLayer layer;
private ComboBox<String> nameField;
private final ComboBox<String> numericOperatorField = ComboBox.newComboBox("operator", "=", "<>",
"IS NULL", "IS NOT NULL", "<", "<=", ">", ">=");
private ComboBox<String> operatorField;
private final JPanel operatorFieldPanel = new JPanel();
private String previousSearchFieldName;
private final RecordDefinition recordDefinition;
private JComponent searchField;
private final JPanel searchFieldPanel = new JPanel();
private final TextField searchTextField = new TextField(20);
private final GlobalBooleanValue settingFilter = new GlobalBooleanValue(false);
private RecordLayerTableModel tableModel;
private final JLabel whereLabel = new JLabel();
public FieldFilterPanel(final TablePanel tablePanel, final RecordLayerTableModel tableModel) {
this.tableModel = tableModel;
this.layer = tableModel.getLayer();
this.recordDefinition = this.layer.getRecordDefinition();
this.fieldNames = new ArrayList<>(this.layer.getFieldNamesSet("All"));
this.fieldNames.removeAll(this.recordDefinition.getGeometryFieldNames());
if (this.fieldNames.isEmpty()) {
setVisible(false);
} else {
this.whereLabel.setMaximumSize(new Dimension(100, 250));
this.whereLabel.setFont(SwingUtil.FONT);
this.whereLabel.setOpaque(true);
this.whereLabel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLoweredBevelBorder(), BorderFactory.createEmptyBorder(1, 2, 1, 2)));
this.whereLabel.setBackground(WebColors.White);
add(this.whereLabel);
this.nameField = ComboBox.newComboBox("fieldNames", this.fieldNames,
(final Object fieldName) -> {
return this.layer.getFieldTitle((String)fieldName);
});
this.nameField.addActionListener(this);
add(this.nameField);
add(this.operatorFieldPanel);
this.searchField = this.searchTextField;
this.searchTextField.addActionListener(this);
this.searchTextField.setPreferredSize(new Dimension(200, 22));
add(this.searchFieldPanel);
GroupLayouts.makeColumns(this, 4, false);
clear();
}
}
public FieldFilterPanel(final TablePanel tablePanel, final RecordLayerTableModel tableModel,
final Map<String, Object> config) {
this(tablePanel, tableModel);
setSearchFieldName((String)config.get("searchField"));
final Predicate<Record> filter = AbstractRecordLayerRenderer.getFilter(this.layer, config);
if (filter instanceof RecordDefinitionSqlFilter) {
final RecordDefinitionSqlFilter sqlFilter = (RecordDefinitionSqlFilter)filter;
final Condition condition = sqlFilter.getCondition();
setFilter(condition);
}
}
@Override
public void actionPerformed(final ActionEvent event) {
if (this.settingFilter.isFalse()) {
try {
final Object source = event.getSource();
if (source == this.searchField) {
updateCondition();
} else if (source == this.nameField) {
setSearchFieldName(getSearchFieldName());
}
} catch (final Throwable e) {
Logs.error(this, "Unable to search", e);
}
}
}
private void addListeners(final JComponent component) {
if (component instanceof AbstractRecordQueryField) {
final AbstractRecordQueryField queryField = (AbstractRecordQueryField)component;
queryField.addPropertyChangeListener("selectedRecord", this);
} else if (component instanceof JXSearchField) {
final JXSearchField searchTextField = (JXSearchField)component;
searchTextField.addActionListener(this);
} else if (component instanceof JTextComponent) {
final JTextComponent searchTextField = (JTextComponent)component;
searchTextField.getDocument().addDocumentListener(this);
} else if (component instanceof DateField) {
final DateField dateField = (DateField)component;
dateField.addActionListener(this);
}
if (component instanceof Field) {
final Field field = (Field)component;
final String fieldName = field.getFieldName();
Property.addListener(field, fieldName, this);
}
}
@Override
public void changedUpdate(final DocumentEvent event) {
updateCondition();
}
public void clear() {
Invoke.later(() -> {
try (
BaseCloseable settingFilter = this.settingFilter.closeable(true)) {
String searchField = this.previousSearchFieldName;
if (!Property.hasValue(searchField)) {
searchField = this.recordDefinition.getFieldNames().get(0);
}
setSearchFieldName(searchField);
setFilter(Condition.ALL);
}
});
}
public void close() {
this.tableModel = null;
}
public void fireSearchChanged(final String propertyName, final Object oldValue,
final Object newValue) {
if (!DataType.equal(oldValue, newValue)) {
Invoke.background("Change search", () -> fireSearchChanged(propertyName, oldValue, newValue));
}
}
public List<String> getFieldNames() {
return this.fieldNames;
}
public Condition getFilter() {
return this.tableModel.getFilter();
}
public AbstractLayer getLayer() {
return this.layer;
}
public String getSearchFieldName() {
if (this.nameField != null) {
final String searchFieldName = this.nameField.getSelectedItem();
if (Property.hasValue(searchFieldName)) {
return searchFieldName;
}
}
return this.previousSearchFieldName;
}
public final String getSearchOperator() {
if (this.operatorField == null) {
return "=";
} else {
return this.operatorField.getSelectedItem();
}
}
public Object getSearchValue() {
if (this.searchField instanceof JTextComponent) {
final JTextComponent textComponent = (JTextComponent)this.searchField;
if (textComponent.isEditable()) {
return textComponent.getText();
} else {
return null;
}
} else {
final Object value = SwingUtil.getValue(this.searchField);
return value;
}
}
@Override
public void insertUpdate(final DocumentEvent event) {
updateCondition();
}
@Override
public void itemStateChanged(final ItemEvent e) {
if (this.settingFilter.isFalse()) {
if (e.getStateChange() == ItemEvent.SELECTED) {
final Object source = e.getSource();
if (source == this.nameField) {
final String searchFieldName = getSearchFieldName();
setSearchFieldName(searchFieldName);
} else if (source == this.operatorField) {
final String searchOperator = getSearchOperator();
setSearchOperator(searchOperator);
} else {
updateCondition();
}
}
}
}
@Override
public void propertyChange(final PropertyChangeEvent event) {
final String propertyName = event.getPropertyName();
if (propertyName.equals("filter")) {
final Condition filter = (Condition)event.getNewValue();
setFilter(filter);
} else if (event.getSource() == this.searchField) {
updateCondition();
}
}
@SuppressWarnings("rawtypes")
private void removeListeners(final JComponent component) {
if (component instanceof AbstractRecordQueryField) {
final AbstractRecordQueryField queryField = (AbstractRecordQueryField)component;
queryField.removePropertyChangeListener("selectedRecord", this);
} else if (component instanceof JXSearchField) {
final JXSearchField searchTextField = (JXSearchField)component;
searchTextField.removeActionListener(this);
} else if (component instanceof JComboBox) {
final JComboBox comboField = (JComboBox)component;
comboField.removeActionListener(this);
} else if (component instanceof DateField) {
final DateField dateField = (DateField)component;
dateField.removeActionListener(this);
}
if (component instanceof Field) {
final Field field = (Field)component;
final String fieldName = field.getFieldName();
Property.removeListener(field, fieldName, this);
}
}
@Override
public void removeUpdate(final DocumentEvent event) {
updateCondition();
}
public void setFilter(final Condition filter) {
Invoke.later(() -> {
try (
BaseCloseable settingFilter = this.settingFilter.closeable(true)) {
setSearchFilter(filter);
boolean simple = false;
if (Property.isEmpty(filter)) {
final Field searchField = (Field)this.searchField;
if (searchField != null) {
searchField.setFieldValue(null);
}
setSearchOperator("=");
simple = true;
} else if (filter instanceof Not) {
final Not not = (Not)filter;
final QueryValue condition = not.getValue();
if (condition instanceof IsNull) {
final IsNull isNull = (IsNull)condition;
final IsNotNull isNotNull = new IsNotNull(isNull.getValue());
setFilter(isNotNull);
return;
}
} else if (filter instanceof ILike) {
final ILike equal = (ILike)filter;
final QueryValue leftCondition = equal.getLeft();
final QueryValue rightCondition = equal.getRight();
if (leftCondition instanceof Column && rightCondition instanceof Value) {
final Column column = (Column)leftCondition;
final String searchFieldName = column.getName();
setSearchFieldName(searchFieldName);
if (setSearchOperator("Like")) {
final Value value = (Value)rightCondition;
final Object searchValue = value.getValue();
String searchText = DataTypes.toString(searchValue);
if (Property.hasValue(searchText)) {
setSearchField(this.searchTextField);
searchText = searchText.replaceAll("%", "");
final String previousSearchText = this.searchTextField.getText();
if (!DataType.equal(searchText, previousSearchText)) {
this.searchTextField.setFieldValue(searchText);
}
simple = true;
} else {
setSearchFilter(null);
}
}
}
} else if (filter instanceof BinaryCondition) {
final BinaryCondition condition = (BinaryCondition)filter;
final QueryValue leftCondition = condition.getLeft();
final QueryValue rightCondition = condition.getRight();
if (leftCondition instanceof Column && rightCondition instanceof Value) {
final Column column = (Column)leftCondition;
final String searchFieldName = column.getName();
setSearchFieldName(searchFieldName);
final String searchOperator = condition.getOperator();
if (setSearchOperator(searchOperator)) {
final Value value = (Value)rightCondition;
final Object searchValue = value.getValue();
final String searchText = DataTypes.toString(searchValue);
final Field searchField = (Field)this.searchField;
final Object oldValue = searchField.getFieldValue();
if (!searchText.equalsIgnoreCase(DataTypes.toString(oldValue))) {
searchField.setFieldValue(searchText);
}
simple = true;
}
}
} else if (filter instanceof RightUnaryCondition) {
final RightUnaryCondition condition = (RightUnaryCondition)filter;
final String operator = condition.getOperator();
if (filter instanceof IsNull || filter instanceof IsNotNull) {
final QueryValue leftValue = condition.getValue();
if (leftValue instanceof Column) {
final Column column = (Column)leftValue;
final String searchFieldName = column.getName();
setSearchFieldName(searchFieldName);
if (setSearchOperator(operator)) {
simple = true;
}
}
}
}
if (simple) {
this.whereLabel.setVisible(false);
if (this.nameField != null) {
this.nameField.setVisible(true);
}
if (this.operatorField != null) {
this.operatorField.setVisible(true);
}
this.searchFieldPanel.setVisible(true);
} else {
String filterText = filter.toString();
if (filterText.length() > 40) {
filterText = filterText.substring(0, 40) + "...";
}
this.whereLabel.setText(filterText);
this.whereLabel.setToolTipText(filterText);
this.whereLabel.setVisible(true);
if (this.nameField != null) {
this.nameField.setVisible(false);
}
this.operatorField.setVisible(false);
this.searchFieldPanel.setVisible(false);
}
}
});
}
private void setOperatorField(final ComboBox<String> field) {
if (field != this.operatorField) {
final String operator = getSearchOperator();
if (this.operatorField != null) {
this.operatorField.removeItemListener(this);
}
this.operatorFieldPanel.removeAll();
this.operatorField = field;
if (field != null) {
this.operatorField.setSelectedIndex(0);
if (operator != null) {
this.operatorField.setSelectedItem(operator);
}
this.operatorField.addItemListener(this);
this.operatorFieldPanel.add(field);
GroupLayouts.makeColumns(this.operatorFieldPanel, 1, false);
}
}
}
private void setSearchField(final JComponent searchField) {
this.searchFieldPanel.removeAll();
removeListeners(this.searchField);
this.searchField = searchField;
if (searchField == null) {
this.searchFieldPanel.setVisible(false);
} else {
this.searchFieldPanel.setVisible(true);
addListeners(searchField);
if (searchField instanceof Field) {
final Field field = (Field)searchField;
field.setFieldValue(this.lastValue);
}
final Dimension size = new Dimension(200, 22);
searchField.setMinimumSize(size);
searchField.setPreferredSize(size);
searchField.setMaximumSize(size);
this.searchFieldPanel.add(this.searchField);
GroupLayouts.makeColumns(this.searchFieldPanel, 1, false);
}
}
private void setSearchFieldName(final String searchFieldName) {
if (Property.hasValue(searchFieldName)
&& !DataType.equal(searchFieldName, this.previousSearchFieldName)
&& this.fieldNames.contains(searchFieldName)) {
this.lastValue = null;
this.previousSearchFieldName = searchFieldName;
final RecordDefinition recordDefinition = this.tableModel.getRecordDefinition();
this.field = recordDefinition.getField(searchFieldName);
final Class<?> fieldClass = this.field.getTypeClass();
if (!DataType.equal(searchFieldName, this.nameField.getSelectedItem())) {
this.nameField.setFieldValue(searchFieldName);
}
if (searchFieldName.equals(recordDefinition.getIdFieldName())) {
this.codeTable = null;
} else {
this.codeTable = this.recordDefinition.getCodeTableByFieldName(searchFieldName);
}
ComboBox<String> operatorField;
if (this.codeTable != null) {
operatorField = this.codeTableOperatorField;
} else if (Number.class.isAssignableFrom(fieldClass)) {
operatorField = this.numericOperatorField;
} else if (Date.class.isAssignableFrom(fieldClass)) {
operatorField = this.dateOperatorField;
} else {
operatorField = this.generalOperatorField;
}
setOperatorField(operatorField);
setSearchOperator("=");
if (this.settingFilter.isFalse()) {
setSearchFilter(null);
}
}
}
public void setSearchFilter(final Condition filter) {
if (this.tableModel != null) {
this.tableModel.setFilter(filter);
}
}
private boolean setSearchOperator(final String searchOperator) {
if (this.operatorField == null) {
return false;
} else {
final Object currentSearchOperator = this.operatorField.getSelectedItem();
if (!DataType.equal(searchOperator, currentSearchOperator)) {
this.operatorField.setSelectedItem(searchOperator);
}
if (this.operatorField.getSelectedIndex() < 0) {
return false;
} else {
JComponent searchField = null;
if ("Like".equals(searchOperator)) {
this.codeTable = null;
searchField = this.searchTextField;
} else if ("IS NULL".equals(searchOperator)) {
if (this.settingFilter.isFalse()) {
setFilter(Q.isNull(this.field));
}
this.codeTable = null;
} else if ("IS NOT NULL".equals(searchOperator)) {
if (this.settingFilter.isFalse()) {
setFilter(Q.isNotNull(this.field));
}
this.codeTable = null;
} else {
searchField = this.layer.newSearchField(this.field, this.codeTable);
}
setSearchField(searchField);
return true;
}
}
}
public void showAdvancedFilter() {
final Condition filter = getFilter();
final QueryWhereConditionField advancedFilter = new QueryWhereConditionField(this.layer, this,
filter);
advancedFilter.showDialog(this);
}
public void updateCondition() {
if (!(this.searchField instanceof Field) || ((Field)this.searchField).isFieldValid()) {
final Object searchValue = getSearchValue();
this.lastValue = searchValue;
Condition condition = null;
final String searchOperator = getSearchOperator();
if ("IS NULL".equalsIgnoreCase(searchOperator)) {
condition = Q.isNull(this.field);
} else if ("IS NOT NULL".equalsIgnoreCase(searchOperator)) {
condition = Q.isNotNull(this.field);
} else if (this.field != null) {
if (Property.hasValue(DataTypes.toString(searchValue))) {
if ("Like".equalsIgnoreCase(searchOperator)) {
final String searchText = DataTypes.toString(searchValue);
if (Property.hasValue(searchText)) {
condition = Q.iLike(this.field, "%" + searchText + "%");
}
} else {
Object value = null;
if (this.codeTable == null) {
value = this.layer.getValidSearchValue(this.field, searchValue);
} else {
value = this.codeTable.getIdentifier(searchValue);
}
if (value != null) {
condition = Q.binary(this.field, searchOperator, value);
}
}
}
}
setSearchFilter(condition);
}
}
}