summaryrefslogtreecommitdiffstats
path: root/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor
diff options
context:
space:
mode:
authorSimon Rettberg2018-06-20 17:01:59 +0200
committerSimon Rettberg2018-06-20 17:01:59 +0200
commite30422516ba4f22b1c5ff67c1f0e00521d602b62 (patch)
treeef5b16f3d76e25ea3ff336da990e84245933973c /dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor
parent[server] Don't use shared instance in multi-threaded app (diff)
downloadtutor-module-e30422516ba4f22b1c5ff67c1f0e00521d602b62.tar.gz
tutor-module-e30422516ba4f22b1c5ff67c1f0e00521d602b62.tar.xz
tutor-module-e30422516ba4f22b1c5ff67c1f0e00521d602b62.zip
[client] Add GUI change handling classes
Code to track if a dialog contains modified controls has been messy, error prone and all over the place. Adding a control to a dialog required adding multiple fields to the dialog class and adding new code in multiple places. This is an approach to create a centralized facility that would only require adding all controls of a dialog to the monitor in one place, and then defining a callback to get informed when the validity or content of the whole form changes. The monitor class will also remember the original state of the dialog, so you can tell if the user undoes their changes manually.
Diffstat (limited to 'dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor')
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/AbstractControlWrapper.java118
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/ButtonGroupWrapper.java38
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/CheckBoxTreeWrapper.java56
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/CheckBoxWrapper.java31
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/DatePickerWrapper.java29
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/DialogChangeMonitor.java232
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/EditableComboBoxWrapper.java33
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/FixedComboBoxWrapper.java33
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/LecturePermissionManagerWrapper.java41
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TableWrapper.java152
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TextControlWrapper.java38
-rw-r--r--dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TimeSpinnerWrapper.java36
12 files changed, 837 insertions, 0 deletions
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/AbstractControlWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/AbstractControlWrapper.java
new file mode 100644
index 00000000..4f7f2145
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/AbstractControlWrapper.java
@@ -0,0 +1,118 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import java.util.Comparator;
+
+/**
+ * Control/element wrapper
+ */
+public abstract class AbstractControlWrapper<T> {
+ private final DialogChangeMonitor dcm;
+ protected boolean wasEverChanged;
+ protected boolean isCurrentlyChanged;
+ protected boolean isValid = true;
+ private T originalContent;
+ private final DialogChangeMonitor.ValidationConstraint<T> constraint;
+ private final Comparator<T> cmp;
+ protected final String constraintErrorMessage;
+ /**
+ * If muted, this wrapper will not react to changes of the underlying
+ * control.
+ */
+ private boolean muted = false;
+
+ protected AbstractControlWrapper(DialogChangeMonitor dcm, String errorMessage, Comparator<T> comp,
+ DialogChangeMonitor.ValidationConstraint<T> cons) {
+ this.dcm = dcm;
+ this.constraintErrorMessage = errorMessage;
+ this.cmp = comp;
+ this.constraint = cons;
+ }
+
+ public boolean hasChangedSinceInit() {
+ return wasEverChanged;
+ }
+
+ public boolean isCurrentlyChanged() {
+ return isCurrentlyChanged;
+ }
+
+ /**
+ * Resets the change state of this control
+ * and removes the "muted" flag, if set.
+ */
+ public void reset() {
+ boolean wasChanged = wasEverChanged;
+ resetChangeState();
+ if (wasChanged) {
+ // It's possible that the global change state changed
+ dcm.contentChanged(this);
+ }
+ }
+
+ protected final void resetChangeState() {
+ muted = false;
+ isCurrentlyChanged = wasEverChanged = false;
+ originalContent = getCurrentValue();
+ checkValid(originalContent);
+ }
+
+ /**
+ * To be called by the implementation whenever the content of the control
+ * changed and according checks should be triggered.
+ */
+ protected final void contentChanged() {
+ T text = getCurrentValue();
+ checkChanged(text);
+ checkValid(text);
+ }
+
+ /**
+ * Ignore any changes, don't trigger callbacks regarding
+ * this component. This flag can be removed by calling
+ * reset() on this ControlWrapper or the enclosing
+ * DialogChangeMonitor.
+ */
+ public void mute() {
+ muted = true;
+ }
+
+ /**
+ * Method MUST return the current value of the monitored component, so
+ * that the current state can be considered as the original unmodified
+ * state.
+ */
+ abstract T getCurrentValue();
+
+ protected void checkChanged(T newContent) {
+ if (muted)
+ return;
+ final boolean changed;
+ if (cmp == null) {
+ if (originalContent == null) {
+ if (newContent == null) {
+ changed = false;
+ } else {
+ changed = true;
+ }
+ } else {
+ changed = !originalContent.equals(newContent);
+ }
+ } else {
+ changed = cmp.compare(originalContent, newContent) != 0;
+ }
+ if (isCurrentlyChanged != changed) {
+ isCurrentlyChanged = changed;
+ dcm.contentChanged(this);
+ }
+ }
+
+ protected void checkValid(T text) {
+ if (muted)
+ return;
+ boolean valid = constraint == null || constraint.isValid(text);
+ if (isValid != valid) {
+ isValid = valid;
+ dcm.validityChanged(this);
+ }
+ }
+} \ No newline at end of file
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/ButtonGroupWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/ButtonGroupWrapper.java
new file mode 100644
index 00000000..3606a99b
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/ButtonGroupWrapper.java
@@ -0,0 +1,38 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Enumeration;
+
+import javax.swing.AbstractButton;
+import javax.swing.ButtonGroup;
+import javax.swing.ButtonModel;
+
+import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
+
+/**
+ * Monitor a button group (e.g. group of radio boxes)
+ */
+class ButtonGroupWrapper extends AbstractControlWrapper<ButtonModel> {
+ private final ButtonGroup buttons;
+
+ public ButtonGroupWrapper(DialogChangeMonitor dcm, ButtonGroup group, ValidationConstraint<ButtonModel> constraint, String errorMessage) {
+ super(dcm, errorMessage, null, constraint);
+ buttons = group;
+ ItemListener listener = new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ contentChanged();
+ }
+ };
+ Enumeration<AbstractButton> it = buttons.getElements();
+ while (it.hasMoreElements()) {
+ it.nextElement().addItemListener(listener);
+ }
+ }
+
+ @Override
+ ButtonModel getCurrentValue() {
+ return buttons.getSelection();
+ }
+} \ No newline at end of file
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/CheckBoxTreeWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/CheckBoxTreeWrapper.java
new file mode 100644
index 00000000..23e8a742
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/CheckBoxTreeWrapper.java
@@ -0,0 +1,56 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import java.util.Comparator;
+
+import javax.swing.tree.TreePath;
+
+import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
+import org.openslx.dozmod.gui.control.JCheckBoxTree;
+import org.openslx.dozmod.gui.control.JCheckBoxTree.CheckChangeEvent;
+import org.openslx.dozmod.gui.control.JCheckBoxTree.CheckChangeEventListener;
+
+class CheckBoxTreeWrapper extends AbstractControlWrapper<TreePath[]> {
+
+ private static final Comparator<TreePath[]> COMPARATOR = new Comparator<TreePath[]>() {
+ @Override
+ public int compare(TreePath[] o1, TreePath[] o2) {
+ if (o1 == null && o2 == null)
+ return 0;
+ if (o1 == null)
+ return o2.length;
+ if (o2 == null)
+ return -o1.length;
+ if (o1.length != o2.length)
+ return o2.length - o1.length;
+ for (int i = 0; i < o1.length; ++i) {
+ TreePath p1 = o1[i];
+ TreePath p2 = o2[i];
+ if (p1.getPathCount() != p2.getPathCount())
+ return p2.getPathCount() - p1.getPathCount();
+ for (; p1 != null && p2 != null; p1 = p1.getParentPath(), p2 = p2.getParentPath()) {
+ long diff = p1.getLastPathComponent().hashCode() - p2.getLastPathComponent().hashCode();
+ if (diff != 0)
+ return (int)diff;
+ }
+ }
+ return 0;
+ }
+ };
+
+ private final JCheckBoxTree tree;
+
+ public CheckBoxTreeWrapper(DialogChangeMonitor dcm, JCheckBoxTree t, ValidationConstraint<TreePath[]> constraint, String errorMessage) {
+ super(dcm, errorMessage, COMPARATOR, constraint);
+ tree = t;
+ tree.addCheckChangeEventListener(new CheckChangeEventListener() {
+ public void checkStateChanged(CheckChangeEvent event) {
+ contentChanged();
+ }
+ });
+ }
+
+ @Override
+ TreePath[] getCurrentValue() {
+ return tree.getSelectionPaths();
+ }
+} \ No newline at end of file
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/CheckBoxWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/CheckBoxWrapper.java
new file mode 100644
index 00000000..4683458e
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/CheckBoxWrapper.java
@@ -0,0 +1,31 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.JCheckBox;
+
+import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
+
+/**
+ * Monitoring a JCheckBox
+ */
+class CheckBoxWrapper extends AbstractControlWrapper<Boolean> {
+
+ private final JCheckBox checkbox;
+
+ public CheckBoxWrapper(DialogChangeMonitor dcm, JCheckBox box, ValidationConstraint<Boolean> constraint, String errorMessage) {
+ super(dcm, errorMessage, null, constraint);
+ checkbox = box;
+ checkbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ contentChanged();
+ }
+ });
+ }
+
+ @Override
+ Boolean getCurrentValue() {
+ return checkbox.isSelected();
+ }
+} \ No newline at end of file
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/DatePickerWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/DatePickerWrapper.java
new file mode 100644
index 00000000..40930862
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/DatePickerWrapper.java
@@ -0,0 +1,29 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import org.jdatepicker.JDatePicker;
+import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
+
+/**
+ * Monitoring a JTextField etc.
+ */
+class DatePickerWrapper extends AbstractControlWrapper<Object> {
+ private final JDatePicker component;
+
+ public DatePickerWrapper(DialogChangeMonitor dcm, JDatePicker picker, ValidationConstraint<Object> constr, String errorMessage) {
+ super(dcm, errorMessage, null, constr);
+ this.component = picker;
+
+ picker.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ contentChanged();
+ }
+ });
+ }
+
+ Object getCurrentValue() {
+ return this.component.getModel().getValue();
+ }
+} \ No newline at end of file
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/DialogChangeMonitor.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/DialogChangeMonitor.java
new file mode 100644
index 00000000..d05fac72
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/DialogChangeMonitor.java
@@ -0,0 +1,232 @@
+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.dozmod.gui.control.LectureCustomPermissionManager;
+import org.openslx.util.Util;
+
+public class DialogChangeMonitor {
+
+ private static final Logger LOGGER = Logger.getLogger(DialogChangeMonitor.class);
+
+ private final Callback callback;
+
+ private final List<AbstractControlWrapper<?>> controls = new ArrayList<>();
+
+ private boolean isCurrentlyModified;
+
+ private boolean wasEverModified;
+
+ private boolean isValid = true;
+
+ public DialogChangeMonitor(Callback cb) {
+ this.callback = cb;
+ }
+
+ private void warnConstraint(Object component, ValidationConstraint<?> constraint, String errorMessage) {
+ if (callback != null && constraint != null && errorMessage == null) {
+ LOGGER.warn("Adding " + component.getClass().getSimpleName()
+ + " with constraint but no error message!");
+ }
+ }
+
+ /*
+ * Methods for adding various controls
+ */
+
+ private AbstractControlWrapper<?> add(AbstractControlWrapper<?> elem) {
+ controls.add(elem);
+ elem.resetChangeState();
+ return elem;
+ }
+
+ public AbstractControlWrapper<?> add(JTextComponent component, ValidationConstraint<String> constraint,
+ String errorMessage) {
+ warnConstraint(component, constraint, errorMessage);
+ return add(new TextControlWrapper(this, component, constraint, errorMessage));
+ }
+
+ public AbstractControlWrapper<?> add(JTextComponent component) {
+ return add(component, null, null);
+ }
+
+ public <T> AbstractControlWrapper<?> addFixedCombo(ComboBox<T> component, Comparator<T> comparator,
+ ValidationConstraint<T> constraint, String errorMessage) {
+ warnConstraint(component, constraint, errorMessage);
+ return add(new FixedComboBoxWrapper<T>(this, component, comparator, constraint, errorMessage));
+ }
+
+ public AbstractControlWrapper<?> addEditableCombo(ComboBox<?> component, Comparator<Object> comparator,
+ ValidationConstraint<Object> constraint, String errorMessage) {
+ warnConstraint(component, constraint, errorMessage);
+ return add(new EditableComboBoxWrapper(this, component, comparator, constraint, errorMessage));
+ }
+
+ public AbstractControlWrapper<?> add(JCheckBox component, ValidationConstraint<Boolean> constraint,
+ String errorMessage) {
+ warnConstraint(component, constraint, errorMessage);
+ return add(new CheckBoxWrapper(this, component, constraint, errorMessage));
+ }
+
+ public AbstractControlWrapper<?> add(JCheckBox component) {
+ return add(component, null, null);
+ }
+
+ public AbstractControlWrapper<?> add(JCheckBoxTree component,
+ ValidationConstraint<TreePath[]> constraint, String errorMessage) {
+ warnConstraint(component, constraint, errorMessage);
+ return add(new CheckBoxTreeWrapper(this, component, constraint, errorMessage));
+ }
+
+ public AbstractControlWrapper<?> add(ButtonGroup group, ValidationConstraint<ButtonModel> constraint,
+ String errorMessage) {
+ warnConstraint(group, constraint, errorMessage);
+ return add(new ButtonGroupWrapper(this, group, constraint, errorMessage));
+ }
+
+ public AbstractControlWrapper<?> add(JDatePicker picker, ValidationConstraint<Object> constraint,
+ String errorMessage) {
+ warnConstraint(picker, constraint, errorMessage);
+ return add(new DatePickerWrapper(this, picker, constraint, errorMessage));
+ }
+
+ public AbstractControlWrapper<?> add(JSpinner spinner) {
+ return add(new TimeSpinnerWrapper(this, spinner, null, null));
+ }
+
+ public AbstractControlWrapper<?> add(LectureCustomPermissionManager manager) {
+ return add(new LecturePermissionManagerWrapper(this, manager, null, null));
+ }
+
+ public AbstractControlWrapper<?> add(JTable table) {
+ return add(new TableWrapper(this, table, null, null));
+ }
+
+ /*
+ * 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.isValid) {
+ this.isValid = false;
+ error = cw.constraintErrorMessage;
+ } else {
+ this.isValid = true;
+ }
+ if (error == null) {
+ for (AbstractControlWrapper<?> c : controls) {
+ if (!c.isValid) {
+ this.isValid = false;
+ error = c.constraintErrorMessage;
+ 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<T> {
+ boolean isValid(T userInput);
+ }
+
+ /*
+ * Default validators
+ */
+
+ public static final ValidationConstraint<String> VC_NOT_EMPTY = new ValidationConstraint<String>() {
+ public boolean isValid(String userInput) {
+ return !Util.isEmptyString(userInput);
+ }
+ };
+
+ /**
+ * Callback for class using the monitor
+ */
+ public static interface Callback {
+ public void validityChanged(String errorMessage);
+
+ public void modificationChanged();
+ }
+
+}
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/EditableComboBoxWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/EditableComboBoxWrapper.java
new file mode 100644
index 00000000..d77a5aa5
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/EditableComboBoxWrapper.java
@@ -0,0 +1,33 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Comparator;
+
+import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
+import org.openslx.dozmod.gui.control.ComboBox;
+
+/**
+ * Monitoring a ComboBox that is editable
+ */
+class EditableComboBoxWrapper extends AbstractControlWrapper<Object> {
+ private final ComboBox<?> comboBox;
+
+ public EditableComboBoxWrapper(DialogChangeMonitor dcm, ComboBox<?> combo, Comparator<Object> comparator, ValidationConstraint<Object> constraint, String errorMessage) {
+ super(dcm, errorMessage, comparator, constraint);
+ comboBox = combo;
+ comboBox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() != ItemEvent.SELECTED)
+ return;
+ contentChanged();
+ }
+ });
+ }
+
+ @Override
+ Object getCurrentValue() {
+ return comboBox.getEditor().getItem();
+ }
+
+} \ No newline at end of file
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/FixedComboBoxWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/FixedComboBoxWrapper.java
new file mode 100644
index 00000000..096aaa23
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/FixedComboBoxWrapper.java
@@ -0,0 +1,33 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Comparator;
+
+import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
+import org.openslx.dozmod.gui.control.ComboBox;
+
+/**
+ * Monitoring a ComboBox that is not editable
+ */
+class FixedComboBoxWrapper<T> extends AbstractControlWrapper<T> {
+ private final ComboBox<T> comboBox;
+
+ public FixedComboBoxWrapper(DialogChangeMonitor dcm, ComboBox<T> combo, Comparator<T> comparator, ValidationConstraint<T> constraint, String errorMessage) {
+ super(dcm, errorMessage, comparator, constraint);
+ comboBox = combo;
+ comboBox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() != ItemEvent.SELECTED)
+ return;
+ contentChanged();
+ }
+ });
+ }
+
+ @Override
+ T getCurrentValue() {
+ return comboBox.getItemAt(comboBox.getSelectedIndex());
+ }
+
+} \ No newline at end of file
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/LecturePermissionManagerWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/LecturePermissionManagerWrapper.java
new file mode 100644
index 00000000..88b618d6
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/LecturePermissionManagerWrapper.java
@@ -0,0 +1,41 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import java.util.Comparator;
+import java.util.Map;
+
+import org.openslx.bwlp.thrift.iface.LecturePermissions;
+import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
+import org.openslx.dozmod.gui.control.LectureCustomPermissionManager;
+import org.openslx.dozmod.gui.control.LectureCustomPermissionManager.UserChangeEvent;
+import org.openslx.dozmod.gui.control.LectureCustomPermissionManager.UserChangeEventListener;
+import org.openslx.dozmod.util.MapHelper;
+
+class LecturePermissionManagerWrapper extends AbstractControlWrapper<Map<String, LecturePermissions>> {
+
+ /**
+ * Comparator so we can check properly if the permission list changed
+ */
+ private static final Comparator<Map<String, LecturePermissions>> COMPARATOR = new Comparator<Map<String,LecturePermissions>>() {
+ public int compare(Map<String, LecturePermissions> o1, Map<String, LecturePermissions> o2) {
+ return MapHelper.compare(o1, o2);
+ }
+ };
+
+ private final LectureCustomPermissionManager component;
+
+ public LecturePermissionManagerWrapper(DialogChangeMonitor dcm, LectureCustomPermissionManager manager,
+ ValidationConstraint<Map<String, LecturePermissions>> constr, String errorMessage) {
+ super(dcm, errorMessage, COMPARATOR, constr);
+ this.component = manager;
+
+ manager.addUserChangeEventListener(new UserChangeEventListener() {
+ public void stateChanged(UserChangeEvent event) {
+ contentChanged();
+ }
+ });
+ }
+
+ Map<String, LecturePermissions> getCurrentValue() {
+ return this.component.getPermissions();
+ }
+} \ No newline at end of file
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TableWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TableWrapper.java
new file mode 100644
index 00000000..f8bcfa01
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TableWrapper.java
@@ -0,0 +1,152 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Comparator;
+
+import javax.swing.JTable;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.TableModel;
+
+import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
+
+/**
+ * Monitoring a JTable etc.
+ */
+class TableWrapper extends AbstractControlWrapper<ClonedTableModel> {
+
+ private static final Comparator<ClonedTableModel> COMPARATOR = new Comparator<ClonedTableModel>() {
+ public int compare(ClonedTableModel o1, ClonedTableModel o2) {
+ if (o1 == null && o2 == null)
+ return 0;
+ if (o1 == null)
+ return -o2.rowCount;
+ if (o2 == null)
+ return o1.rowCount;
+ if (o1.rowCount != o2.rowCount)
+ return o2.rowCount - o1.rowCount;
+ if (o1.columnCount != o2.columnCount)
+ return o2.columnCount - o1.columnCount;
+ for (int i = 0; i < o1.grid.length; ++i) {
+ Object c1 = o1.grid[i];
+ Object c2 = o2.grid[i];
+ if (c1 == c2)
+ continue;
+ if (c1 == null)
+ return c2.hashCode();
+ if (c2 == null)
+ return c1.hashCode();
+ if (c1.equals(c2))
+ continue;
+ return c1.hashCode() - c2.hashCode();
+ }
+ return 0;
+ }
+ };
+
+ private final JTable component;
+
+ public TableWrapper(DialogChangeMonitor dcm, JTable table,
+ ValidationConstraint<ClonedTableModel> constr, String errorMessage) {
+ super(dcm, errorMessage, COMPARATOR, constr);
+ this.component = table;
+
+ final TableModelListener changeListener = new TableModelListener() {
+ public void tableChanged(TableModelEvent e) {
+ contentChanged();
+ }
+ };
+
+ // Make sure we notice when the whole model changes
+ table.addPropertyChangeListener("model", new PropertyChangeListener() {
+ public void propertyChange(PropertyChangeEvent evt) {
+ Object o = evt.getOldValue();
+ Object n = evt.getNewValue();
+ if (o != null && o instanceof TableModel) {
+ ((TableModel)o).removeTableModelListener(changeListener);
+ }
+ if (n != null && n instanceof TableModel) {
+ ((TableModel)n).addTableModelListener(changeListener);
+ }
+ }
+ });
+ // Add listener to the current model
+ if (table.getModel() != null) {
+ table.getModel().addTableModelListener(changeListener);
+ }
+ }
+
+ ClonedTableModel getCurrentValue() {
+ TableModel model = component.getModel();
+ if (model == null)
+ return null;
+ return new ClonedTableModel(model);
+ }
+}
+
+class ClonedTableModel implements TableModel {
+
+ protected final Object[] grid;
+ protected final int columnCount, rowCount;
+
+ protected ClonedTableModel(TableModel other) {
+ columnCount = other.getColumnCount();
+ rowCount = other.getRowCount();
+ grid = new Object[columnCount * rowCount];
+ for (int y = 0; y < rowCount; ++y) {
+ for (int x = 0; x < columnCount; ++x) {
+ // Hopefully we mostly deal with imutable types so we don't need clone
+ grid[y * rowCount + x] = other.getValueAt(y, x);
+ }
+ }
+ }
+
+ @Override
+ public int getRowCount() {
+ return rowCount;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return columnCount;
+ }
+
+ @Override
+ public String getColumnName(int columnIndex) {
+ return null;
+ }
+
+ @Override
+ public Class<?> getColumnClass(int columnIndex) {
+ if (grid[columnIndex] == null)
+ return Object.class;
+ return grid[columnIndex].getClass();
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return false;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ return grid[rowIndex * rowCount + columnCount];
+ }
+
+ @Override
+ public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+ throw new RuntimeException("A cloned table model is read only");
+ }
+
+ @Override
+ public void addTableModelListener(TableModelListener l) {
+ throw new RuntimeException("A cloned table model is read only");
+ }
+
+ @Override
+ public void removeTableModelListener(TableModelListener l) {
+ throw new RuntimeException("A cloned table model is read only");
+ }
+
+} \ No newline at end of file
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TextControlWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TextControlWrapper.java
new file mode 100644
index 00000000..6535a113
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TextControlWrapper.java
@@ -0,0 +1,38 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.JTextComponent;
+
+import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
+
+/**
+ * Monitoring a JTextField etc.
+ */
+class TextControlWrapper extends AbstractControlWrapper<String> {
+ private final JTextComponent component;
+
+ public TextControlWrapper(DialogChangeMonitor dcm, JTextComponent textField,
+ ValidationConstraint<String> constr, String errorMessage) {
+ super(dcm, errorMessage, null, constr);
+ this.component = textField;
+
+ textField.getDocument().addDocumentListener(new DocumentListener() {
+ public void removeUpdate(DocumentEvent e) {
+ insertUpdate(e);
+ }
+
+ public void insertUpdate(DocumentEvent e) {
+ contentChanged();
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ insertUpdate(e);
+ }
+ });
+ }
+
+ String getCurrentValue() {
+ return this.component.getText();
+ }
+} \ No newline at end of file
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TimeSpinnerWrapper.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TimeSpinnerWrapper.java
new file mode 100644
index 00000000..ecb6e50c
--- /dev/null
+++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/changemonitor/TimeSpinnerWrapper.java
@@ -0,0 +1,36 @@
+package org.openslx.dozmod.gui.changemonitor;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.JSpinner;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor.ValidationConstraint;
+
+class TimeSpinnerWrapper extends AbstractControlWrapper<Object> {
+
+ private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("k:m");
+
+ private final JSpinner component;
+
+ public TimeSpinnerWrapper(DialogChangeMonitor dcm, JSpinner picker, ValidationConstraint<Object> constr, String errorMessage) {
+ super(dcm, errorMessage, null, constr);
+ this.component = picker;
+
+ picker.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ contentChanged();
+ }
+ });
+ }
+
+ Object getCurrentValue() {
+ Object value = this.component.getValue();
+ if (value instanceof Date) {
+ return TIME_FORMAT.format(value);
+ }
+ return value;
+ }
+} \ No newline at end of file