package org.openslx.dozmod.gui.changemonitor; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import javax.swing.ButtonGroup; import javax.swing.ButtonModel; import javax.swing.JCheckBox; import javax.swing.JSpinner; import javax.swing.JTable; import javax.swing.text.JTextComponent; import javax.swing.tree.TreePath; import org.apache.log4j.Logger; import org.jdatepicker.JDatePicker; import org.openslx.dozmod.gui.control.ComboBox; import org.openslx.dozmod.gui.control.JCheckBoxTree; import org.openslx.util.Util; public class DialogChangeMonitor { private static final Logger LOGGER = Logger.getLogger(DialogChangeMonitor.class); private final Callback callback; private final List> controls = new ArrayList<>(); private boolean isCurrentlyModified; private boolean wasEverModified; private boolean isValid = true; public DialogChangeMonitor(Callback cb) { this.callback = cb; } /* * Methods for adding various controls */ private AbstractControlWrapper add(AbstractControlWrapper elem) { controls.add(elem); elem.resetChangeState(); return elem; } public AbstractControlWrapper add(JTextComponent component) { return add(new TextControlWrapper(this, component)); } public AbstractControlWrapper addFixedCombo(ComboBox component, Comparator comparator) { return add(new FixedComboBoxWrapper(this, component, comparator)); } public AbstractControlWrapper addEditableCombo(ComboBox component, Comparator comparator) { return add(new EditableComboBoxWrapper(this, component, comparator)); } public AbstractControlWrapper add(JCheckBox component) { return add(new CheckBoxWrapper(this, component)); } public AbstractControlWrapper add(JCheckBoxTree component) { return add(new CheckBoxTreeWrapper(this, component)); } public AbstractControlWrapper add(ButtonGroup group) { return add(new ButtonGroupWrapper(this, group)); } public AbstractControlWrapper add(JDatePicker picker) { return add(new DatePickerWrapper(this, picker)); } public AbstractControlWrapper add(JSpinner spinner) { return add(new TimeSpinnerWrapper(this, spinner)); } public AbstractControlWrapper add(JTable table) { return add(new TableWrapper(this, table)); } public AbstractControlWrapper add(GenericControlWindow editor) { return add(new GenericControlWrapper(this, editor)); } /* * public methods for controlling the monitor */ public boolean wasEverModified() { return wasEverModified; } public boolean isCurrentlyModified() { return isCurrentlyModified; } public boolean isValid() { return isValid; } public void reset() { boolean oldState = wasEverModified; this.wasEverModified = this.isCurrentlyModified = false; for (AbstractControlWrapper cw : controls) { cw.resetChangeState(); } if (callback != null && oldState) { callback.modificationChanged(); } } /* * */ void validityChanged(AbstractControlWrapper cw) { String error = null; //LOGGER.info(cw.getClass().getSimpleName() + " changed validity to " + cw.isValid); boolean oldValid = this.isValid; if (cw.currentError != null) { this.isValid = false; error = cw.currentError; } else { this.isValid = true; } if (error == null) { for (AbstractControlWrapper c : controls) { if (c.currentError != null) { this.isValid = false; error = c.currentError; break; } } } /* * Trigger callback, either when * a) global state changing from invalid to valid, so the error message can get cleared * b) some control changed from valid to invalid, and we have an error message */ if (callback != null && ((isValid && !oldValid) || error != null)) { callback.validityChanged(error); } } /** * Called from one of the inner "ControlWrapper" classes to trigger * the callback for the according window/control. * * @param cw The ControlWrapper where the changed state changed. */ void contentChanged(AbstractControlWrapper cw) { LOGGER.info(cw.getClass().getSimpleName() + " is changed: " + cw.isCurrentlyChanged); final boolean oldEver = this.wasEverModified; final boolean oldCurrent = this.isCurrentlyModified; this.wasEverModified = false; this.isCurrentlyModified = false; for (AbstractControlWrapper c : controls) { if (c.isCurrentlyChanged) { this.isCurrentlyModified = true; c.wasEverChanged = true; } if (c.wasEverChanged) { this.wasEverModified = true; } } if (callback != null && (oldCurrent != isCurrentlyModified || oldEver != wasEverModified)) { callback.modificationChanged(); } } /* * Validators */ public static interface ValidationConstraint { /** * Method should return null if the input is valid, an * error message otherwise. * * @param userInput Current content/state of the control being monitored * @return error message if invalid, null otherwise */ String checkStateValid(T userInput); } /** * Simple validator that checks for text not being empty */ public static class TextNotEmptyConstraint implements ValidationConstraint { private final String errorMsg; public TextNotEmptyConstraint(String errorMessage) { this.errorMsg = errorMessage; } @Override public String checkStateValid(String userInput) { if (Util.isEmptyString(userInput)) return errorMsg; return null; } } /** * Simple validator that checks if object is not null */ public static class NotNullConstraint implements ValidationConstraint { private final String errorMsg; public NotNullConstraint(String errorMessage) { this.errorMsg = errorMessage; } @Override public String checkStateValid(T userInput) { if (userInput == null) return errorMsg; return null; } } /* * Default validators */ /** * Callback for class using the monitor */ public static interface Callback { public void validityChanged(String errorMessage); public void modificationChanged(); } }