package com.revolsys.swing.component; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dialog.ModalityType; import java.awt.FlowLayout; import java.awt.LayoutManager; import java.awt.Window; import java.beans.PropertyChangeListener; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.function.BiConsumer; import java.util.function.Consumer; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import com.revolsys.collection.map.Maps; import com.revolsys.datatype.DataType; import com.revolsys.io.BaseCloseable; import com.revolsys.logging.Logs; import com.revolsys.swing.SwingUtil; import com.revolsys.swing.action.RunnableAction; import com.revolsys.swing.field.Field; import com.revolsys.swing.layout.GroupLayouts; import com.revolsys.swing.parallel.Invoke; import com.revolsys.util.Pair; import com.revolsys.util.Property; import com.revolsys.value.ThreadBooleanValue; public class Form extends BasePanel { private static final long serialVersionUID = 1L; private final Map<String, Field> fieldByName = new HashMap<>(); private final ThreadBooleanValue settingFieldValue = new ThreadBooleanValue(false); private final PropertyChangeListener propertyChangeSetValue = Property .newListener(this::setFieldValue); private final Map<String, List<BiConsumer<String, Object>>> fieldValueListenersByFieldName = new HashMap<>(); private final Map<String, Object> fieldValueByName = new HashMap<>(); private String title; private boolean saved = false; public Form() { } public Form(final Component... components) { super(components); } public Form(final LayoutManager layout) { super(layout); } public Form(final LayoutManager layout, final Component... components) { super(layout, components); } public void addField(final Field field) { setField(field); add((JComponent)field); } protected void addFields(final Component component) { if (component instanceof Field) { final Field field = (Field)component; setField(field); } else if (component instanceof Container) { final Container container = (Container)component; for (final Component childComponent : container.getComponents()) { addFields(childComponent); } } } public boolean addFieldValueListener(final BiConsumer<String, Object> listener) { return addFieldValueListener(null, listener); } public boolean addFieldValueListener(final String fieldName, final BiConsumer<String, Object> listener) { synchronized (this.fieldValueListenersByFieldName) { final List<BiConsumer<String, Object>> listeners = Maps .getList(this.fieldValueListenersByFieldName, fieldName); if (listeners.contains(listener)) { return false; } else { return listeners.add(listener); } } } @SuppressWarnings("unchecked") public <V> boolean addFieldValueListener(final String fieldName, final Consumer<V> listener) { if (Property.hasValue(fieldName)) { return addFieldValueListener(fieldName, (name, value) -> { listener.accept((V)value); }); } else { throw new IllegalArgumentException("A field name must be specified"); } } @Override protected void addImpl(final Component comp, final Object constraints, final int index) { super.addImpl(comp, constraints, index); addFields(comp); } public void addLabelAndField(final Container container, final Field field) { if (field != null) { final String fieldName = field.getFieldName(); SwingUtil.addLabel(container, fieldName); setField(field); container.add(field.getComponent()); } } public void addLabelAndField(final Field field) { addLabelAndField(this, field); } public <F> F addLabelAndNewField(final String fieldName, final DataType dataType) { SwingUtil.addLabel(this, fieldName); return addNewField(fieldName, dataType); } @SuppressWarnings("unchecked") public <F> F addNewField(final String fieldName, final DataType dataType) { final Field field = newField(fieldName, dataType); addField(field); return (F)field; } public BasePanel addNewPanelTitledLabelledFields(final String title, final Field... fields) { final BasePanel panel = newPanelTitledLabelledFields(title, fields); add(panel); return panel; } @Override public void addNotify() { super.addNotify(); for (final Entry<String, Field> entry : this.fieldByName.entrySet()) { final String fieldName = entry.getKey(); final Field field = entry.getValue(); Property.addListener(field, fieldName, this.propertyChangeSetValue); } } public void cancel() { this.saved = false; } public void cancel(final JDialog dialog) { cancel(); SwingUtil.setVisible(dialog, false); } protected void fireFieldValueChanged(final String keyFieldName, final String fieldName, final Object fieldValue) { final List<BiConsumer<String, Object>> listeners = this.fieldValueListenersByFieldName .get(keyFieldName); if (listeners != null) { for (final BiConsumer<String, Object> listener : listeners) { try { listener.accept(fieldName, fieldValue); } catch (final Throwable e) { Logs.error(this, "Error calling listener " + fieldName + "=" + fieldValue, e); } } } } @SuppressWarnings("unchecked") public <F> F getField(final String filedName) { return (F)this.fieldByName.get(filedName); } public <V> V getFieldValue(final String fieldName) { final Field field = getField(fieldName); if (field == null) { return null; } else { return field.getFieldValue(); } } public Map<String, Object> getFieldValues() { final Map<String, Object> values = new TreeMap<>(); for (final Entry<String, Field> entry : this.fieldByName.entrySet()) { final String fieldName = entry.getKey(); final Field field = entry.getValue(); final Object fieldValue = field.getFieldValue(); values.put(fieldName, fieldValue); } return values; } public String getTitle() { return this.title; } public boolean isSettingFieldValue() { return this.settingFieldValue.isTrue(); } @SuppressWarnings("unchecked") public <F> F newField(final String fieldName, final DataType dataType) { return (F)SwingUtil.newField(dataType, fieldName, null); } public BasePanel newPanelTitledLabelledFields(final String title, final Field... fields) { final BasePanel panel = BasePanel.newPanelTitled(title); for (final Field field : fields) { final String fieldName = field.getFieldName(); final Component component = field.getComponent(); panel.addWithLabel(fieldName, component); } GroupLayouts.makeColumns(panel, 2, true, true); return panel; } protected void postSetFieldValues(final Map<String, Object> newValues) { } protected void postSetFieldValuesErrors( final Map<String, Pair<Object, Throwable>> fieldValueErrors) { for (final Entry<String, Pair<Object, Throwable>> entry : fieldValueErrors.entrySet()) { final String fieldName = entry.getKey(); final Pair<Object, Throwable> pair = entry.getValue(); final Object fieldValue = pair.getValue1(); final Throwable exception = pair.getValue2(); Logs.error(this, "Error setting field " + fieldName + "=" + fieldValue, exception); } } public boolean removeFieldValueListener(final BiConsumer<String, Object> listener) { return removeFieldValueListener(null, listener); } public boolean removeFieldValueListener(final String fieldName, final BiConsumer<String, Object> listener) { synchronized (this.fieldValueListenersByFieldName) { return Maps.removeFromCollection(this.fieldValueListenersByFieldName, fieldName, listener); } } @Override public void removeNotify() { super.addNotify(); for (final Entry<String, Field> entry : this.fieldByName.entrySet()) { final String fieldName = entry.getKey(); final Field field = entry.getValue(); Property.removeListener(field, fieldName, this.propertyChangeSetValue); } } public void save() { this.saved = true; } public void save(final JDialog dialog) { save(); dialog.setVisible(false); } public void setField(final Field field) { if (field != null) { Invoke.later(() -> { final String fieldName = field.getFieldName(); final Field oldField = this.fieldByName.put(fieldName, field); if (oldField != null) { Property.removeListener(oldField, fieldName, this.propertyChangeSetValue); } if (isDisplayable()) { Property.addListener(field, fieldName, this.propertyChangeSetValue); } }); } } public void setFieldValue(final String fieldName, final Object value) { final Map<String, Object> values = Collections.singletonMap(fieldName, value); setFieldValues(values); } public void setFieldValues(final Map<String, ? extends Object> values) { if (Property.hasValue(values)) { Invoke.later(() -> { final Map<String, Object> newValues = new HashMap<>(); final Map<String, Pair<Object, Throwable>> fieldValueErrors = new HashMap<>(); try ( BaseCloseable settingFieldValue = this.settingFieldValue.closeable(true)) { for (final Entry<String, ? extends Object> entry : values.entrySet()) { final String fieldName = entry.getKey(); Object fieldValue = entry.getValue(); final Field field = getField(fieldName); if (field != null) { try { final boolean valueSet = field.setFieldValue(fieldValue); fieldValue = field.getFieldValue(); if (!DataType.equal(this.fieldValueByName.put(fieldName, fieldValue), fieldValue) || valueSet) { newValues.put(fieldName, fieldValue); } } catch (final Throwable e) { fieldValueErrors.put(fieldName, new Pair<>(fieldValue, e)); } } } } for (final Entry<String, Object> entry : newValues.entrySet()) { final String fieldName = entry.getKey(); final Object fieldValue = entry.getValue(); fireFieldValueChanged(null, fieldName, fieldValue); fireFieldValueChanged(fieldName, fieldName, fieldValue); } if (!isSettingFieldValue()) { if (!fieldValueErrors.isEmpty()) { postSetFieldValuesErrors(fieldValueErrors); } if (!newValues.isEmpty()) { postSetFieldValues(newValues); } } }); } } public void setTitle(final String title) { this.title = title; } public boolean showDialog() { final Window window = SwingUtil.getActiveWindow(); return showDialog(window); } public boolean showDialog(final Component component) { Window window; if (component == null) { window = SwingUtil.getActiveWindow(); } else if (component instanceof Window) { window = (Window)component; } else { window = SwingUtilities.windowForComponent(component); } final JDialog dialog = new JDialog(window, this.title, ModalityType.APPLICATION_MODAL); dialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); dialog.setLayout(new BorderLayout()); dialog.add(this, BorderLayout.CENTER); final JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT)); buttons.add(RunnableAction.newButton("Cancel", () -> cancel(dialog))); buttons.add(RunnableAction.newButton("OK", () -> save(dialog))); dialog.add(buttons, BorderLayout.SOUTH); dialog.pack(); SwingUtil.autoAdjustPosition(dialog); dialog.setVisible(true); SwingUtil.dispose(dialog); return this.saved; } }