package rocks.inspectit.ui.rcp.validation;
import java.util.Collection;
import java.util.HashSet;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.forms.IMessageManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract class for all {@link ValidationControlDecoration}s.
*
* @author Ivan Senic
*
* @param <T>
* Type of control to decorate.
*/
public abstract class ValidationControlDecoration<T extends Control> {
/**
* Logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ValidationControlDecoration.class);
/**
* Control.
*/
private final T control;
/**
* {@link IMessageManager} that can be provided for reporting the validation of the input.
*/
private IMessageManager messageManager;
/**
* Control decoration to be created when the {@link #messageManager} is not provided.
*/
private ControlDecoration controlDecoration;
/**
* Description text.
*/
private String descriptionText;
/**
* Color of the control background.
*/
private final Color controlBackground;
/**
* Color to highlight the widget when input is not valid.
*/
private final Color nonValidBackground;
/**
* If control holds valid values.
*/
private boolean valid = true;
/**
* Defines if the control background should be changed to/from valid/invalid color. Defaults to
* <code>true</code>. If set to <code>false</code> no background will be changed on the control
* being validated.
*/
private boolean alterControlBackround = true;
/**
* Validation listeners.
*/
private final Collection<IControlValidationListener> validationListeners = new HashSet<>();
/**
* Listener used to be hooked to the events.
*/
private final Listener listener = new Listener() {
@Override
public void handleEvent(Event event) {
executeValidation();
}
};
/**
* Simple constructor. Control decoration will be handled by message manager if one is supplied.
*
* @param control
* Control to decorate.
* @param messageManager
* {@link IMessageManager} to use for reporting messages. Can be <code>null</code>
*/
public ValidationControlDecoration(T control, IMessageManager messageManager) {
this(control, messageManager, true);
}
/**
* Simple constructor. Control decoration will not be handled by message manager.Registers
* listener to the list of validation listeners.
*
* @param control
* Control to decorate.
* @param listener
* {@link IControlValidationListener}.
*/
public ValidationControlDecoration(T control, IControlValidationListener listener) {
this(control, listener, true);
}
/**
* Simple constructor. Control decoration will not be handled by message manager.Registers
* listener to the list of validation listeners.
*
* @param control
* Control to decorate.
* @param listener
* {@link IControlValidationListener}.
* @param startupValidation
* Defines whether the control shall be validated during construction of the
* ValidationControlDecoration.
*/
public ValidationControlDecoration(T control, IControlValidationListener listener, boolean startupValidation) {
this(control, (IMessageManager) null, true, startupValidation);
addControlValidationListener(listener);
}
/**
* Default constructor that allows setting of the {@link #alterControlBackround}. Control
* decoration will be handled by message manager if one is supplied.
*
* @param control
* Control to decorate.
* @param messageManager
* {@link IMessageManager} to use for reporting messages. Can be <code>null</code>
* @param alterControlBackround
* Defines if the control background should be changed to/from valid/invalid color.
* Defaults to <code>true</code>. If set to <code>false</code> no background will be
* changed on the control being validated.
*/
public ValidationControlDecoration(T control, IMessageManager messageManager, boolean alterControlBackround) {
this(control, messageManager, alterControlBackround, true);
}
/**
* Default constructor that allows setting of the {@link #alterControlBackround}. Control
* decoration will be handled by message manager if one is supplied.
*
* @param control
* Control to decorate.
* @param messageManager
* {@link IMessageManager} to use for reporting messages. Can be <code>null</code>
* @param alterControlBackround
* Defines if the control background should be changed to/from valid/invalid color.
* Defaults to <code>true</code>. If set to <code>false</code> no background will be
* changed on the control being validated.
* @param startupValidation
* Defines whether the control shall be validated during construction of the
* ValidationControlDecoration.
*/
public ValidationControlDecoration(T control, IMessageManager messageManager, boolean alterControlBackround, boolean startupValidation) {
this.control = control;
this.messageManager = messageManager;
this.alterControlBackround = alterControlBackround;
this.controlBackground = control.getBackground();
this.nonValidBackground = new Color(control.getDisplay(), 255, 200, 200);
// must be added before creating the decoration
this.control.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
try {
hide();
} catch (Exception exception) {
// ignore exception on purpose
// SWT exception is thrown when one of the sibling elements is disposed in the
// process that one of the parent UI elements is disposed.
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Ignoring Exception on ValidationControlDecoration disposal.", exception);
}
}
dispose();
}
});
// must be added before creating the decoration
// ensures that decoration is moved with the control on layout events
this.control.addControlListener(new ControlListener() {
@Override
public void controlResized(ControlEvent e) {
Composite c = ValidationControlDecoration.this.control.getParent();
while (null != c) {
c.redraw();
c = c.getParent();
}
}
@Override
public void controlMoved(ControlEvent e) {
Composite c = ValidationControlDecoration.this.control.getParent();
while (null != c) {
c.redraw();
c = c.getParent();
}
}
});
if (null == messageManager) {
this.controlDecoration = new ControlDecoration(control, SWT.LEFT | SWT.BOTTOM);
this.controlDecoration.setImage(FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_ERROR).getImage());
}
if (startupValidation) {
startupValidation();
} else {
// hide decoration per default
hide();
}
}
/**
* Constructor. Registers listener to the list of validation listeners.
*
* @param control
* Control to decorate.
* @param messageManager
* {@link IMessageManager} to use for reporting messages.
* @param listener
* {@link IControlValidationListener}.
*/
public ValidationControlDecoration(T control, IMessageManager messageManager, IControlValidationListener listener) {
this(control, messageManager);
addControlValidationListener(listener);
}
/**
* Constructor allowing setting of all fields. Registers listener to the list of validation
* listeners.
*
* @param control
* Control to decorate.
* @param messageManager
* {@link IMessageManager} to use for reporting messages.
* @param alterControlBackround
* Defines if the control background should be changed to/from valid/invalid color.
* Defaults to <code>true</code>. If set to <code>false</code> no background will be
* changed on the control being validated.
* @param listener
* {@link IControlValidationListener}.
*/
public ValidationControlDecoration(T control, IMessageManager messageManager, boolean alterControlBackround, IControlValidationListener listener) {
this(control, messageManager, alterControlBackround);
addControlValidationListener(listener);
}
/**
* Executes validation on the startup.
*/
protected final void startupValidation() {
this.valid = !controlActive() || validate(control);
if (this.valid) {
hide();
} else {
show();
}
}
/**
* Validates the current value in the control.
*
* @param control
* {@link Control}
* @return <code>true</code> if validation passed, false otherwise.
*/
protected abstract boolean validate(T control);
/**
* Executes the validation, shows or hides the decoration and informs the
* {@link #validationListeners} only if the validation value changed.
*/
public void executeValidation() {
executeValidation(false);
}
/**
* Executes the validation, shows or hides the decoration and informs the
* {@link #validationListeners}.
*
* @param informListenerAlways
* Always inform the listener even if the results of the validation did not change.
*/
public void executeValidation(boolean informListenerAlways) {
boolean tmp = valid;
if (controlActive()) {
valid = validate(control);
} else {
valid = true;
}
if (tmp != valid) {
if (valid) {
hide();
} else {
show();
}
}
if (informListenerAlways || (tmp != valid)) {
for (IControlValidationListener listener : validationListeners) {
listener.validationStateChanged(valid, ValidationControlDecoration.this);
}
}
}
/**
* Shows error.
*/
private void show() {
if (null != controlDecoration) {
controlDecoration.show();
} else if (null != messageManager) {
messageManager.addMessage(this, descriptionText, null, IMessageProvider.ERROR, control);
}
if (alterControlBackround) {
control.setBackground(nonValidBackground);
}
}
/**
* Hides error.
*/
private void hide() {
if (null != controlDecoration) {
controlDecoration.hide();
} else if (null != messageManager) {
messageManager.removeMessage(this, control);
}
if (alterControlBackround) {
control.setBackground(controlBackground);
}
}
/**
* Register event type when validation should kick in.
*
* @param eventType
* Event type.
*/
public void registerListener(int eventType) {
registerListener(control, eventType);
}
/**
* Register event type when validation should kick in with any arbitrary control.
*
* @param control
* to register validation to
* @param eventType
* Event type.
*/
public void registerListener(Control control, int eventType) {
Assert.isNotNull(control);
control.addListener(eventType, listener);
}
/**
* Adds {@link IControlValidationListener} to the list of listeners.
*
* @param validationListener
* {@link IControlValidationListener}.
*/
public final void addControlValidationListener(IControlValidationListener validationListener) {
if (null != validationListener) {
validationListeners.add(validationListener);
}
}
/**
* Returns if control is active. No validation will be process when control is not active.
*
* @return Returns if control is active. No validation will be process when control is not
* active.
*/
private boolean controlActive() {
return control.isEnabled();
}
/**
* Gets {@link #valid}.
*
* @return {@link #valid}
*/
public boolean isValid() {
return valid;
}
/**
* Gets {@link #control}.
*
* @return {@link #control}
*/
public T getControl() {
return control;
}
/**
* Gets {@link #descriptionText}.
*
* @return {@link #descriptionText}
*/
public String getDescriptionText() {
return descriptionText;
}
/**
* Sets {@link #descriptionText}.
*
* @param descriptionText
* New value for {@link #descriptionText}
*/
public void setDescriptionText(String descriptionText) {
this.descriptionText = descriptionText;
if (null != controlDecoration) {
controlDecoration.setDescriptionText(descriptionText);
}
}
/**
* Disposes the {@link #nonValidBackground} and the {@link #controlDecoration}.
*/
private void dispose() {
nonValidBackground.dispose();
if (null != controlDecoration) {
controlDecoration.dispose();
}
}
}