blob: 5c814b200961281b942f368967916fae1ace699e [file] [log] [blame]
/*
* Copyright (c) 2010-2018 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
*/
package org.eclipse.scout.rt.client.ui;
import static org.eclipse.scout.rt.platform.util.Assertions.assertNotNull;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import org.eclipse.scout.rt.client.ui.action.IAction;
import org.eclipse.scout.rt.platform.Order;
import org.eclipse.scout.rt.platform.annotations.ConfigProperty;
import org.eclipse.scout.rt.platform.classid.ClassId;
import org.eclipse.scout.rt.platform.holders.Holder;
import org.eclipse.scout.rt.platform.reflect.AbstractPropertyObserver;
import org.eclipse.scout.rt.platform.reflect.ConfigurationUtility;
import org.eclipse.scout.rt.platform.util.CollectionUtility;
import org.eclipse.scout.rt.platform.util.visitor.IBreadthFirstTreeVisitor;
import org.eclipse.scout.rt.platform.util.visitor.IDepthFirstTreeVisitor;
import org.eclipse.scout.rt.platform.util.visitor.TreeTraversals;
import org.eclipse.scout.rt.platform.util.visitor.TreeVisitResult;
import org.eclipse.scout.rt.security.ACCESS;
import org.eclipse.scout.rt.shared.data.basic.NamedBitMaskHelper;
import org.eclipse.scout.rt.shared.dimension.IDimensions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @since 8.0
*/
@ClassId("c11a79f7-0af6-430e-9700-2d050e3aa41e")
public abstract class AbstractWidget extends AbstractPropertyObserver implements IWidget {
private static final Logger LOG = LoggerFactory.getLogger(AbstractWidget.class);
private static final NamedBitMaskHelper ENABLED_BIT_HELPER = new NamedBitMaskHelper(IDimensions.ENABLED, IDimensions.ENABLED_GRANTED);
private static final String PROP_ENABLED_BYTE = "enabledByte";
private List<WidgetListener> m_listeners;
public AbstractWidget() {
this(true);
}
public AbstractWidget(boolean callInitializer) {
setEnabledByte(NamedBitMaskHelper.ALL_BITS_SET, false); // default enabled
if (callInitializer) {
callInitializer();
}
}
protected final void callInitializer() {
if (isInitConfigDone()) {
return;
}
initConfigInternal();
setInitConfigDone(true);
}
/**
* Will be called by {@link #callInitializer()} but only if {@link #isInitConfigDone()} returns false.
*/
protected void initConfigInternal() {
initConfig();
}
protected void initConfig() {
setCssClass(getConfiguredCssClass());
setEnabled(getConfiguredEnabled());
setInheritAccessibility(getConfiguredInheritAccessibility());
}
@Override
public boolean isInitConfigDone() {
return propertySupport.getPropertyBool(PROP_INIT_CONFIG_DONE);
}
protected void setInitConfigDone(boolean initConfigDone) {
propertySupport.setPropertyBool(PROP_INIT_CONFIG_DONE, initConfigDone);
}
@Override
public final void init() {
if (isInitDone()) {
return;
}
doInit(true);
}
@Override
public <T extends IWidget> T getWidgetByClass(Class<T> widgetClassToFind) {
assertNotNull(widgetClassToFind);
final Holder<T> result = new Holder<>(widgetClassToFind);
Function<IWidget, TreeVisitResult> visitor = w -> {
if (w instanceof AbstractWidget) {
return ((AbstractWidget) w).getWidgetByClassInternal(result, widgetClassToFind);
}
return TreeVisitResult.CONTINUE;
};
visit(visitor);
return result.getValue();
}
public <T extends IWidget> TreeVisitResult getWidgetByClassInternal(Holder<T> result, Class<T> widgetClassToFind) {
if (widgetClassToFind.isInstance(this)) {
result.setValue(widgetClassToFind.cast(this));
return TreeVisitResult.TERMINATE;
}
return TreeVisitResult.CONTINUE;
}
private void doInit(boolean recursive) {
initInternal();
if (recursive) {
initChildren();
}
setInitDone(true);
setDisposeDone(false);
}
protected void initChildren() {
initChildren(getChildren());
}
protected void initChildren(List<? extends IWidget> widgets) {
for (IWidget w : widgets) {
w.init();
}
}
/**
* Will be called by {@link #init()} but only if {@link #isInitDone()} returns false.<br>
* This method should initialize this instance and non-widget-children only. Child widgets will be initialized in
* {@link #initChildren()}.
*/
protected void initInternal() {
// nop
}
@Override
public boolean isInitDone() {
return propertySupport.getPropertyBool(PROP_INIT_DONE);
}
protected void setInitDone(boolean initDone) {
propertySupport.setPropertyBool(PROP_INIT_DONE, initDone);
}
@Override
public void reinit() {
setInitDone(false);
doInit(false);
for (IWidget w : getChildren()) {
w.reinit();
}
}
@Override
public final void dispose() {
if (isDisposeDone()) {
return;
}
disposeChildren();
try {
disposeInternal();
}
catch (RuntimeException t) {
LOG.warn("Could not dispose widget '{}'.", this, t);
}
setDisposeDone(true);
setInitDone(false);
}
protected void disposeChildren() {
disposeChildren(getChildren());
}
protected void disposeChildren(List<? extends IWidget> widgetsToDispose) {
for (IWidget w : widgetsToDispose) {
try {
w.dispose();
}
catch (RuntimeException t) {
LOG.warn("Could not dispose widget '{}'.", w, t);
}
}
}
/**
* Will be called by {@link #dispose()} but only if {@link #isDisposeDone()} returns false.<br>
* This method should only dispose this single instance and non-widget children. Widget children are disposed
* automatically in {@link #disposeChildren()}.
*/
protected void disposeInternal() {
// nop
}
@Override
public boolean isDisposeDone() {
return propertySupport.getPropertyBool(PROP_DISPOSE_DONE);
}
protected void setDisposeDone(boolean disposeDone) {
propertySupport.setPropertyBool(PROP_DISPOSE_DONE, disposeDone);
}
@Override
public <T extends IWidget> TreeVisitResult visit(Function<T, TreeVisitResult> visitor, Class<T> type) {
return visit(new WidgetVisitorTypeAdapter<>(visitor, type));
}
@Override
public <T extends IWidget> void visit(Consumer<T> visitor, Class<T> type) {
assertNotNull(visitor);
visit(new WidgetVisitorTypeAdapter<>(widget -> {
visitor.accept(widget);
return TreeVisitResult.CONTINUE;
}, type));
}
@Override
public void visit(Consumer<IWidget> visitor) {
assertNotNull(visitor);
visit(widget -> {
visitor.accept(widget);
return TreeVisitResult.CONTINUE;
});
}
@Override
public TreeVisitResult visit(Function<IWidget, TreeVisitResult> visitor) {
return visit(WidgetVisitorTypeAdapter.functionToVisitor(visitor));
}
@Override
public <T extends IWidget> TreeVisitResult visit(IDepthFirstTreeVisitor<T> visitor, Class<T> type) {
return visit(new WidgetVisitorTypeAdapter<>(visitor, type));
}
@Override
public TreeVisitResult visit(IDepthFirstTreeVisitor<IWidget> visitor) {
return visit(visitor, IWidget::getChildren);
}
@SuppressWarnings("unchecked")
protected <T extends IWidget> TreeVisitResult visit(IDepthFirstTreeVisitor<T> visitor, Function<T, Collection<? extends IWidget>> childrenSupplier, Class<T> type) {
IDepthFirstTreeVisitor<IWidget> widgetVisitorTypeAdapter = new WidgetVisitorTypeAdapter<>(visitor, type);
Function<IWidget, Collection<? extends IWidget>> cs = widget -> {
if (type.isAssignableFrom(widget.getClass())) {
childrenSupplier.apply((T) widget);
}
return widget.getChildren();
};
return visit(widgetVisitorTypeAdapter, cs);
}
protected TreeVisitResult visit(IDepthFirstTreeVisitor<IWidget> visitor, Function<IWidget, Collection<? extends IWidget>> childrenSupplier) {
return TreeTraversals.create(visitor, childrenSupplier).traverse(this);
}
@Override
@SuppressWarnings("unchecked")
public <T extends IWidget> TreeVisitResult visit(IBreadthFirstTreeVisitor<T> visitor, Class<T> type) {
return visit((widget, level, index) -> {
if (type.isAssignableFrom(widget.getClass())) {
return visitor.visit((T) widget, level, index);
}
return TreeVisitResult.CONTINUE;
});
}
@Override
public TreeVisitResult visit(IBreadthFirstTreeVisitor<IWidget> visitor) {
return TreeTraversals.create(visitor, IWidget::getChildren).traverse(this);
}
@Override
public List<? extends IWidget> getChildren() {
return CollectionUtility.emptyArrayList(); // by default a widget has no children
}
/**
* Configures whether this widget is enabled or not.
* <p>
* The value returned by this method is used for the default enabled dimension only.
* <p>
* Subclasses can override this method. Default is {@code true}.
*
* @return {@code true} if this widget is enabled and {@code false} otherwise.
* @see #setEnabled(boolean, String)
* @see #isEnabledIncludingParents()
*/
@Order(10)
@ConfigProperty(ConfigProperty.BOOLEAN)
protected boolean getConfiguredEnabled() {
return true;
}
/**
* Configures whether this widgets inherits the enabled state of its parent widgets.
* <p>
* For example a menu of a table field with {@link #isInheritAccessibility()}{@code == true} is automatically disabled
* if the table field is disabled.
* <p>
* Subclasses can override this method. Default is {@code true}.
*
* @return {@code true} if this widget is only enabled if all parent widgets are enabled as well. {@code false} if the
* enabled state of this widget is independent of the parent widgets.
* @see #setInheritAccessibility(boolean)
* @see #isEnabledIncludingParents()
*/
@Order(20)
@ConfigProperty(ConfigProperty.BOOLEAN)
protected boolean getConfiguredInheritAccessibility() {
return true;
}
/**
* Configures the css class(es) of this widget.
* <p>
* Subclasses can override this method. Default is {@code null}.
*
* @return a string containing one or more classes separated by space, or null if no class should be set.
*/
@Order(30)
@ConfigProperty(ConfigProperty.STRING)
protected String getConfiguredCssClass() {
return null;
}
@Override
public String getCssClass() {
return propertySupport.getPropertyString(PROP_CSS_CLASS);
}
@Override
public void setCssClass(String cssClass) {
propertySupport.setPropertyString(PROP_CSS_CLASS, cssClass);
}
@Override
public Object getProperty(String name) {
return propertySupport.getProperty(name);
}
@Override
public void setProperty(String name, Object value) {
propertySupport.setProperty(name, value);
}
@Override
public boolean hasProperty(String name) {
return propertySupport.hasProperty(name);
}
@Override
public boolean isLoading() {
return propertySupport.getPropertyBool(PROP_LOADING);
}
@Override
public void setLoading(boolean loading) {
propertySupport.setPropertyBool(PROP_LOADING, loading);
}
@Override
public boolean isEnabled() {
return NamedBitMaskHelper.allBitsSet(getEnabledByte());
}
@Override
public void setEnabled(boolean enabled) {
setEnabled(enabled, IDimensions.ENABLED);
}
@Override
public boolean isEnabledGranted() {
return isEnabled(IDimensions.ENABLED_GRANTED);
}
@Override
public void setEnabledGranted(boolean enabled) {
setEnabled(enabled, IDimensions.ENABLED_GRANTED);
}
@Override
public boolean isEnabled(String dimension) {
return ENABLED_BIT_HELPER.isBitSet(dimension, getEnabledByte());
}
@Override
public boolean isInheritAccessibility() {
return propertySupport.getPropertyBool(PROP_INHERIT_ACCESSIBILITY);
}
@Override
public void setInheritAccessibility(boolean b) {
propertySupport.setPropertyBool(PROP_INHERIT_ACCESSIBILITY, b);
}
@Override
public void setEnabledPermission(Permission permission) {
boolean b = true;
if (permission != null) {
b = ACCESS.check(permission);
}
setEnabledGranted(b);
}
@Override
public void setEnabledGranted(boolean enabled, boolean updateParents) {
setEnabledGranted(enabled, updateParents, false);
}
@Override
public void setEnabledGranted(boolean enabled, boolean updateParents, boolean updateChildren) {
setEnabled(enabled, updateParents, updateChildren, IDimensions.ENABLED_GRANTED);
}
@Override
public void setEnabled(final boolean enabled, final boolean updateParents) {
setEnabled(enabled, updateParents, false);
}
@Override
public void setEnabled(final boolean enabled, final boolean updateParents, final boolean updateChildren) {
setEnabled(enabled, updateParents, updateChildren, IDimensions.ENABLED);
}
@Override
public void setEnabled(boolean enabled, String dimension) {
setEnabled(enabled, false, dimension);
}
@Override
public void setEnabled(final boolean enabled, final boolean updateParents, final String dimension) {
setEnabled(enabled, updateParents, false, dimension);
}
@Override
public void setEnabled(final boolean enabled, final boolean updateParents, final boolean updateChildren, final String dimension) {
setEnabledByte(ENABLED_BIT_HELPER.changeBit(dimension, enabled, getEnabledByte()), true);
if (enabled && updateParents) {
// also enable all parents
visitParents(field -> {
field.setEnabled(true, dimension);
});
}
if (updateChildren) {
// propagate change to children
for (IWidget w : getChildren()) {
w.visit(field -> {
if (field instanceof IAction) {
if (field.isInheritAccessibility()) {
field.setEnabled(enabled, dimension);
}
}
else {
field.setEnabled(enabled, dimension);
}
});
}
}
}
@Override
public boolean isEnabled(Predicate<String> filter) {
return ENABLED_BIT_HELPER.allBitsEqual(getEnabledByte(), filter);
}
private byte getEnabledByte() {
return propertySupport.getPropertyByte(PROP_ENABLED_BYTE);
}
private boolean setEnabledByte(byte enabled, boolean fireListeners) {
boolean changed = propertySupport.setPropertyByte(PROP_ENABLED_BYTE, enabled);
if (changed && fireListeners) {
boolean newEnabled = isEnabled();
propertySupport.firePropertyChange(PROP_ENABLED, !newEnabled, newEnabled);
}
return changed;
}
@Override
public IWidget getParent() {
return (IWidget) propertySupport.getProperty(PROP_PARENT_WIDGET);
}
@Override
public boolean setParentInternal(IWidget w) {
return propertySupport.setProperty(PROP_PARENT_WIDGET, w);
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean isEnabledIncludingParents() {
if (!isEnabled()) {
return false;
}
if (!isInheritAccessibility()) {
return true;
}
AtomicReference<Boolean> result = new AtomicReference<>(true);
visitParents(w -> {
if (!w.isEnabled()) {
result.set(false);
return false; // disabled parent found: cancel search
}
if (!w.isInheritAccessibility()) {
return false; // no need to check any more parents
}
return true; // check next parent
});
return result.get();
}
@Override
public boolean visitParents(Consumer<IWidget> visitor) {
return visitParents(visitor, IWidget.class);
}
@Override
public <T extends IWidget> boolean visitParents(Consumer<T> visitor, Class<T> typeFilter) {
return visitParents(w -> {
visitor.accept(w);
return true;
}, typeFilter);
}
@Override
public boolean visitParents(Predicate<IWidget> visitor) {
return visitParents(visitor, IWidget.class);
}
@Override
@SuppressWarnings("unchecked")
public <T extends IWidget> boolean visitParents(Predicate<T> visitor, Class<T> typeFilter) {
IWidget cur = this;
while ((cur = cur.getParent()) != null) {
if (typeFilter.isInstance(cur) && !visitor.test((T) cur)) {
return false;
}
}
return true;
}
@Override
public <T extends IWidget> T getParentOfType(Class<T> type) {
AtomicReference<T> result = new AtomicReference<>();
visitParents(composite -> !result.compareAndSet(null, composite), type);
return result.get();
}
@Override
public String classId() {
String simpleClassId = ConfigurationUtility.getAnnotatedClassIdWithFallback(getClass());
IWidget parent = getParent();
if (parent != null) {
return simpleClassId + ID_CONCAT_SYMBOL + parent.classId();
}
return simpleClassId;
}
@Override
public void scrollToTop() {
fireWidgetEvent(new WidgetEvent(this, WidgetEvent.TYPE_SCROLL_TO_TOP));
}
protected void fireWidgetEvent(WidgetEvent event) {
if (m_listeners != null) {
m_listeners.forEach(listener -> listener.widgetChanged(event));
}
}
@Override
public void addWidgetListener(WidgetListener listener) {
if (m_listeners == null) {
m_listeners = new ArrayList<>();
}
m_listeners.add(listener);
}
@Override
public void removeWidgetListener(WidgetListener listener) {
if (m_listeners != null) {
m_listeners.remove(listener);
}
}
}