blob: 1cc582336c244270a28deda0888887c8a69ace65 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 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.form.fields;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.eclipse.scout.commons.CollectionUtility;
import org.eclipse.scout.commons.ConfigurationUtility;
import org.eclipse.scout.commons.annotations.ClassId;
import org.eclipse.scout.commons.annotations.InjectFieldTo;
import org.eclipse.scout.commons.annotations.Replace;
import org.eclipse.scout.commons.exception.ProcessingException;
import org.eclipse.scout.commons.holders.Holder;
import org.eclipse.scout.rt.client.ui.form.AbstractForm;
import org.eclipse.scout.rt.client.ui.form.DefaultFormFieldInjection;
import org.eclipse.scout.rt.client.ui.form.FormFieldInjectionThreadLocal;
import org.eclipse.scout.rt.client.ui.form.IForm;
import org.eclipse.scout.rt.client.ui.form.IFormFieldVisitor;
import org.eclipse.scout.rt.client.ui.form.fields.groupbox.AbstractGroupBox;
import org.eclipse.scout.rt.client.ui.form.fields.wrappedform.IWrappedFormField;
import org.eclipse.scout.rt.shared.services.common.exceptionhandler.IExceptionHandlerService;
import org.eclipse.scout.service.SERVICES;
@ClassId("4a641cd4-801f-45d2-9f08-5798e20b03c4")
public abstract class AbstractCompositeField extends AbstractFormField implements ICompositeField {
private List<IFormField> m_fields;
private Map<Class<?>, Class<? extends IFormField>> m_formFieldReplacements;
public AbstractCompositeField() {
this(true);
}
public AbstractCompositeField(boolean callInitializer) {
super(callInitializer);
}
protected List<Class<? extends IFormField>> getConfiguredFields() {
Class[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass());
return ConfigurationUtility.sortFilteredClassesByOrderAnnotation(Arrays.asList(dca), IFormField.class);
}
/**
* Full override: disable
*/
@Override
protected boolean execCalculateVisible() {
return true;
}
@Override
protected void initConfig() {
/*
* call first super initConfig to ensure all properties are applied to the field only.
* E.g. setEnabled(getConfiguredEnabled()) would enable/disable all children when called
* after field creation. -> all fields would have the enabled state of the MainBox.
*/
m_fields = CollectionUtility.emptyArrayList();
super.initConfig();
// prepare injected fields
DefaultFormFieldInjection injectedFields = null;
List<Class<? extends IFormField>> configuredFields = new ArrayList<Class<? extends IFormField>>();
for (Class<? extends IFormField> clazz : getConfiguredFields()) {
if (ConfigurationUtility.isInjectFieldAnnotationPresent(clazz)) {
if (injectedFields == null) {
injectedFields = new DefaultFormFieldInjection(this);
}
injectedFields.addField(clazz);
}
else {
configuredFields.add(clazz);
}
}
List<IFormField> fieldList = new ArrayList<IFormField>();
try {
if (injectedFields != null) {
FormFieldInjectionThreadLocal.push(injectedFields);
}
//
filterFieldsInternal(configuredFields);
for (Class<? extends IFormField> clazz : configuredFields) {
try {
IFormField f = ConfigurationUtility.newInnerInstance(this, clazz);
fieldList.add(f);
}// end try
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("error creating instance of class '" + clazz.getName() + "'.", t));
}
}
injectFieldsInternal(fieldList);
}
finally {
if (injectedFields != null) {
m_formFieldReplacements = injectedFields.getReplacementMapping();
FormFieldInjectionThreadLocal.pop(injectedFields);
}
}
for (IFormField f : fieldList) {
f.setParentFieldInternal(this);
}
m_fields = fieldList;
// attach a proxy controller to each child field in the group for: visible, saveNeeded
for (IFormField f : m_fields) {
f.addPropertyChangeListener(new P_FieldPropertyChangeListener());
}
handleFieldVisibilityChanged();
}
/**
* Filter list of configured fields before they are instantiated.
* <p/>
* The default implementation removes fields replaced by another field annotated with {@link Replace}.
*
* @param fieldList
* live and mutable list of configured field classes (i.e. yet not instantiated)
* @since 3.8.2
*/
protected void filterFieldsInternal(List<Class<? extends IFormField>> fieldList) {
FormFieldInjectionThreadLocal.filterFields(this, fieldList);
}
/**
* Override this internal method only in order to make use of dynamic fields<br>
* Used to manage field list and add/remove fields (see {@link AbstractGroupBox} with wizard buttons)
* <p>
* The default implementation checks for {@link InjectFieldTo} annotations in the enclosing (runtime) classes.
*
* @param fieldList
* live and mutable list of configured fields, not yet initialized
* and added to composite field
*/
protected void injectFieldsInternal(List<IFormField> fieldList) {
FormFieldInjectionThreadLocal.injectFields(this, fieldList);
}
@Override
public void setFormInternal(IForm form) {
super.setFormInternal(form);
for (IFormField field : m_fields) {
field.setFormInternal(form);
}
}
@Override
public int getFieldIndex(IFormField f) {
return m_fields.indexOf(f);
}
@Override
public int getFieldCount() {
return m_fields.size();
}
@Override
public IFormField getFieldById(final String id) {
final Holder<IFormField> found = new Holder<IFormField>(IFormField.class);
IFormFieldVisitor v = new IFormFieldVisitor() {
@Override
public boolean visitField(IFormField field, int level, int fieldIndex) {
if (field.getFieldId().equals(id)) {
found.setValue(field);
}
return found.getValue() == null;
}
};
visitFields(v, 0);
return found.getValue();
}
@Override
public <T extends IFormField> T getFieldById(final String id, final Class<T> type) {
final Holder<T> found = new Holder<T>(type);
IFormFieldVisitor v = new IFormFieldVisitor() {
@Override
@SuppressWarnings("unchecked")
public boolean visitField(IFormField field, int level, int fieldIndex) {
if (type.isAssignableFrom(field.getClass()) && field.getFieldId().equals(id)) {
found.setValue((T) field);
}
return found.getValue() == null;
}
};
visitFields(v, 0);
return found.getValue();
}
@Override
public <T extends IFormField> T getFieldByClass(Class<T> c) {
final Class<? extends T> formFieldClass = getReplacingFieldClass(c);
final Holder<T> found = new Holder<T>(c);
IFormFieldVisitor v = new IFormFieldVisitor() {
@Override
@SuppressWarnings("unchecked")
public boolean visitField(IFormField field, int level, int fieldIndex) {
if (field.getClass() == formFieldClass) {
found.setValue((T) field);
}
return found.getValue() == null;
}
};
visitFields(v, 0);
return found.getValue();
}
/**
* Checks whether the form field with the given class has been replaced by another form field. If so, the replacing
* form field's class is returned. Otherwise the given class itself.
*
* @param c
* @return Returns the possibly available replacing field class for the given class.
* @see Replace
* @since 3.8.2
*/
private <T extends IFormField> Class<? extends T> getReplacingFieldClass(Class<T> c) {
// 1. check local replacements
if (m_formFieldReplacements != null) {
@SuppressWarnings("unchecked")
Class<? extends T> replacementFieldClass = (Class<? extends T>) m_formFieldReplacements.get(c);
if (replacementFieldClass != null) {
return replacementFieldClass;
}
}
// 2. check global replacements
IForm form = getForm();
if (form instanceof AbstractForm) {
Map<Class<?>, Class<? extends IFormField>> mapping = ((AbstractForm) form).getFormFieldReplacementsInternal();
if (mapping != null) {
@SuppressWarnings("unchecked")
Class<? extends T> replacementFieldClass = (Class<? extends T>) mapping.get(c);
if (replacementFieldClass != null) {
return replacementFieldClass;
}
}
}
// 3. field is not replaced
return c;
}
@Override
public List<IFormField> getFields() {
return CollectionUtility.arrayList(m_fields);
}
@Override
public boolean visitFields(IFormFieldVisitor visitor, int startLevel) {
// myself
if (!visitor.visitField(this, startLevel, 0)) {
return false;
}
// children
int index = 0;
for (IFormField field : m_fields) {
if (field instanceof ICompositeField) {
if (!((ICompositeField) field).visitFields(visitor, startLevel + 1)) {
return false;
}
}
else if (field instanceof IWrappedFormField) {
if (!((IWrappedFormField) field).visitFields(visitor, startLevel + 1)) {
return false;
}
}
else {
if (!visitor.visitField(field, startLevel, index)) {
return false;
}
}
index++;
}
return true;
}
@Override
protected boolean execIsSaveNeeded() throws ProcessingException {
for (IFormField f : m_fields) {
if (f.isSaveNeeded()) {
return true;
}
}
return false;
}
@Override
protected void execMarkSaved() throws ProcessingException {
super.execMarkSaved();
for (IFormField f : m_fields) {
f.markSaved();
}
}
@Override
protected boolean execIsEmpty() throws ProcessingException {
for (IFormField f : m_fields) {
if (!f.isEmpty()) {
return false;
}
}
return true;
}
/**
* broadcast this change to all children
*/
@Override
public void setMandatory(boolean b) {
// recursively down all children
for (IFormField f : m_fields) {
f.setMandatory(b);
}
}
/**
* when granting of enabled property changes, broadcast and set this property
* on all children that have no permission set
*/
@Override
public void setEnabledGranted(boolean b) {
super.setEnabledGranted(b);
for (IFormField f : getFields()) {
if (f.getEnabledPermission() == null) {
f.setEnabledGranted(b);
}
}
}
/**
* when granting of visible property changes, do not broadcast and set this
* property on all children that have no permission set
*/
@Override
public void setVisibleGranted(boolean b) {
super.setVisibleGranted(b);
}
/**
* if initialized broadcast this change to all children.
*/
@Override
public void setEnabled(boolean b) {
super.setEnabled(b);
// recursively down all children only if initialized.
if (isInitialized()) {
for (IFormField f : m_fields) {
f.setEnabled(b);
}
}
}
// box is only visible when it has at least one visible item
protected void handleFieldVisibilityChanged() {
int visCount = 0;
for (IFormField field : m_fields) {
if (field.isVisible()) {
visCount++;
}
}
setVisibleFieldCount(visCount);
calculateVisibleInternal();
}
@Override
public void rebuildFieldGrid() {
}
private void setVisibleFieldCount(int n) {
propertySupport.setPropertyInt(PROP_VISIBLE_FIELD_COUNT, n);
}
protected int getVisibleFieldCount() {
return propertySupport.getPropertyInt(PROP_VISIBLE_FIELD_COUNT);
}
/**
* Implementation of PropertyChangeListener Proxy on all attached fields (not
* groups)
*/
private class P_FieldPropertyChangeListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent e) {
if (e.getPropertyName().equals(IFormField.PROP_VISIBLE)) {
// fire group box visibility
handleFieldVisibilityChanged();
}
else if (e.getPropertyName().equals(IFormField.PROP_SAVE_NEEDED)) {
checkSaveNeeded();
}
else if (e.getPropertyName().equals(IFormField.PROP_EMPTY)) {
checkEmpty();
}
}
}// end private class
}