package com.revolsys.swing.map.form; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dialog.ModalityType; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.ComboBoxEditor; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.TransferHandler; import javax.swing.WindowConstants; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.undo.UndoableEdit; import org.jdesktop.swingx.VerticalLayout; import com.revolsys.awt.WebColors; import com.revolsys.beans.PropertyChangeSupportProxy; import com.revolsys.collection.map.Maps; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.model.Geometry; import com.revolsys.identifier.Identifier; import com.revolsys.io.BaseCloseable; import com.revolsys.logging.Logs; import com.revolsys.record.Record; import com.revolsys.record.RecordState; import com.revolsys.record.code.CodeTable; import com.revolsys.record.property.DirectionalFields; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.record.schema.RecordStore; import com.revolsys.swing.Panels; import com.revolsys.swing.SwingUtil; import com.revolsys.swing.action.RunnableAction; import com.revolsys.swing.action.enablecheck.EnableCheck; import com.revolsys.swing.action.enablecheck.ObjectPropertyEnableCheck; import com.revolsys.swing.component.BaseDialog; import com.revolsys.swing.dnd.transferhandler.RecordLayerFormTransferHandler; import com.revolsys.swing.field.ComboBox; import com.revolsys.swing.field.Field; import com.revolsys.swing.field.NumberTextField; import com.revolsys.swing.field.ObjectLabelField; import com.revolsys.swing.layout.GroupLayouts; import com.revolsys.swing.listener.WeakFocusListener; import com.revolsys.swing.map.MapPanel; import com.revolsys.swing.map.ProjectFrame; import com.revolsys.swing.map.layer.record.AbstractRecordLayer; import com.revolsys.swing.map.layer.record.LayerRecord; import com.revolsys.swing.map.layer.record.component.RecordLayerFields; import com.revolsys.swing.map.layer.record.table.model.LayerRecordTableModel; import com.revolsys.swing.map.layer.record.table.model.RecordLayerTableModel; import com.revolsys.swing.menu.MenuFactory; import com.revolsys.swing.parallel.Invoke; import com.revolsys.swing.table.TablePanel; import com.revolsys.swing.table.record.editor.RecordTableCellEditor; import com.revolsys.swing.toolbar.ToolBar; import com.revolsys.swing.undo.ReverseRecordFieldsUndo; import com.revolsys.swing.undo.ReverseRecordGeometryUndo; import com.revolsys.swing.undo.ReverseRecordUndo; import com.revolsys.swing.undo.UndoManager; import com.revolsys.util.Property; import com.revolsys.util.Strings; import com.revolsys.value.ThreadBooleanValue; public class LayerRecordForm extends JPanel implements PropertyChangeListener, CellEditorListener, FocusListener, PropertyChangeSupportProxy, WindowListener { public static final String FLIP_FIELDS_ICON = "flip_fields"; public static final String FLIP_FIELDS_NAME = "Flip Fields Orientation"; public static final String FLIP_LINE_ORIENTATION_ICON = "flip_line_orientation"; public static final String FLIP_LINE_ORIENTATION_NAME = "Flip Line Orientation (Visually Flips Fields)"; public static final String FLIP_RECORD_ICON = "flip_orientation"; public static final String FLIP_RECORD_NAME = "Flip Record Orientation"; private static final long serialVersionUID = 1L; public static String getTitle(final LayerRecord record) { final AbstractRecordLayer layer = record.getLayer(); final Identifier id = record.getIdentifier(); String title; final String layerName = layer.getName(); if (record.getState() == RecordState.NEW) { title = "Add New " + layerName; } else if (layer.isCanEditRecords()) { title = "Edit " + layerName; if (id != null) { title += " #" + id; } } else { title = "View " + layerName; if (id != null) { title += " #" + id; } } return title; } private JButton addOkButton = RunnableAction.newButton("OK", this::actionAddOk); private LayerRecord addRecord; private LayerRecordTableModel fieldsTableModel; private boolean allowAddWithErrors = false; private boolean cancelled = false; private boolean editable = true; private final Map<String, List<String>> fieldErrors = new HashMap<>(); private final Map<String, String> fieldInValidMessage = new HashMap<>(); private final Map<String, Field> fields = new LinkedHashMap<>(); private final ThreadLocal<Set<String>> fieldsToValidate = new ThreadLocal<>(); private final Map<String, Integer> fieldTabIndex = new HashMap<>(); private final Map<Field, String> fieldToNameMap = new HashMap<>(); private final ThreadBooleanValue fieldValidationEnabled = new ThreadBooleanValue(true); private final Map<String, Object> fieldValues = new HashMap<>(); private final Map<String, List<String>> fieldWarnings = new HashMap<>(); private String focussedFieldName; private GeometryCoordinatesPanel geometryCoordinatesPanel; private final Set<String> invalidFieldNames = new HashSet<>(); private String lastFocussedFieldName; private AbstractRecordLayer layer; private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); private Set<String> readOnlyFieldNames = new HashSet<>(); private LayerRecord record; private RecordDefinition recordDefinition; private RecordStore recordStore; private Set<String> requiredFieldNames = new HashSet<>(); private final Map<Integer, Set<String>> tabInvalidFieldMap = new TreeMap<>(); private JTabbedPane tabs = new JTabbedPane(); private ToolBar toolBar; private UndoManager undoManager = new RecordLayerFormUndoManager(this); private boolean settingRecord = false; public LayerRecordForm(final AbstractRecordLayer layer) { ProjectFrame.addSaveActions(this, layer.getProject()); setLayout(new BorderLayout()); setName(layer.getName()); this.layer = layer; final RecordDefinition recordDefinition = layer.getRecordDefinition(); setRecordDefinition(recordDefinition); addToolBar(layer); final ActionMap map = getActionMap(); map.put("copy", TransferHandler.getCopyAction()); map.put("paste", TransferHandler.getPasteAction()); final RecordLayerFormTransferHandler transferHandler = new RecordLayerFormTransferHandler(this); setTransferHandler(transferHandler); setFont(SwingUtil.FONT); addTabFields(); final boolean editable = layer.isEditable(); setEditable(editable); getFieldsTableModel().setEditable(isEditable()); if (recordDefinition.getGeometryFieldName() != null) { addTabGeometry(); } Property.addListener(layer, this); this.undoManager.setLimit(100); this.undoManager.addKeyMap(this); } public LayerRecordForm(final AbstractRecordLayer layer, final LayerRecord record) { this(layer); setRecord(record); } protected void actionAddCancel() { final AbstractRecordLayer layer = getLayer(); final LayerRecord record = getRecord(); setRecord(null); layer.deleteRecordAndSaveChanges(record); this.cancelled = true; closeWindow(); } protected void actionAddOk() { final AbstractRecordLayer layer = getLayer(); final LayerRecord record = getRecord(); layer.saveChanges(record); layer.setSelectedRecords(record); layer.showRecordsTable(RecordLayerTableModel.MODE_RECORDS_SELECTED); closeWindow(); } protected ObjectLabelField addCodeTableLabelField(final String fieldName) { final RecordStore recordStore = getRecordStore(); final CodeTable codeTable = recordStore.getCodeTableByFieldName(fieldName); final ObjectLabelField field = new ObjectLabelField(fieldName, codeTable); field.setFont(SwingUtil.FONT); addField(fieldName, field); return field; } protected void addDoubleField(final String fieldName, final int length, final int scale, final Double minimumValie, final Double maximumValue) { final DataType dataType = DataTypes.DOUBLE; final NumberTextField field = new NumberTextField(fieldName, dataType, length, scale, minimumValie, maximumValue); addField(fieldName, field); } public void addField(final Container container, final Field field) { final JComponent component = (JComponent)field; addField(container, component); } public void addField(final Container container, JComponent field) { if (field instanceof JTextArea) { final JTextArea textArea = (JTextArea)field; field = new JScrollPane(textArea); } container.add(field); } @SuppressWarnings("unchecked") public <T> T addField(final Container container, final String fieldName) { final Field field = getField(fieldName); addField(container, field); return (T)field; } public void addField(final Field field) { final String fieldName = field.getFieldName(); addField(fieldName, field); } public Field addField(final String fieldName, final Field field) { Property.addListener(field, fieldName, this); field.setUndoManager(this.undoManager); if (field instanceof ComboBox) { final ComboBox<?> comboBox = (ComboBox<?>)field; final ComboBoxEditor editor = comboBox.getEditor(); final Component editorComponent = editor.getEditorComponent(); editorComponent.addFocusListener(new WeakFocusListener(this)); } else { ((JComponent)field).addFocusListener(new WeakFocusListener(this)); } this.fields.put(fieldName, field); this.fieldToNameMap.put(field, fieldName); return field; } protected boolean addFieldError(final String fieldName, final String message) { Maps.addToList(this.fieldErrors, fieldName, message); return false; } public void addFields(final Collection<? extends Field> fields) { for (final Field field : fields) { addField(field); } } protected boolean addFieldWarning(final String fieldName, final String message) { Maps.addToList(this.fieldWarnings, fieldName, message); return false; } protected void addLabel(final Container container, final String fieldName) { final JLabel label = getLabel(fieldName); container.add(label); } public void addLabelledField(final Container container, final Field field) { final String fieldName = field.getFieldName(); addLabel(container, fieldName); addField(container, field); } @SuppressWarnings("unchecked") public <T> T addLabelledField(final Container container, final String fieldName) { if (this.recordDefinition.hasField(fieldName)) { final Field field = getField(fieldName); if (field == null) { Logs.error(this, "Cannot find field " + this.recordDefinition.getPath() + " " + fieldName); } else { addLabelledField(container, field); } return (T)field; } else { return null; } } protected void addNumberField(final String fieldName, final DataType dataType, final int length, final Number minimumValue, final Number maximumValue) { final NumberTextField field = new NumberTextField(fieldName, dataType, length, 0, minimumValue, maximumValue); addField(fieldName, field); } protected void addPanel(final JPanel container, final String title, final List<String> fieldNames) { final JPanel panel = newPanel(container, title); for (final String fieldName : fieldNames) { addLabelledField(panel, fieldName); } GroupLayouts.makeColumns(panel, 2, true); } public void addReadOnlyFieldNames(final Collection<String> readOnlyFieldNames) { this.readOnlyFieldNames.addAll(readOnlyFieldNames); for (final Entry<String, Field> entry : this.fields.entrySet()) { final String name = entry.getKey(); final Field field = entry.getValue(); if (this.readOnlyFieldNames.contains(name)) { field.setEditable(false); } else { field.setEditable(true); } } } public void addReadOnlyFieldNames(final String... readOnlyFieldNames) { addReadOnlyFieldNames(Arrays.asList(readOnlyFieldNames)); } public void addRequiredFieldNames(final Collection<String> requiredFieldNames) { this.requiredFieldNames.addAll(requiredFieldNames); } public void addRequiredFieldNames(final String... requiredFieldNames) { addRequiredFieldNames(Arrays.asList(requiredFieldNames)); } protected JPanel addScrollPaneTab(final int index, final String title) { final JPanel panel = new JPanel(new VerticalLayout()); panel.setOpaque(false); addScrollPaneTab(index, title, panel); return panel; } public JScrollPane addScrollPaneTab(final int index, final String name, final Component component) { boolean init = false; final Container parent = this.tabs.getParent(); if (parent != this) { add(this.tabs, BorderLayout.CENTER); init = true; } final JScrollPane scrollPane = new JScrollPane(component); scrollPane.getViewport().setOpaque(false); scrollPane.setOpaque(false); scrollPane.setBorder(null); this.tabs.insertTab(name, null, scrollPane, null, index); if (init) { this.tabs.setSelectedIndex(0); } final JLabel label = new JLabel(name); this.tabs.setTabComponentAt(index, label); return scrollPane; } protected JPanel addScrollPaneTab(final String title) { final JPanel panel = new JPanel(new VerticalLayout()); panel.setOpaque(false); addScrollPaneTab(title, panel); return panel; } public JScrollPane addScrollPaneTab(final String name, final Component component) { return addScrollPaneTab(this.tabs.getTabCount(), name, component); } public void addTab(final int index, final String name, final Component component) { boolean init = false; final Container parent = this.tabs.getParent(); if (parent != this) { add(this.tabs, BorderLayout.CENTER); init = true; } this.tabs.insertTab(name, null, component, null, index); if (init) { this.tabs.setSelectedIndex(0); } final JLabel label = new JLabel(name); this.tabs.setTabComponentAt(index, label); } public void addTab(final String name, final Component component) { addTab(this.tabs.getTabCount(), name, component); } protected void addTabFields() { final TablePanel tablePanel = LayerRecordTableModel.newTablePanel(this); this.fieldsTableModel = tablePanel.getTableModel(); addTab("Fields", tablePanel); } protected void addTabGeometry() { final String geometryFieldName = this.recordDefinition.getGeometryFieldName(); if (this.geometryCoordinatesPanel == null && geometryFieldName != null) { this.geometryCoordinatesPanel = new GeometryCoordinatesPanel(this, geometryFieldName); addField(geometryFieldName, this.geometryCoordinatesPanel); final JPanel panel = Panels.titledTransparent(new GridLayout(1, 1), "Vertices"); panel.add(this.geometryCoordinatesPanel); addScrollPaneTab("Geometry", panel); } } protected ToolBar addToolBar(final AbstractRecordLayer layer) { this.toolBar = new ToolBar(); add(this.toolBar, BorderLayout.NORTH); final RecordDefinition recordDefinition = getRecordDefinition(); final FieldDefinition geometryField = recordDefinition.getGeometryField(); final boolean hasGeometry = geometryField != null; final EnableCheck editable = new ObjectPropertyEnableCheck(this, "editable"); if (layer != null) { final MenuFactory menuFactory = MenuFactory.findMenu(layer); if (menuFactory != null) { this.toolBar.addButtonTitleIcon("menu", "Layer Menu", "menu", () -> menuFactory.showMenu(layer, this, 10, 10)); } } final EnableCheck deletableEnableCheck = editable .and(new ObjectPropertyEnableCheck(this, "deletable")); this.toolBar.addButton("record", "Delete Record", "table_row_delete", deletableEnableCheck, this::deleteRecord); // Cut, Copy Paste this.toolBar.addButton("dnd", "Copy Record", "page_copy", (EnableCheck)null, this::dataTransferCopy); if (hasGeometry) { this.toolBar.addButton("dnd", "Copy Geometry", "geometry_copy", (EnableCheck)null, this::copyGeometry); } this.toolBar.addButton("dnd", "Paste Record", "paste_plain", editable, this::dataTransferPaste); if (hasGeometry) { this.toolBar.addButton("dnd", "Paste Geometry", "geometry_paste", editable, this::pasteGeometry); } final EnableCheck canUndo = new ObjectPropertyEnableCheck(this.undoManager, "canUndo"); final EnableCheck canRedo = new ObjectPropertyEnableCheck(this.undoManager, "canRedo"); final EnableCheck modifiedOrDeleted = new ObjectPropertyEnableCheck(this, "modifiedOrDeleted"); this.toolBar.addButton("changes", "Revert Record", "arrow_revert", modifiedOrDeleted, this::revertChanges); this.toolBar.addButton("changes", "Revert Empty Fields", "field_empty_revert", new ObjectPropertyEnableCheck(this, "hasModifiedEmptyFields"), this::revertEmptyFields); this.toolBar.addButton("changes", "Undo", "arrow_undo", canUndo, this.undoManager::undo); this.toolBar.addButton("changes", "Redo", "arrow_redo", canRedo, this.undoManager::redo); // Zoom if (hasGeometry) { this.toolBar.addButtonTitleIcon("zoom", "Zoom to Record", "magnifier_zoom_selected", () -> { final LayerRecord record = getRecord(); layer.zoomToRecord(record); }); this.toolBar.addButtonTitleIcon("zoom", "Pan to Record", "pan_selected", () -> { final LayerRecord record = getRecord(); final MapPanel mapPanel = layer.getMapPanel(); if (mapPanel != null) { mapPanel.panToRecord(record); } }); } // Geometry manipulation if (hasGeometry) { final DataType geometryDataType = geometryField.getDataType(); if (geometryDataType == DataTypes.LINE_STRING || geometryDataType == DataTypes.MULTI_LINE_STRING) { if (DirectionalFields.getProperty(recordDefinition).hasDirectionalFields()) { this.toolBar.addButton("geometry", FLIP_RECORD_NAME, FLIP_RECORD_ICON, editable, this::flipRecordOrientation); this.toolBar.addButton("geometry", FLIP_LINE_ORIENTATION_NAME, FLIP_LINE_ORIENTATION_ICON, editable, this::flipLineOrientation); this.toolBar.addButton("geometry", FLIP_FIELDS_NAME, FLIP_FIELDS_ICON, editable, this::flipFields); } else { this.toolBar.addButton("geometry", "Flip Line Orientation", "flip_line", editable, this::flipLineOrientation); } } } return this.toolBar; } public void addUndo(final UndoableEdit edit) { final boolean validationEnabled = isFieldValidationEnabled(); try ( final BaseCloseable c = setFieldValidationEnabled(false)) { this.undoManager.addEdit(edit); if (validationEnabled) { validateFields(this.fieldsToValidate.get()); } } } public boolean canPasteRecordGeometry() { final AbstractRecordLayer layer = getLayer(); if (layer == null) { return false; } else { final LayerRecord record = getRecord(); return layer.canPasteRecordGeometry(record); } } public void clearTabColor(final int index) { setTabColor(index, null); } public void closeWindow() { final Window window = SwingUtilities.windowForComponent(this); SwingUtil.setVisible(window, false); } public void copyGeometry() { final LayerRecord record = getRecord(); final AbstractRecordLayer layer = getLayer(); if (layer != null) { if (record != null) { layer.copyRecordGeometry(record); } } } public void dataTransferCopy() { invokeAction("copy"); } public void dataTransferPaste() { invokeAction("paste"); } public void deleteRecord() { final LayerRecord record = getRecord(); if (record != null) { final AbstractRecordLayer layer = getLayer(); layer.deleteRecord(record); } } public void destroy() { this.addOkButton = null; this.fieldsTableModel = null; this.recordStore = null; this.fieldInValidMessage.clear(); for (final Field field : this.fields.values()) { Property.removeAllListeners(field); } this.fields.clear(); this.fieldTabIndex.clear(); this.fieldToNameMap.clear(); this.invalidFieldNames.clear(); this.geometryCoordinatesPanel = null; this.recordDefinition = null; this.record = null; this.propertyChangeSupport = null; this.readOnlyFieldNames.clear(); this.tabInvalidFieldMap.clear(); this.tabs = null; this.toolBar = null; this.undoManager = null; final Container parent = getParent(); if (parent != null) { parent.remove(this); } final AbstractRecordLayer layer = getLayer(); if (layer != null) { this.layer = null; if (this.fieldsTableModel != null) { Property.removeListener(layer, this.fieldsTableModel); this.fieldsTableModel = null; } Property.removeListener(layer, this); } final Window window = SwingUtil.getWindowAncestor(this); if (window != null) { window.removeWindowListener(this); } removeAll(); } @Override public void editingCanceled(final ChangeEvent e) { } @Override public void editingStopped(final ChangeEvent e) { final RecordTableCellEditor editor = (RecordTableCellEditor)e.getSource(); final String name = editor.getFieldName(); final Object value = editor.getCellEditorValue(); setFieldValue(name, value, true); } private void fireButtonPropertyChanges() { if (this.propertyChangeSupport != null) { final boolean modifiedOrDeleted = isModifiedOrDeleted(); this.propertyChangeSupport.firePropertyChange("modifiedOrDeleted", !modifiedOrDeleted, modifiedOrDeleted); final boolean deletable = isDeletable(); this.propertyChangeSupport.firePropertyChange("deletable", !deletable, deletable); final boolean hasModifiedEmptyFields = isHasModifiedEmptyFields(); this.propertyChangeSupport.firePropertyChange("hasModifiedEmptyFields", !hasModifiedEmptyFields, hasModifiedEmptyFields); } } @Override public void firePropertyChange(final String propertyName, final Object oldValue, final Object newValue) { super.firePropertyChange(propertyName, oldValue, newValue); } public void flipFields() { addUndo(new ReverseRecordFieldsUndo(this.record)); } public void flipLineOrientation() { addUndo(new ReverseRecordGeometryUndo(this.record)); } public void flipRecordOrientation() { addUndo(new ReverseRecordUndo(this.record)); } @Override public void focusGained(final FocusEvent e) { Component component = e.getComponent(); while (component != null) { if (component instanceof Field) { final Field field = (Field)component; this.focussedFieldName = field.getFieldName(); return; } else { component = component.getParent(); } } } @Override public void focusLost(final FocusEvent e) { Component component = e.getComponent(); while (component != null) { if (component instanceof Field) { final Field field = (Field)component; this.lastFocussedFieldName = field.getFieldName(); return; } else { component = component.getParent(); } } } public LayerRecord getAddRecord() { return this.addRecord; } public String getCodeValue(final String fieldName, final Object value) { final CodeTable codeTable = this.recordDefinition.getCodeTableByFieldName(fieldName); String string; if (value == null) { return "-"; } else if (codeTable == null) { string = DataTypes.toString(value); } else { final List<Object> values = codeTable.getValues(value); if (values == null || values.isEmpty()) { string = "-"; } else { string = Strings.toString(values); } } if (!Property.hasValue(string)) { string = "-"; } return string; } public Color getErrorForegroundColor() { return WebColors.Red; } @SuppressWarnings("unchecked") protected <T extends Field> T getField(final String fieldName) { synchronized (this.fields) { Field field = this.fields.get(fieldName); if (field == null) { final boolean editable = !this.readOnlyFieldNames.contains(fieldName); try { field = RecordLayerFields.newFormField(this.layer, fieldName, editable); addField(fieldName, field); } catch (final IllegalArgumentException e) { } } if (field != null && !isEditable()) { field.setEditable(false); } return (T)field; } } public Map<String, List<String>> getFieldErrors() { return this.fieldErrors; } public String getFieldName(Component field) { String fieldName = null; do { fieldName = this.fieldToNameMap.get(field); field = field.getParent(); } while (fieldName == null && field != null); return fieldName; } public Set<String> getFieldNames() { return this.fields.keySet(); } public Collection<Field> getFields() { return this.fields.values(); } public LayerRecordTableModel getFieldsTableModel() { return this.fieldsTableModel; } public Set<String> getFieldsToValidate() { return this.fieldsToValidate.get(); } protected Map<String, Integer> getFieldTabIndex() { return this.fieldTabIndex; } @SuppressWarnings("unchecked") public <T> T getFieldValue(final String name) { final Object value = this.fieldValues.get(name); final CodeTable codeTable = this.recordDefinition.getCodeTableByFieldName(name); if (codeTable == null) { if (value != null && name.endsWith("_IND")) { if ("Y".equals(value) || Boolean.TRUE.equals(value)) { return (T)"Y"; } else { return (T)"N"; } } else { return (T)value; } } else { final Identifier id = codeTable.getIdentifier(value); return (T)id; } } public Map<String, List<String>> getFieldWarnings() { return this.fieldWarnings; } public GeometryCoordinatesPanel getGeometryCoordinatesPanel() { return this.geometryCoordinatesPanel; } public String getGeometryFieldName() { return getRecordDefinition().getGeometryFieldName(); } protected JLabel getLabel(final String fieldName) { final AbstractRecordLayer layer = getLayer(); String title = layer.getFieldTitle(fieldName); title = title.replaceAll(" Code$", ""); title = title.replaceAll(" Ind$", ""); final JLabel label = new JLabel(title); label.setFont(SwingUtil.BOLD_FONT); label.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5)); return label; } public String getLastFocussedFieldName() { return this.lastFocussedFieldName; } public AbstractRecordLayer getLayer() { return this.layer; } public <T> T getOriginalValue(final String fieldName) { final LayerRecord record = getRecord(); return record.getOriginalValue(fieldName); } @Override public PropertyChangeSupport getPropertyChangeSupport() { return this.propertyChangeSupport; } public Set<String> getReadOnlyFieldNames() { return this.readOnlyFieldNames; } public LayerRecord getRecord() { return this.record; } public RecordDefinition getRecordDefinition() { return this.recordDefinition; } public RecordStore getRecordStore() { if (this.recordStore == null) { if (this.recordDefinition == null) { return null; } else { return this.recordDefinition.getRecordStore(); } } else { return this.recordStore; } } public Set<String> getRequiredFieldNames() { return this.requiredFieldNames; } protected int getTabIndex(final String fieldName) { Integer index = this.fieldTabIndex.get(fieldName); if (index == null) { final JComponent field = (JComponent)getField(fieldName); if (field == null) { return -1; } else { Component panel = field; Component component = field.getParent(); while (component != this.tabs && component != null) { panel = component; component = component.getParent(); } index = this.tabs.indexOfComponent(panel); this.fieldTabIndex.put(fieldName, index); } } return index; } public JTabbedPane getTabs() { return this.tabs; } public ToolBar getToolBar() { return this.toolBar; } public UndoManager getUndoManager() { return this.undoManager; } @SuppressWarnings("unchecked") public <T> T getValue(final String name) { return (T)this.record.getValue(name); } public Map<String, Object> getValues() { final Map<String, Object> values = new LinkedHashMap<>(); if (this.record != null) { values.putAll(this.record); } return values; } public boolean hasErrors() { return !this.fieldErrors.isEmpty(); } public boolean hasFieldValue(final String fieldName) { final Field field = getField(fieldName); if (field == null) { return false; } else { final Object value = field.getFieldValue(); return Property.hasValue(value); } } public boolean hasOriginalValue(final String name) { return getRecordDefinition().hasField(name); } public boolean hasWarnings() { return !this.fieldWarnings.isEmpty(); } protected void invokeAction(final String actionName) { final Action action = getActionMap().get(actionName); if (action != null) { final ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null); action.actionPerformed(event); } } public boolean isAllowAddWithErrors() { return this.allowAddWithErrors; } public boolean isDeletable() { final LayerRecord record = getRecord(); if (record == null) { return false; } else { return record.isDeletable(); } } public boolean isEditable() { return this.editable; } public boolean isEditable(final String fieldName) { if (isEditable()) { if (!this.readOnlyFieldNames.contains(fieldName)) { return true; } } return false; } public boolean isFieldsValid() { return this.invalidFieldNames.isEmpty(); } public boolean isFieldValid(final String fieldName) { return !this.invalidFieldNames.contains(fieldName); } protected boolean isFieldValidationEnabled() { final boolean enabled = this.fieldValidationEnabled.isTrue(); return enabled; } public boolean isHasModifiedEmptyFields() { final LayerRecord record = getRecord(); if (record == null) { return false; } else { return record.isHasModifiedEmptyFields(); } } public boolean isModifiedOrDeleted() { final LayerRecord record = getRecord(); if (record == null) { return false; } else { return record.isDeleted() || record.isModified(); } } public boolean isNewRecord(final LayerRecord record) { return record.getState() == RecordState.NEW; } public boolean isReadOnly(final String fieldName) { return getReadOnlyFieldNames().contains(fieldName); } public boolean isSame(final Object object) { final LayerRecord record = getRecord(); if (record != null) { if (object instanceof Record) { final Record otherRecord = (Record)object; if (record.isSame(otherRecord)) { return true; } } } return false; } protected boolean isTabValid(final int tabIndex) { return this.tabInvalidFieldMap.get(tabIndex) == null; } protected JPanel newPanel(final JPanel container, final String title) { final JPanel panel = Panels.titledTransparentBorderLayout(title); container.add(panel); return panel; } public void pasteGeometry() { final LayerRecord record = getRecord(); final AbstractRecordLayer layer = getLayer(); if (layer != null) { if (record != null) { layer.pasteRecordGeometry(record); } } } public void pasteValues(final Map<String, Object> map) { final AbstractRecordLayer layer = getLayer(); if (layer != null) { final Map<String, Object> newValues = new LinkedHashMap<>(map); final Collection<String> ignorePasteFieldNames = layer.getIgnorePasteFieldNames(); final Set<String> keySet = newValues.keySet(); if (ignorePasteFieldNames != null) { keySet.removeAll(ignorePasteFieldNames); } keySet.removeAll(getReadOnlyFieldNames()); final Map<String, Object> values = getValues(); values.putAll(newValues); setValues(values); } } protected void postValidate() { } @Override public void propertyChange(final PropertyChangeEvent event) { final String propertyName = event.getPropertyName(); final AbstractRecordLayer layer = getLayer(); if (layer != null) { final LayerRecord record = getRecord(); if (record != null) { final RecordState state = record.getState(); if (!state.equals(RecordState.DELETED)) { final Object source = event.getSource(); if (this.geometryCoordinatesPanel != null && source == this.geometryCoordinatesPanel.getTable().getModel()) { if (propertyName.equals("geometry")) { record.setGeometryValue((Geometry)event.getNewValue()); } } else if (source == layer) { if (AbstractRecordLayer.RECORDS_CHANGED.equals(propertyName)) { setRecord(record); } else if (AbstractRecordLayer.RECORDS_DELETED.equals(propertyName)) { @SuppressWarnings("unchecked") final List<Record> deletedRecords = (List<Record>)event.getNewValue(); if (layer.isDeleted(record) || record.contains(deletedRecords)) { final Window window = SwingUtilities.getWindowAncestor(this); SwingUtil.setVisible(window, false); setRecord(record); return; } } else if (AbstractRecordLayer.RECORD_UPDATED.equals(propertyName)) { if (record.isDeleted()) { final Window window = SwingUtilities.getWindowAncestor(this); SwingUtil.setVisible(window, false); setRecord(record); } } else if ("editable".equals(propertyName)) { setEditable(layer.isEditable()); } } else if (source instanceof Field) { final Field field = (Field)source; final String fieldName = field.getFieldName(); final Object fieldValue = field.getFieldValue(); final Object recordValue = this.record.getValue(fieldName); if (!DataType.equal(recordValue, fieldValue)) { boolean equal = false; if (fieldValue instanceof String) { final String string = (String)fieldValue; if (!Property.hasValue(string) && recordValue == null) { equal = true; } } if (!equal && layer.isEditable() && (state == RecordState.NEW && layer.isCanAddRecords() || layer.isCanEditRecords())) { record.setValueByPath(fieldName, fieldValue); } } } else { if (isSame(source)) { if (getRecordDefinition().getIdFieldNames().contains(propertyName)) { Invoke.later(() -> { final String title = getTitle(record); final BaseDialog dialog = (BaseDialog)SwingUtil.getWindowAncestor(this); if (dialog != null) { dialog.setTitle(title); } }); } if (record.isDeleted()) { final Window window = SwingUtilities.getWindowAncestor(this); SwingUtil.setVisible(window, false); } else if (Record.EVENT_RECORD_CHANGED.equals(propertyName)) { setValues(record); return; } final Object value = event.getNewValue(); final RecordDefinition recordDefinition = getRecordDefinition(); if ("qaMessagesUpdated".equals(propertyName)) { updateErrors(); } else if (recordDefinition.hasField(propertyName)) { setFieldValue(propertyName, value, isFieldValidationEnabled()); } fireButtonPropertyChanges(); repaint(); } } } } } } public void revertChanges() { final LayerRecord record = getRecord(); if (record != null) { record.revertChanges(); setValues(record); } } public void revertEmptyFields() { final LayerRecord record = getRecord(); if (record != null) { record.revertEmptyFields(); } } public void setAddOkButtonEnabled(final boolean enabled) { if (this.addOkButton != null) { this.addOkButton.setEnabled(enabled); } } public void setAddRecord(final LayerRecord addRecord) { this.addRecord = addRecord; } public void setAllowAddWithErrors(final boolean allowAddWithErrors) { this.allowAddWithErrors = allowAddWithErrors; } public void setEditable(final boolean editable) { final boolean oldValue = this.editable; this.editable = editable; for (final String fieldName : getFieldNames()) { if (!getReadOnlyFieldNames().contains(fieldName)) { final Field field = getField(fieldName); field.setEditable(editable); } } this.propertyChangeSupport.firePropertyChange("editable", oldValue, editable); } public void setFieldFocussed(final String fieldName) { if (fieldName != null) { final int tabIndex = getTabIndex(fieldName); if (tabIndex >= 0) { this.tabs.setSelectedIndex(tabIndex); } final JComponent field = (JComponent)getField(fieldName); if (field != null) { field.requestFocusInWindow(); } } } public final void setFieldInvalid(final String fieldName, final String message) { Invoke.later(() -> setFieldInvalidDo(fieldName, message)); } protected void setFieldInvalidDo(final String fieldName, String message) { final String oldValue = this.fieldInValidMessage.get(fieldName); if (message == null) { message = "Invalid value"; } if (!DataType.equal(message, oldValue)) { this.fieldInValidMessage.put(fieldName, message); final Field field = getField(fieldName); field.setFieldInvalid(message, WebColors.Red, WebColors.Pink); this.invalidFieldNames.add(fieldName); final int tabIndex = getTabIndex(fieldName); Maps.addToSet(this.tabInvalidFieldMap, tabIndex, fieldName); updateTabValid(tabIndex); updateInvalidFields(true); } } public void setFieldInvalidToolTip(final String fieldName, final JComponent field) { final String message = this.fieldInValidMessage.get(fieldName); if (Property.hasValue(message)) { field.setToolTipText(message); } } public final void setFieldValid(final String fieldName) { Invoke.later(() -> setFieldValidDo(fieldName)); } protected BaseCloseable setFieldValidationEnabled(final boolean fieldValidationEnabled) { final boolean oldValue = isFieldValidationEnabled(); if (fieldValidationEnabled) { this.fieldsToValidate.remove(); } else if (oldValue) { this.fieldsToValidate.set(new TreeSet<>()); } return this.fieldValidationEnabled.closeable(fieldValidationEnabled); } protected void setFieldValidDo(final String fieldName) { this.fieldErrors.remove(fieldName); this.fieldWarnings.remove(fieldName); final boolean valid = isFieldValid(fieldName); final Field field = getField(fieldName); field.setFieldValid(); if (this.record.isModified(fieldName)) { final Object originalValue = this.record.getOriginalValue(fieldName); String originalString; if (originalValue == null) { originalString = "-"; } else { originalString = DataTypes.toString(originalValue); } field.setFieldToolTip(originalString); field.setFieldBackgroundColor(new Color(0, 255, 0, 31)); } else { field.setFieldToolTip(""); } if (!valid) { this.invalidFieldNames.remove(fieldName); this.fieldInValidMessage.remove(fieldName); final int tabIndex = getTabIndex(fieldName); Maps.removeFromSet(this.tabInvalidFieldMap, tabIndex, fieldName); updateTabValid(tabIndex); updateInvalidFields(true); } } public void setFieldValue(final String fieldName, Object value, final boolean validate) { final Object oldValue = getFieldValue(fieldName); final RecordDefinition recordDefinition = getRecordDefinition(); if (recordDefinition != null) { try { final FieldDefinition field = recordDefinition.getField(fieldName); if (field != null) { value = field.toFieldValue(value); } } catch (final Throwable e) { } } this.fieldValues.put(fieldName, value); final JComponent field = (JComponent)getField(fieldName); boolean changed = Property.isChanged(oldValue, value); if (!changed) { final Object recordValue = this.record.getValue(fieldName); if (Property.isChanged(oldValue, recordValue)) { this.record.setValueByPath(fieldName, value); changed = true; } } SwingUtil.setFieldValue(field, value); if (changed) { if (validate) { validateFields(fieldName); } else { final Set<String> fieldsToValidate = this.fieldsToValidate.get(); if (fieldsToValidate != null) { fieldsToValidate.add(fieldName); } } } } public void setReadOnlyFieldNames(final Collection<String> readOnlyFieldNames) { this.readOnlyFieldNames = new HashSet<>(readOnlyFieldNames); updateReadOnlyFields(); } public void setReadOnlyFieldNames(final String... readOnlyFieldNames) { setReadOnlyFieldNames(Arrays.asList(readOnlyFieldNames)); } public final void setRecord(final LayerRecord record) { Invoke.later(() -> { if (!this.settingRecord) { try { this.settingRecord = true; setRecordDo(record); } finally { this.settingRecord = false; } } }); } public void setRecordDefinition(final RecordDefinition recordDefinition) { this.recordDefinition = recordDefinition; setRecordStore(recordDefinition.getRecordStore()); final String idFieldName = recordDefinition.getIdFieldName(); if (Property.hasValue(idFieldName)) { this.readOnlyFieldNames.add(idFieldName); } for (final FieldDefinition field : recordDefinition.getFields()) { if (field.isRequired()) { final String name = field.getName(); addRequiredFieldNames(name); } } } protected void setRecordDo(final LayerRecord record) { if (!isSame(record)) { requestFocusInWindow(); } if (this.undoManager != null) { try ( final BaseCloseable cu = this.undoManager.setEventsEnabled(false); final BaseCloseable c = setFieldValidationEnabled(false)) { this.record = record; this.fieldsTableModel.setRecord(record); fireButtonPropertyChanges(); setValues(record); this.undoManager.discardAllEdits(); } } } protected void setRecordStore(final RecordStore recordStore) { this.recordStore = recordStore; } public void setRequiredFieldNames(final Collection<String> requiredFieldNames) { this.requiredFieldNames = new HashSet<>(requiredFieldNames); } public final void setTabColor(final int index, final Color foregroundColor) { if (index > -1) { Invoke.later(() -> { if (foregroundColor == null) { this.tabs.setTabComponentAt(index, null); } else { if (this.tabs != null) { final JLabel label = new JLabel(this.tabs.getTitleAt(index)); label.setOpaque(false); label.setForeground(foregroundColor); this.tabs.setTabComponentAt(index, label); } } }); } } public final void setValues(final Map<String, Object> values) { if (values != null) { Invoke.later(() -> { final Set<String> fieldNames = values.keySet(); try ( final BaseCloseable c = setFieldValidationEnabled(false)) { this.fieldValues.putAll(values); for (final String fieldName : fieldNames) { final Object value = values.get(fieldName); final JComponent field = (JComponent)getField(fieldName); if (field != null) { SwingUtil.setFieldValue(field, value); } } } validateFields(fieldNames); }); } } public boolean showAddDialog() { final String title = "Add New " + getName(); final Window window = SwingUtil.getActiveWindow(); final JDialog dialog = new JDialog(window, title, ModalityType.APPLICATION_MODAL); dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); dialog.setLayout(new BorderLayout()); dialog.add(this, BorderLayout.CENTER); final JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT)); dialog.add(buttons, BorderLayout.SOUTH); final JButton addCancelButton = RunnableAction.newButton("Cancel", () -> actionAddCancel()); buttons.add(addCancelButton); buttons.add(this.addOkButton); dialog.pack(); dialog.setLocation(50, 50); dialog.addWindowListener(this); dialog.setVisible(true); SwingUtil.dispose(dialog); return !this.cancelled; } protected final void updateErrors() { Invoke.later(() -> updateErrorsDo()); } protected void updateErrorsDo() { for (final Field field : getFields()) { final String fieldName = field.getFieldName(); final List<String> errors = this.fieldErrors.get(fieldName); final List<String> warnings = this.fieldWarnings.get(fieldName); if (Property.hasValue(errors)) { String message = Strings.toString("<br />", errors); if (Property.hasValue(warnings)) { message += "<br />" + warnings; } field.setFieldInvalid("<html>" + message + "</html>", WebColors.Red, WebColors.Pink); } else { field.setFieldValid(); if (Property.hasValue(warnings)) { field.setFieldToolTip("<html>" + Strings.toString("<br />", warnings) + "</html>"); field.setFieldBackgroundColor(WebColors.Yellow); } } } } public void updateFocussedField() { final Field field = this.fields.get(this.focussedFieldName); if (field != null) { field.updateFieldValue(); } } protected void updateInvalidFields(final boolean fieldsValid) { if (isAllowAddWithErrors()) { setAddOkButtonEnabled(true); } else { final boolean enabled = fieldsValid && isFieldsValid(); setAddOkButtonEnabled(enabled); } } protected void updateReadOnlyFields() { for (final Entry<String, Field> entry : this.fields.entrySet()) { final String name = entry.getKey(); final Field field = entry.getValue(); if (this.readOnlyFieldNames.contains(name)) { field.setEditable(false); } else { field.setEditable(true); } } if (this.fieldsTableModel != null) { this.fieldsTableModel.setReadOnlyFieldNames(this.readOnlyFieldNames); } } public boolean updateTabValid(final int tabIndex) { final boolean tabValid = isTabValid(tabIndex); if (tabValid) { setTabColor(tabIndex, null); } else { setTabColor(tabIndex, WebColors.Red); } return tabValid; } protected boolean validateFieldInternalDo(final String fieldName) { final boolean oldValid = isFieldValid(fieldName); final Field field = getField(fieldName); boolean valid = true; if (!field.isFieldValid()) { final String message = field.getFieldValidationMessage(); setFieldInvalid(fieldName, message); valid = false; } if (valid) { final Set<String> requiredFieldNames = getRequiredFieldNames(); if (requiredFieldNames.contains(fieldName)) { boolean run = true; if (this.record.getState() == RecordState.NEW) { final String idFieldName = getRecordDefinition().getIdFieldName(); if (fieldName.equals(idFieldName)) { run = false; } } if (run) { final Object value = getFieldValue(fieldName); if (!Property.hasValue(value)) { valid = addFieldError(fieldName, "Required"); } } } } if (oldValid != valid) { final int tabIndex = getTabIndex(fieldName); updateTabValid(tabIndex); } return valid; } public final void validateFields() { final Set<String> fieldNames = getFieldNames(); validateFields(fieldNames); } protected final void validateFields(final Collection<String> fieldNames) { if (isFieldValidationEnabled()) { final boolean valid = validateFieldsDo(fieldNames); postValidate(); updateInvalidFields(valid); } } public void validateFields(final String... fieldNames) { validateFields(Arrays.asList(fieldNames)); } protected boolean validateFieldsDo(final Collection<String> fieldNames) { boolean valid = true; for (final String fieldName : fieldNames) { setFieldValid(fieldName); valid &= validateFieldInternalDo(fieldName); } return valid; } @Override public void windowActivated(final WindowEvent e) { } @Override public void windowClosed(final WindowEvent e) { destroy(); final Window window = (Window)e.getSource(); window.removeWindowListener(this); } @Override public void windowClosing(final WindowEvent e) { updateFocussedField(); } @Override public void windowDeactivated(final WindowEvent e) { updateFocussedField(); } @Override public void windowDeiconified(final WindowEvent e) { } @Override public void windowIconified(final WindowEvent e) { } @Override public void windowOpened(final WindowEvent e) { } }