org.nuclos.client.ui.collect.component.AbstractCollectableComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.nuclos.client.ui.collect.component.AbstractCollectableComponent.java

Source

//Copyright (C) 2010  Novabit Informationssysteme GmbH
//
//This file is part of Nuclos.
//
//Nuclos is free software: you can redistribute it and/or modify
//it under the terms of the GNU Affero General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//Nuclos is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU Affero General Public License for more details.
//
//You should have received a copy of the GNU Affero General Public License
//along with Nuclos.  If not, see <http://www.gnu.org/licenses/>.
package org.nuclos.client.ui.collect.component;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.prefs.Preferences;

import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;

import org.apache.commons.lang.NullArgumentException;
import org.apache.commons.lang.ObjectUtils;
import org.apache.log4j.Logger;
import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.BufferedLayerUI;
import org.jdesktop.jxlayer.plaf.effect.BufferedImageOpEffect;
import org.jdesktop.swingx.renderer.DefaultListRenderer;
import org.nuclos.api.context.ScriptContext;
import org.nuclos.client.common.ClientParameterProvider;
import org.nuclos.client.common.MetaDataClientProvider;
import org.nuclos.client.common.Utils;
import org.nuclos.client.common.security.SecurityCache;
import org.nuclos.client.genericobject.GenericObjectClientUtils;
import org.nuclos.client.genericobject.Modules;
import org.nuclos.client.masterdata.MasterDataCache;
import org.nuclos.client.masterdata.MasterDataLayoutHelper;
import org.nuclos.client.scripting.ScriptEvaluator;
import org.nuclos.client.scripting.context.CollectableScriptContext;
import org.nuclos.client.synthetica.NuclosThemeSettings;
import org.nuclos.client.ui.ColorProvider;
import org.nuclos.client.ui.DateChooser;
import org.nuclos.client.ui.ListOfValues;
import org.nuclos.client.ui.ResourceIdMapper;
import org.nuclos.client.ui.TableRowMouseOverSupport;
import org.nuclos.client.ui.ToolTipTextProvider;
import org.nuclos.client.ui.collect.FixedColumnRowHeader;
import org.nuclos.client.ui.collect.FixedColumnRowHeader.FixedRowIndicatorTableModel;
import org.nuclos.client.ui.collect.SubForm;
import org.nuclos.client.ui.collect.SubForm.Column;
import org.nuclos.client.ui.collect.SubForm.SubFormTable;
import org.nuclos.client.ui.collect.component.custom.FileChooserComponent;
import org.nuclos.client.ui.collect.component.model.CollectableComponentModel;
import org.nuclos.client.ui.collect.component.model.CollectableComponentModelEvent;
import org.nuclos.client.ui.collect.component.model.CollectableComponentModelListener;
import org.nuclos.client.ui.collect.component.model.DetailsComponentModel;
import org.nuclos.client.ui.collect.component.model.DetailsComponentModelEvent;
import org.nuclos.client.ui.collect.component.model.SearchComponentModel;
import org.nuclos.client.ui.collect.component.model.SearchComponentModelEvent;
import org.nuclos.client.ui.collect.model.SortableCollectableTableModel;
import org.nuclos.client.ui.gc.ListenerUtil;
import org.nuclos.client.ui.labeled.LabeledComboBox;
import org.nuclos.client.ui.popupmenu.DefaultJPopupMenuListener;
import org.nuclos.client.ui.popupmenu.JPopupMenuFactory;
import org.nuclos.client.ui.popupmenu.JPopupMenuListener;
import org.nuclos.client.wizard.model.DataTyp;
import org.nuclos.common.NuclosEntity;
import org.nuclos.common.NuclosFatalException;
import org.nuclos.common.NuclosScript;
import org.nuclos.common.ParameterProvider;
import org.nuclos.common.collect.collectable.Collectable;
import org.nuclos.common.collect.collectable.CollectableEntity;
import org.nuclos.common.collect.collectable.CollectableEntityField;
import org.nuclos.common.collect.collectable.CollectableField;
import org.nuclos.common.collect.collectable.CollectableUtils;
import org.nuclos.common.collect.collectable.access.CefSecurityAgent;
import org.nuclos.common.collect.collectable.searchcondition.AtomicCollectableSearchCondition;
import org.nuclos.common.collect.collectable.searchcondition.CollectableComparison;
import org.nuclos.common.collect.collectable.searchcondition.CollectableComparisonWithOtherField;
import org.nuclos.common.collect.collectable.searchcondition.CollectableComparisonWithParameter;
import org.nuclos.common.collect.collectable.searchcondition.CollectableComparisonWithParameter.ComparisonParameter;
import org.nuclos.common.collect.collectable.searchcondition.CollectableLikeCondition;
import org.nuclos.common.collect.collectable.searchcondition.CollectableSearchCondition;
import org.nuclos.common.collect.collectable.searchcondition.ComparisonOperator;
import org.nuclos.common.collect.collectable.searchcondition.ToHumanReadablePresentationVisitor;
import org.nuclos.common.collect.exception.CollectableFieldFormatException;
import org.nuclos.common.collection.CollectionUtils;
import org.nuclos.common.collection.Predicate;
import org.nuclos.common.dal.vo.EntityMetaDataVO;
import org.nuclos.common.expressions.ExpressionParser;
import org.nuclos.common2.LangUtils;
import org.nuclos.common2.SpringLocaleDelegate;
import org.nuclos.common2.StringUtils;
import org.nuclos.common2.exception.CommonFatalException;
import org.nuclos.common2.exception.CommonFinderException;
import org.nuclos.common2.exception.CommonPermissionException;
import org.nuclos.server.masterdata.valueobject.MasterDataVO;

import com.jhlabs.image.BoxBlurFilter;

/**
 * Component that displays and lets the user edit a <code>Collectable</code>.
 * Contains all properties that are common to all <code>CollectableComponents</code>.
 * Some properties are ignored by some components.
 * <br>
 * <br>Created by Novabit Informationssysteme GmbH
 * <br>Please visit <a href="http://www.novabit.de">www.novabit.de</a>
 *
 * @author   <a href="mailto:Christoph.Radig@novabit.de">Christoph.Radig</a>
 * @version 01.00.00
 */
public abstract class AbstractCollectableComponent
        implements CollectableComponent, CollectableComponentModelListener, JPopupMenuFactory, ToolTipTextProvider {

    private static final Logger LOG = Logger.getLogger(AbstractCollectableComponent.class);

    protected static final String TEXT_NOCHANGE = SpringLocaleDelegate.getInstance()
            .getMessage("AbstractCollectableComponent.13", "Keine \u00c4nderung vornehmen");
    protected static final String TEXT_CLEAR = SpringLocaleDelegate.getInstance()
            .getMessage("AbstractCollectableComponent.11", "Feld leeren");
    protected static final String TEXT_SHOWDETAILS = SpringLocaleDelegate.getInstance()
            .getMessage("AbstractCollectableComponent.22", "Details anzeigen...");
    protected static final String TEXT_NEW = SpringLocaleDelegate.getInstance()
            .getMessage("AbstractCollectableComponent.context.new", "Neu...");
    protected static final String TEXT_REFRESH = SpringLocaleDelegate.getInstance()
            .getMessage("AbstractCollectableComponent.context.refresh", "Aktualisieren...");

    /**
     * the color to be used as background for multi editable components that don't share a common value.
     */
    public static Color colorCommonValues = Utils
            .translateColorFromParameter(ParameterProvider.KEY_HISTORICAL_STATE_CHANGED_COLOR);//new Color(246,229,255);

    //

    private final JComponent comp;
    private final CollectableEntityField clctef;
    private CollectableComponentModel clctcompmodel;
    private final boolean bSearchable;
    private boolean bViewLocked;

    private DataTyp dataTyp;
    private String sNextFocusComponent;

    private ReferencingListener reflistener;

    private Map<String, Object> mpProperties;

    /**
     * the comparison operator, if any, that can be set by the user.
     */
    private ComparisonOperator compop;

    /**
     * the other field, used for comparison with other field.
     */
    private CollectableEntityField clctefOtherField;

    /**
     * the parameter, used for comparison.
     */
    private ComparisonParameter compParameter;

    /**
     * used to display the possible entity fields for comparison with other field.
     */
    private CollectableEntity clcte;

    private SpringLocaleDelegate localeDelegate = SpringLocaleDelegate.getInstance();

    private boolean enabled = true;

    private boolean readOnly = false;

    protected boolean dynamicallyEnabled = true;

    private NuclosScript enabledScript;

    //   /**
    //    * @param clctef
    //    * @param comp
    //    * @postcondition isDetailsComponent()
    //    */
    //   protected AbstractCollectableComponent(CollectableEntityField clctef, JComponent comp) {
    //      this(clctef, comp, false);
    //   }

    /**
     * @param clctef
     * @param comp
     * @param bSearchable
     * @precondition clctef != null
     * @precondition comp != null
     * @postcondition isSearchComponent() == bSearchable
     */
    protected AbstractCollectableComponent(CollectableEntityField clctef, JComponent comp, boolean bSearchable) {
        if (clctef == null) {
            throw new NullArgumentException("clctef");
        }
        if (comp == null) {
            throw new NullArgumentException("comp");
        }
        this.clctef = clctef;
        this.comp = comp;

        try {
            dataTyp = getDataTyp(clctef.getJavaClass().getName(), clctef.getDefaultComponentType(),
                    clctef.getMaxLength(), clctef.getPrecision(), clctef.getFormatInput(),
                    clctef.getFormatOutput());
        } catch (CommonPermissionException e) {
            throw new NuclosFatalException(e);
        } catch (CommonFinderException e) {
            throw new NuclosFatalException(e);
        }

        // set the name of the JComponent so it can be identified by GUI testing tools:
        comp.setName(getFieldName());
        this.bSearchable = bSearchable;
        setModel(CollectableComponentModel.newCollectableComponentModel(clctef, bSearchable));

        if (isSearchComponent() && hasComparisonOperator()) {
            setComparisonOperator(ComparisonOperator.NONE);
        }

        setupJPopupMenuListener(newJPopupMenuListener());

        assert isSearchComponent() == bSearchable;
    }

    void setSpringLocaleDelegate(SpringLocaleDelegate cld) {
        this.localeDelegate = cld;
    }

    protected SpringLocaleDelegate getSpringLocaleDelegate() {
        return localeDelegate;
    }

    public static synchronized void setCommonValuesBackgroundColor(Color color) {
        AbstractCollectableComponent.colorCommonValues = color;
    }

    @Override
    public CollectableEntityField getEntityField() {
        return clctef;
    }

    @Override
    public final CollectableComponentModel getModel() {
        return clctcompmodel;
    }

    @Override
    public SearchComponentModel getSearchModel() {
        if (!isSearchComponent()) {
            throw new IllegalStateException("searchComponent");
        }
        return (SearchComponentModel) getModel();
    }

    @Override
    public DetailsComponentModel getDetailsModel() {
        if (!isDetailsComponent()) {
            throw new IllegalStateException("detailsComponent");
        }
        return (DetailsComponentModel) getModel();
    }

    /**
     * @deprecated Use constructor to initialize the model.
     *       The model itself shouldn't be changed after construction of the view.
     */
    @Override
    public void setModel(CollectableComponentModel clctcompmodel) {
        if (clctcompmodel.isSearchModel() != isSearchComponent()) {
            throw new CommonFatalException(getSpringLocaleDelegate().getMessage("AbstractCollectableComponent.14",
                    "Model und View stimmen in der Eigenschaft \"searchable\" nicht \u00fcberein."));
        }
        if (getModel() != null) {
            getModel().removeCollectableComponentModelListener(this);
        }
        this.clctcompmodel = clctcompmodel;
        // this.clctcompmodel.addCollectableComponentModelListener(this);
        ListenerUtil.registerCollectableComponentModelListener(this.clctcompmodel, null, this);
    }

    @Override
    public boolean isSearchComponent() {
        return bSearchable;
    }

    @Override
    public boolean isDetailsComponent() {
        return !isSearchComponent();
    }

    @Override
    public boolean isMultiEditable() {
        return isDetailsComponent() && getDetailsModel().isMultiEditable();
    }

    @Override
    public void setVisible(boolean bVisible) {
        getJComponent().setVisible(bVisible);
    }

    @Override
    public String getFieldName() {
        return clctef.getName();
    }

    @Override
    public JComponent getJComponent() {
        return comp;
    }

    /**
     * @return default implementation: the whole JComponent is the control component.
     */
    @Override
    public JComponent getControlComponent() {
        return getJComponent();
    }

    @Override
    public JComponent getFocusableComponent() {
        return getControlComponent();
    }

    /**
     * locks the view. The view will not be updated (by the model) while it is locked.
     * @param bViewLocked
     */
    protected void setViewLocked(boolean bViewLocked) {
        this.bViewLocked = bViewLocked;
    }

    /**
     * @return Ist updating the view prohibited?
     */
    boolean isViewLocked() {
        return bViewLocked;
    }

    /**
     * runs the given runnable in locked mode. The view will not be updated (by the model) during this operation.
     * @param runnable
     */
    protected void runLocked(Runnable runnable) {
        if (!isViewLocked()) {
            try {
                setViewLocked(true);

                runnable.run();
            } finally {
                setViewLocked(false);
            }
        }
        // It seems to be quiet common to <em>not</em> execute the Runnable...
        /*
        else if (LOG.isDebugEnabled()) {
           LOG.debug("runLocked(" + runnable + ") not executed: view is already locked: " +
        this + " field: " + getFieldName() + " -> " + getModel().getField().getValue());
        }
         */
    }

    protected final CollectableEntityField getComparisonOtherField() {
        return clctefOtherField;
    }

    protected final ComparisonParameter getComparisonParameter() {
        return compParameter;
    }

    protected final void resetWithComparison() {
        this.clctefOtherField = null;
        this.compParameter = null;
    }

    protected final void setWithComparison(CollectableEntityField clctefOtherField) {
        this.clctefOtherField = clctefOtherField;
        this.compParameter = null;
    }

    protected final void setWithComparison(ComparisonParameter compParameter) {
        this.clctefOtherField = null;
        this.compParameter = compParameter;
    }

    protected static interface ExceptionalRunnable {
        void run() throws CollectableFieldFormatException;
    }

    /**
     * runs the given runnable in locked mode. The view will not be updated (by the model) during this operation.
     * @param runnable
     */
    protected void runLocked(ExceptionalRunnable runnable) throws CollectableFieldFormatException {
        if (!isViewLocked()) {
            try {
                setViewLocked(true);

                runnable.run();
            } finally {
                setViewLocked(false);
            }
        }
    }

    /**
     * @return Is the model consistent with the view?
     */
    @Override
    public boolean isConsistent() {
        try {
            if (isSearchComponent()) {
                final CollectableSearchCondition condModel = getSearchModel().getSearchCondition();
                final CollectableSearchCondition condView = getSearchConditionFromView();
                return LangUtils.equals(condModel, condView);
            } else {
                final CollectableField clctfModel = getModel().getField();
                final CollectableField clctfView = getFieldFromView();
                return clctfView.equals(clctfModel);
            }
        } catch (CollectableFieldFormatException ex) {
            return false;
        }
    }

    /**
     * tries to make the model consistent with the view, if this isn't the case already.
     * @throws CollectableFieldFormatException if the model can't be made consistent.
     * @postcondition isConsistent()
     */
    @Override
    public void makeConsistent() throws CollectableFieldFormatException {
        // NUCLOSINT-839: If the view is locked and isConsistent() is false,
        // viewToModel() does nothing. This seems to be a problem for search
        // ComboBoxes. As a quick fix, we simply return, doing nothing as before
        // but avoiding the assert. (Thomas Pasch)
        //
        if (isSearchComponent() && isViewLocked())
            return;

        if (!isConsistent()) {
            viewToModel();
        }
        /*
        if (!isConsistent()) {
           final CollectableSearchCondition condModel = getSearchModel().getSearchCondition();
           final CollectableSearchCondition condView = getSearchConditionFromView();
           LOG.warn("makeConsistent failed on " + this + " field: " + getFieldName() + " -> " + getModel().getField().getValue());
        }
         */
        //assert isConsistent();
    }

    /**
     * updates the view with the value in the model. This method must not update the model again!
     * Note that <code>isConsistent()</code> is not a postcondition here, because this cannot be guaranteed
     * in general. For example, one may set the <code>collectableField</code> for a <code>CollectableComboBox</code>,
     * that doesn't contain this value in its list of possible values. In those cases, the view is not
     * consistent with the model.
     * @todo adjust comment and implementation
     * @precondition isViewLocked()
     * @todo add precondition isDetailsComponent()?
     * @todo add postcondition isConsistent()
     */
    protected final void modelToView() {
        if (!isViewLocked()) {
            throw new IllegalStateException("View must be locked.");
        }
        updateView(getModel().getField());
    }

    /**
     * sets the view according to the given value.
     * @param clctfValue
     */
    protected abstract void updateView(CollectableField clctfValue);

    /**
     * sets the given condition in this component, using text comp as this component's text component.
     * @param atomiccond
     * @param textcomp
     * @precondition isViewLocked()
     * @precondition canDisplay(atomiccond)
     */
    protected final void modelToView(AtomicCollectableSearchCondition atomiccond, JTextComponent textcomp) {
        if (!canDisplay(atomiccond)) {
            throw new IllegalArgumentException("Condition cannot be displayed: " + LangUtils.toString(atomiccond));
        }
        final ComparisonOperator compop = (atomiccond == null) ? ComparisonOperator.NONE
                : atomiccond.getComparisonOperator();

        setComparisonOperator(compop);

        if (atomiccond instanceof CollectableComparisonWithOtherField) {
            assert canDisplayComparisonWithOtherField();
            final CollectableComparisonWithOtherField comparisonwf = (CollectableComparisonWithOtherField) atomiccond;
            setWithComparison(comparisonwf.getOtherField());
        } else if (atomiccond instanceof CollectableComparisonWithParameter) {
            assert canDisplayComparisonWithOtherField();
            final CollectableComparisonWithParameter comparisonwp = (CollectableComparisonWithParameter) atomiccond;
            setWithComparison(comparisonwp.getParameter());
        } else {
            resetWithComparison();
        }

        final String sText;
        if (compop.getOperandCount() < 2) {
            sText = null;
        } else {
            assert atomiccond != null;
            sText = atomiccond.getComparandAsString();
        }

        assert isViewLocked();
        textcomp.setText(sText);
    }

    /**
     * updates the model with the value in the view. If the value in the view is invalid,
     * the model is cleared, and a CollectableFieldFormatException is thrown.
     * The view is locked during the execution of this method, to prevent recursive updates
     * between view and model. If the view is locked already, this method does nothing.
     * @throws CollectableFieldFormatException if the value in the view is invalid.
     * @todo try to make this final.
     */
    protected void viewToModel() throws CollectableFieldFormatException {
        runLocked(new ExceptionalRunnable() {
            @Override
            public void run() throws CollectableFieldFormatException {
                if (isSearchComponent()) {
                    getSearchModel().setSearchCondition(getSearchConditionFromView());
                } else {
                    try {
                        viewToModel(getFieldFromView());
                    } catch (CollectableFieldFormatException ex) {
                        /** @todo Is it right to clear the model here? However note that it is required that the model
                         * fires a change event, so the CollectController is notified in Details mode and switches the CollectState
                         * as soon as the user starts typing eg. in a DateChooser field. */
                        // The value in the view is not valid. At least, the model is cleared:
                        getModel().clear();
                        throw ex;
                    }
                }
            }
        });
    }

    /**
     * updates the model with the given value.
     * @param clctfView
     * @todo add precondition !isSearchComponent(), eliminate parameter and write a second method viewToModel(CollectableSearchCondition) for the isSearchComponent() case.
     */
    protected final void viewToModel(CollectableField clctfView) {
        /** @todo check if it is right to do that here: */
        /** @todo No, it is not! The parameter is ignored in case of searchable. */
        // 22.01.04:
        if (isSearchComponent()) {
            updateSearchConditionInModel();
        }
        // :22.01.04
        else {
            if (!getModel().getField().equals(clctfView, false)) //@see NUCLOS-592
                getModel().setField(clctfView);
            // We need to adjust the appearance of the view here, as viewToModel is not (and must not be)
            // called from here:
            adjustAppearance();
        }
    }

    /**
     * adjusts the appearance of the view reflecting the model. Only those changes to the view are allowed that don't
     * cause the value (as defined in <code>getField()</code>) of the component to be changed again,
     * that is: don't trigger another <code>CollectableComponentEvent</code>.
     * Only the view's appearance (like foreground/background colors, borders etc.) may be adjusted here.
     */
    protected void adjustAppearance() {
        //      adjustBackground();
        getControlComponent().repaint();
    }

    /**
     * @deprecated
     */
    @Deprecated
    protected void adjustBackground() {
        //      getControlComponent().setBackground(getBackgroundColor());
    }

    /**
     * A convenient method for '<code>makeConsistent(); getModel().getField()</code>', i.e.
     * <em>different</em> from '<code>getModel().getField()</code>'.
     *
     * @return CollectableField
     * @throws CollectableFieldFormatException
     */
    @Override
    public CollectableField getField() throws CollectableFieldFormatException {
        makeConsistent();

        return getModel().getField();
    }

    @Override
    public final void setField(CollectableField clctfValue) {
        getModel().setField(clctfValue);
    }

    @Override
    public final CollectableSearchCondition getSearchCondition() throws CollectableFieldFormatException {
        if (!isSearchComponent()) {
            throw new IllegalStateException("searchComponent");
        }
        makeConsistent();

        return getSearchModel().getSearchCondition();
    }

    /**
     * @param cond
     * @postcondition LangUtils.equals(getSearchModel().getSearchCondition(), cond)
     * @postcondition canDisplay(cond) --> isConsistent()
     * @todo pull down to CollectableComponent?
     */
    public final void setSearchCondition(CollectableSearchCondition cond) {
        if (!isSearchComponent()) {
            throw new IllegalStateException("searchComponent");
        }
        getSearchModel().setSearchCondition(cond);

        assert LangUtils.equals(getSearchModel().getSearchCondition(), cond);
        // assert !canDisplay(cond) || isConsistent();
    }

    @Override
    public boolean canDisplay(CollectableSearchCondition cond) {
        return !(cond instanceof CollectableComparisonWithOtherField
                || cond instanceof CollectableComparisonWithParameter) || canDisplayComparisonWithOtherField();
    }

    /**
     * @return Can this component display a {@link CollectableComparisonWithOtherField}?
     * @precondition isSearchComponent()
     * @todo move to CollectableComponent interface?
     */
    public boolean canDisplayComparisonWithOtherField() {
        if (dataTyp != null) {
            if (dataTyp.getDatabaseTyp() != null
                    && (dataTyp.getDatabaseTyp().equals("blob") || dataTyp.getDatabaseTyp().equals("clob")))
                return false;
        }
        return clcte != null;
    }

    /**
     * sets the collectable entity that getEntityField() belongs to.
     * This is needed to support <code>ComparisonWithOtherField</code>.
     * @param clcte May be <code>null</code>.
     * @precondition clcte != null --> clcte.getFieldNames().contains(getEntityField().getName())
     */
    @Override
    public void setCollectableEntity(CollectableEntity clcte) {
        if (!(clcte == null || clcte.getEntityField(getEntityField().getName()) != null)) {
            throw new IllegalArgumentException("clcte");
        }
        this.clcte = clcte;
    }

    /**
     * sets the given <code>CollectableField</code> or <code>CollectableSearchCondition</code>.
     * @param oValue
     * @precondition isSearchComponent() --> (oValue instanceof CollectableField)
     * @precondition !isSearchComponent() --> (oValue instanceof CollectableSearchCondition)
     */
    protected void setObjectValue(Object oValue) {
        if (isSearchComponent()) {
            setSearchCondition((CollectableSearchCondition) oValue);
        } else {
            setField((CollectableField) oValue);
        }
    }

    /**
     * clears the field.
     * @postcondition getField().isNull()
     * @postcondition isSearchComponent() -> (getSearchCondition() == null)
     */
    @Override
    public final void clear() {
        getModel().clear();
    }

    /**
     * @param bEnabled
     */
    @Override
    public final void setEnabled(boolean bEnabled) {
        this.enabled = bEnabled;
        if (isSearchComponent()) {
            if (dataTyp != null) {
                if (dataTyp.getDatabaseTyp() != null
                        && (dataTyp.getDatabaseTyp().equals("blob") || dataTyp.getDatabaseTyp().equals("clob"))) {
                    enabled = false;
                    getControlComponent().setEnabled(false);
                }
            }
        }
        setEnabledState(enabled && !readOnly && dynamicallyEnabled);
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        this.readOnly = readOnly;
        setEnabledState(enabled && !readOnly && dynamicallyEnabled);
    }

    @Override
    public boolean isReadOnly() {
        return this.readOnly;
    }

    @Override
    public boolean isEnabled() {
        return getJComponent().isEnabled();
    }

    @Override
    public void setEnabledScript(NuclosScript script) {
        this.enabledScript = script;
    }

    /**
     * @param bScalable
     */
    @Override
    public void setScalable(boolean bln) {
        // only for special components
    }

    public void setKeepAspectRatio(boolean keepAspectRatio) {
        // only for special components
    }

    /**
     * @param sNextFocusComponent
     */
    @Override
    public void setNextFocusComponent(String sNextFocusComponent) {
        this.sNextFocusComponent = sNextFocusComponent;
    }

    @Override
    public void setToolTipText(String sToolTipText) {
        getJComponent().setToolTipText(sToolTipText);
    }

    @Override
    public void setOpaque(boolean bOpaque) {
        getJComponent().setOpaque(bOpaque);
    }

    @Override
    public void setFillControlHorizontally(boolean bFill) {
        // do nothing
    }

    /**
     * makes the component insertable (or not). At the moment, this applies for comboboxes only.
     * @param bInsertable Can new values (apart from this component's given list of values) be inserted?
     */
    @Override
    public abstract void setInsertable(boolean bInsertable);

    /**
     * sets the text for the contained label, if any.
     * @param sLabel
     */
    @Override
    public void setLabelText(String sLabel) {
        // do nothing here
    }

    /**
     * sets the mnemonic for this component (or a contained label), if applicable.
     * @param cMnemonic
     */
    @Override
    public void setMnemonic(char cMnemonic) {
        // do nothing here
    }

    /**
     * sets the number of columns for this component, if applicable.
     * @param iColumns
     */
    @Override
    public void setColumns(int iColumns) {
        // do nothing here
    }

    /**
     * sets the number of rows for this component, if reasonable.
     * Note that this doesn't apply to comboboxes.
     * @param iRows
     */
    @Override
    public void setRows(int iRows) {
        // do nothing here
    }

    private void lockedModelToView() {
        runLocked(new Runnable() {
            @Override
            public void run() {
                modelToView();
            }
        });
    }

    /**
     * Implementation of <code>CollectableComponentModelListener</code>.
     * The model has changed. Updates the view.
     * @param ev
     */
    @Override
    public void collectableFieldChangedInModel(CollectableComponentModelEvent ev) {
        lockedModelToView();
    }

    /**
     * Implementation of <code>CollectableComponentModelListener</code>.
     * The model has changed. Updates the view.
     * @param ev
     */
    @Override
    public void valueToBeChanged(DetailsComponentModelEvent ev) {
        lockedModelToView();
    }

    /**
     * Implementation of <code>CollectableComponentModelListener</code>.
     * @param ev
     */
    @Override
    public void searchConditionChangedInModel(final SearchComponentModelEvent ev) {
        // update the view:
        runLocked(new Runnable() {
            @Override
            public void run() {
                final CollectableSearchCondition cond = ev.getSearchComponentModel().getSearchCondition();

                if (cond == null) {
                    clear();
                } else {
                    if (cond instanceof CollectableComparisonWithOtherField
                            && canDisplayComparisonWithOtherField()) {
                        final CollectableComparisonWithOtherField comparisonwf = (CollectableComparisonWithOtherField) cond;
                        handleComparisonOperator(comparisonwf.getComparisonOperator());
                        setWithComparison(comparisonwf.getOtherField());
                        setField(null);
                    } else if (cond instanceof CollectableComparisonWithParameter
                            && canDisplayComparisonWithOtherField()) {
                        final CollectableComparisonWithParameter comparisonwp = (CollectableComparisonWithParameter) cond;
                        handleComparisonOperator(comparisonwp.getComparisonOperator());
                        setWithComparison(comparisonwp.getParameter());
                        setField(null);
                    } else if (cond instanceof CollectableComparison) {
                        final CollectableComparison comparison = (CollectableComparison) cond;
                        handleComparisonOperator(comparison.getComparisonOperator());
                        resetWithComparison();
                        setField(comparison.getComparand());
                        // Note that setSearchCondition() needs setField(), but not vice versa.
                    } else {
                        // If this happens, there needs to be a more specific implementation for the component.
                        throw new CommonFatalException(getBadSearchConditionErrorMessage());
                    }
                }
            }

            private void handleComparisonOperator(ComparisonOperator compop) {
                if (hasComparisonOperator()) {
                    setComparisonOperator(compop);
                } else if (compop != ComparisonOperator.EQUAL) {
                    // If this happens, there needs to be a more specific implementation for the component.
                    throw new CommonFatalException(getBadSearchConditionErrorMessage());
                }
            }

            private String getBadSearchConditionErrorMessage() {
                return getSpringLocaleDelegate().getMessage("AbstractCollectableComponent.8",
                        "Die angegebene Suchbedingung kann in der Komponente f\u00fcr das Feld {0} nicht dargestellt werden.",
                        getFieldName());
            }
        });
    }

    /**
     * default implementation.
     * @return
     * @precondition isSearchComponent()
     * @throws CollectableFieldFormatException
     */
    protected CollectableSearchCondition getSearchConditionFromView() throws CollectableFieldFormatException {
        return SearchComponentModel.getDefaultSearchCondition(getFieldFromView(), getEntityField());
    }

    /**
     * updates the search condition in the model, ignoring a possible
     * <code>CollectableFieldFormatException</code>.
     * @precondition isSearchComponent()
     */
    protected void updateSearchConditionInModel() {
        try {
            getSearchModel().setSearchCondition(getSearchConditionFromView());
        } catch (CollectableFieldFormatException ex) {
            // this is ignored here by definition of this method.
        }
    }

    /**
     * @return the tooltip text to show when the search condition is NONE.
     * @precondition isSearchComponent()
     */
    protected String getTooltipTextForSearchConditionNone() {
        final StringBuffer sb = new StringBuffer(getEntityField().getLabel());
        sb.append(" (");
        sb.append(getSpringLocaleDelegate().getMessage("comparisonOperator.NONE.description", "NONE"));
        sb.append(")");
        return sb.toString();
    }

    /**
     * @return the tooltip text for the current searchcondition
     * @precondition isSearchComponent()
     */
    protected String getToolTipTextForCurrentSearchCondition() {
        String result;
        try {
            final CollectableSearchCondition searchcond = getSearchConditionFromView();
            result = (searchcond == null) ? getTooltipTextForSearchConditionNone()
                    : searchcond.accept(new ToHumanReadablePresentationVisitor());
        } catch (CollectableFieldFormatException ex) {
            result = getSpringLocaleDelegate().getMessage("AbstractCollectableComponent.5",
                    "<Ung\u00fcltige Suchbedingung:" + ex.getMessage() + ">", ex.getMessage());
        }
        return result;
    }

    /**
     * @return the tool tip text to show in multi edit mode
     * @precondition isMultiEditable()
     */
    protected String getToolTipTextForMultiEdit() {
        if (!isMultiEditable()) {
            throw new IllegalStateException("multiEditable");
        }
        String result;
        final String sLabel = getEntityField().getLabel();
        try {
            if (getDetailsModel().isValueToBeChanged()) {
                final CollectableField clctf = getFieldFromView();
                final String sValue = clctf.isNull()
                        ? getSpringLocaleDelegate().getMessage("AbstractCollectableComponent.2", "<gel\u00f6scht>")
                        : getSpringLocaleDelegate().getMessage("AbstractCollectableComponent.1", "<ge\u00e4ndert>");
                result = sLabel + " = " + sValue;
            } else {
                result = sLabel + " (" + getSpringLocaleDelegate().getMessage("AbstractCollectableComponent.3",
                        "<keine \u00c4nderung>") + ")";
            }
        } catch (CollectableFieldFormatException ex) {
            result = getSpringLocaleDelegate().getMessage("AbstractCollectableComponent.4",
                    "<Ung\u00fcltiger Wert>");
        }
        return result;
    }

    @Override
    public final ReferencingListener getReferencingListener() {
        return reflistener;
    }

    // @todo include in interface (again)?
    public void setReferencingListener(ReferencingListener listener) {
        reflistener = listener;
    }

    private void fireShowDetails() {
        final ReferencingListener reflistener = getReferencingListener();
        if (reflistener != null) {
            reflistener.showDetails(new CollectableComponentEvent(this));
        }
    }

    private void fireCreateNew() {
        final ReferencingListener reflistener = getReferencingListener();
        if (reflistener != null) {
            reflistener.createNew(new CollectableComponentEvent(this));
        }
    }

    /**
     * @return Does this component have a comparison operator that can be set by the user?
     * This default implementation returns <code>false</code>.
     * @precondition isSearchComponent()
     */
    public boolean hasComparisonOperator() {
        return false;
    }

    /**
     * @precondition hasComparisonOperator()
     */
    public final ComparisonOperator getComparisonOperator() {
        if (!hasComparisonOperator()) {
            throw new IllegalStateException();
        }
        return compop;
    }

    /**
     * @precondition hasComparisonOperator()
     */
    public void setComparisonOperator(ComparisonOperator compop) {
        if (!hasComparisonOperator()) {
            throw new IllegalStateException();
        }
        this.compop = compop;
    }

    /**
     * Subclasses may add menu items to the default popup menu or provide their own completely.
     * @return a new popup menu for this component.
     * @see JPopupMenuFactory
     */
    @Override
    public JPopupMenu newJPopupMenu() {
        JPopupMenu result;

        if (isSearchComponent() && hasComparisonOperator()) {
            result = newComparisonOperatorPopupMenu();
        } else {
            // regular popup menu:
            result = new JPopupMenu();

            if (isMultiEditable()) {
                result.add(newNoChangeEntry());
                result.add(newClearEntry());
            }

            if (getEntityField().isReferencing()) {
                if (result.getComponentCount() > 0) {
                    result.addSeparator();
                }
                result.add(newShowDetailsEntry());
                result.add(newInsertEntry());
            }

            if (result.getComponentCount() == 0) {
                result = null;
            }
        }
        return result;
    }

    /**
     * Subclasses should redefine {@link #newJPopupMenu()} rather than this method, to change the popup menu for this component.
     * @return the popup menu listener for this component. This default implementation returns a dynamic popup menu listener
     * that creates the popup menu on demand, each time it is about to be displayed.
     * @postcondition result != null
     */
    protected JPopupMenuListener newJPopupMenuListener() {
        return new DefaultJPopupMenuListener(this);
    }

    /**
     * sets up the popup menu listener for this component.
     * This default implementation adds a mouse listener to the control component.
     * May be overridden if the popup menu shouldn't be activated from the control component and/or should be activated
     * from other components contained in this component (the label etc.).
     * @param popupmenulistener
     */
    protected void setupJPopupMenuListener(JPopupMenuListener popupmenulistener) {
        getControlComponent().addMouseListener(popupmenulistener);
    }

    /**
     * @return
     * @precondition hasComparisonOperator()
     */
    public JPopupMenu newComparisonOperatorPopupMenu() {
        if (!hasComparisonOperator()) {
            throw new IllegalStateException();
        }
        final JPopupMenu result = new JPopupMenu(
                getSpringLocaleDelegate().getMessage("AbstractCollectableComponent.16", "Vergleichsoperator"));

        // 1. comparison operators:
        final ButtonGroup btngrpComparisonOperators = new ButtonGroup();

        final ItemListener itemlistener = new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent ev) {
                if (ev.getStateChange() == ItemEvent.SELECTED) {
                    final String sOperatorName = ((AbstractButton) ev.getItem()).getActionCommand();
                    setComparisonOperator(ComparisonOperator.getInstance(sOperatorName));
                    runLocked(new Runnable() {
                        @Override
                        public void run() {
                            updateSearchConditionInModel();
                        }
                    });
                }
            }
        };

        ComparisonOperator[] supportedComparisonOperators = getSupportedComparisonOperators();
        if (supportedComparisonOperators == null)
            return null;

        for (ComparisonOperator compop : supportedComparisonOperators) {
            JMenuItem mi = new JRadioButtonMenuItem(
                    getSpringLocaleDelegate().getMessage(compop.getResourceIdForLabel(), null));
            mi.setActionCommand(compop.name());
            mi.setToolTipText(getSpringLocaleDelegate().getMessage(compop.getResourceIdForDescription(), null));
            result.add(mi);
            btngrpComparisonOperators.add(mi);
            mi.addItemListener(itemlistener);
        }

        result.addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
                for (int i = 0, n = result.getComponentCount(); i < n; i++) {
                    Component c = result.getComponent(i);
                    if (c instanceof JMenuItem && StringUtils.emptyIfNull(((JMenuItem) c).getActionCommand())
                            .equals(getComparisonOperator().name()))
                        ((JMenuItem) c).setSelected(true);
                }
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) {
            }

            @Override
            public void popupMenuCanceled(PopupMenuEvent ev) {
            }
        });

        // 2. right operand (value or other field):
        if (canDisplayComparisonWithOtherField()) {
            addRightOperandToPopupMenu(result, this);
        }

        return result;
    }

    protected ComparisonOperator[] getSupportedComparisonOperators() {
        if (dataTyp != null) {
            if (dataTyp.getDatabaseTyp() != null
                    && (dataTyp.getDatabaseTyp().equals("blob") || dataTyp.getDatabaseTyp().equals("clob")))
                return new ComparisonOperator[0];
        }
        return ComparisonOperator.getComparisonOperators();
    }

    protected static DataTyp getDataTyp(String javaType, String defaultComponentType, Integer scale,
            Integer precision, String inputFormat, String outputFormat)
            throws CommonFinderException, CommonPermissionException {
        DataTyp typ = null;
        Collection<MasterDataVO> colMasterData = MasterDataCache.getInstance()
                .get(NuclosEntity.DATATYP.getEntityName());

        List<MasterDataVO> lstVO = new ArrayList<MasterDataVO>(colMasterData);
        Collections.sort(lstVO, new Comparator<MasterDataVO>() {

            @Override
            public int compare(MasterDataVO o1, MasterDataVO o2) {
                return ((String) o1.getField("name")).compareTo((String) o2.getField("name"));
            }

        });

        for (MasterDataVO vo : lstVO) {
            String strJavaTyp = (String) vo.getField("javatyp");
            String strDefaultComponentType = (String) vo.getField("defaultcomponenttype");
            String strOutputFormat = (String) vo.getField("outputformat");
            String strInputFormat = (String) vo.getField("inputformat");
            Integer iScale = (Integer) vo.getField("scale");
            if (iScale != null && iScale.intValue() == 0)
                iScale = null;
            Integer iPrecision = (Integer) vo.getField("precision");
            if (iPrecision != null && iPrecision.intValue() == 0)
                iPrecision = null;

            String strDatabaseTyp = (String) vo.getField("databasetyp");
            String strName = (String) vo.getField("name");
            if (strName.equals("Referenzfeld"))
                continue;
            if (strName.equals("Nachschlagefeld"))
                continue;

            try {
                if (StringUtils.equals(javaType, strJavaTyp) && StringUtils.equals(outputFormat, strOutputFormat) &&
                /*StringUtils.equals(inputFormat, strInputFormat) &&*/ ObjectUtils.equals(scale, iScale)
                        && ObjectUtils.equals(precision, iPrecision)
                        && StringUtils.equals(defaultComponentType, strDefaultComponentType)) {
                    typ = new DataTyp(strName, strInputFormat, strOutputFormat, strDatabaseTyp, iScale, iPrecision,
                            strJavaTyp, strDefaultComponentType);
                    break;
                }
            } catch (Exception e) {
                // ignore
            }
        }
        return typ;
    }

    /**
     * @param result
     * @param clctcomp
     */
    private static void addRightOperandToPopupMenu(JPopupMenu result, final AbstractCollectableComponent clctcomp) {
        result.addSeparator();
        final ButtonGroup btngrpCompareWith = new ButtonGroup();
        final SpringLocaleDelegate localeDelegate = SpringLocaleDelegate.getInstance();

        final JRadioButtonMenuItem miValue = new JRadioButtonMenuItem(
                localeDelegate.getMessage("AbstractCollectableComponent.17", "Wertvergleich"));
        miValue.setToolTipText(localeDelegate.getMessage("AbstractCollectableComponent.10",
                "Dieses Feld mit einem festen Wert vergleichen"));
        result.add(miValue);
        btngrpCompareWith.add(miValue);
        miValue.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                clctcomp.resetWithComparison();
                clctcomp.runLocked(new Runnable() {
                    @Override
                    public void run() {
                        clctcomp.updateSearchConditionInModel();
                    }
                });
            }
        });

        final JRadioButtonMenuItem miOtherField = new JRadioButtonMenuItem(
                localeDelegate.getMessage("AbstractCollectableComponent.12", "Feldvergleich..."));
        miOtherField.setToolTipText(localeDelegate.getMessage("AbstractCollectableComponent.9",
                "Dieses Feld mit dem Inhalt eines anderen Felds vergleichen"));
        result.add(miOtherField);
        btngrpCompareWith.add(miOtherField);
        miOtherField.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                assert clctcomp.clcte != null;

                // select entity field with the same data type:
                final List<CollectableEntityField> lstclctefFiltered = CollectionUtils.select(
                        CollectableUtils.getCollectableEntityFields(clctcomp.clcte),
                        new Predicate<CollectableEntityField>() {
                            @Override
                            public boolean evaluate(CollectableEntityField clctef) {
                                return clctef.getJavaClass() == clctcomp.clctef.getJavaClass();
                            }
                        });
                // and sort by label:
                final List<CollectableEntityField> lstclctefSorted = CollectionUtils.sorted(lstclctefFiltered,
                        new CollectableEntityField.LabelComparator());

                final JComboBox cmbbx = new JComboBox(lstclctefSorted.toArray());
                cmbbx.setSelectedItem(clctcomp.getComparisonOtherField());

                final int iBtn = JOptionPane
                        .showConfirmDialog(clctcomp.getJComponent(),
                                new Object[] { localeDelegate.getMessage("AbstractCollectableComponent.6",
                                        "Anderes Feld: "), cmbbx },
                                localeDelegate.getMessage("AbstractCollectableComponent.15",
                                        "Vergleich mit anderem Feld"),
                                JOptionPane.OK_CANCEL_OPTION);

                if (iBtn == JOptionPane.OK_OPTION) {
                    clctcomp.setWithComparison((CollectableEntityField) cmbbx.getSelectedItem());
                    if (clctcomp.getComparisonOtherField() != null) {
                        // clear the view:
                        clctcomp.updateView(CollectableUtils.getNullField(clctcomp.getEntityField()));

                        if (clctcomp.compop.getOperandCount() < 2) {
                            // If the user selects "other field" and forgot to set the operator, we assume "EQUAL":
                            clctcomp.compop = ComparisonOperator.EQUAL;
                        }
                    }
                    clctcomp.runLocked(new Runnable() {
                        @Override
                        public void run() {
                            clctcomp.updateSearchConditionInModel();
                        }
                    });
                }
            }
        });

        final List<ComparisonParameter> compatibleParameters = ComparisonParameter
                .getCompatibleParameters(clctcomp.getEntityField());
        final JRadioButtonMenuItem miParameterField = new JRadioButtonMenuItem(
                localeDelegate.getMessage("AbstractCollectableComponent.18", null));
        miParameterField.setToolTipText(localeDelegate.getMessage("AbstractCollectableComponent.19", null));
        btngrpCompareWith.add(miParameterField);
        if (compatibleParameters.size() > 0) {
            result.add(miParameterField);
            miParameterField.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent ev) {
                    ResourceIdMapper<ComparisonParameter> mapper = new ResourceIdMapper<ComparisonParameter>(
                            compatibleParameters);
                    JComboBox cmbbx = new JComboBox(CollectionUtils.sorted(compatibleParameters, mapper).toArray());
                    cmbbx.setRenderer(new DefaultListRenderer(mapper));
                    cmbbx.setSelectedItem(clctcomp.getComparisonParameter());

                    final int opt = JOptionPane.showConfirmDialog(clctcomp.getJComponent(),
                            new Object[] { localeDelegate.getMessage("AbstractCollectableComponent.20", null),
                                    cmbbx },
                            localeDelegate.getMessage("AbstractCollectableComponent.19", null),
                            JOptionPane.OK_CANCEL_OPTION);

                    if (opt == JOptionPane.OK_OPTION) {
                        clctcomp.setWithComparison((ComparisonParameter) cmbbx.getSelectedItem());
                        if (clctcomp.getComparisonParameter() != null) {
                            clctcomp.updateView(CollectableUtils.getNullField(clctcomp.getEntityField()));
                            if (clctcomp.compop.getOperandCount() < 2) {
                                clctcomp.compop = ComparisonOperator.EQUAL;
                            }
                        }
                        clctcomp.runLocked(new Runnable() {
                            @Override
                            public void run() {
                                clctcomp.updateSearchConditionInModel();
                            }
                        });
                    }
                }
            });
        }

        result.addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
                if (clctcomp.getComparisonParameter() != null) {
                    miParameterField.setSelected(true);
                } else if (clctcomp.getComparisonOtherField() == null
                        || clctcomp.getComparisonOperator().getOperandCount() < 2) {
                    miValue.setSelected(true);
                } else {
                    miOtherField.setSelected(true);
                }
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) {
            }

            @Override
            public void popupMenuCanceled(PopupMenuEvent ev) {
            }
        });
    }

    /**
     * @precondition isMultiEditable()
     * @return a new "no change" entry for the context menu in multi edit mode
     */
    protected final JMenuItem newNoChangeEntry() {
        if (!isMultiEditable()) {
            throw new IllegalStateException();
        }
        final JMenuItem result = new JMenuItem(TEXT_NOCHANGE);
        result.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                // restore common value, if any - otherwise clear the field:
                final DetailsComponentModel clctcompmodel = getDetailsModel();
                if (clctcompmodel.hasCommonValue()) {
                    clctcompmodel.setField(clctcompmodel.getCommonValue());
                } else {
                    clctcompmodel.clear();
                }
                clctcompmodel.setValueToBeChanged(false);
            }
        });
        return result;
    }

    /**
     * @precondition getEntityField().isReferencing()
     * @return a new "show details" entry for the context menu in edit mode
     */
    protected final JMenuItem newShowDetailsEntry() {
        if (!getEntityField().isReferencing()) {
            throw new IllegalStateException();
        }
        final JMenuItem result = new JMenuItem(TEXT_SHOWDETAILS);
        boolean bShowDetailsEnabled;
        try {
            bShowDetailsEnabled = isReferencedEntityDisplayable() && getField().getValueId() != null;
        } catch (CollectableFieldFormatException ex) {
            bShowDetailsEnabled = false;
        }
        if (bShowDetailsEnabled) {
            bShowDetailsEnabled = SecurityCache.getInstance()
                    .isReadAllowedForEntity(getEntityField().getReferencedEntityName());
        }
        result.setEnabled(bShowDetailsEnabled);

        result.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                fireShowDetails();
            }
        });
        return result;
    }

    /**
     * @precondition getEntityField().isReferencing()
     * @return a new "new" entry for the context menu in edit mode
     */
    protected final JMenuItem newInsertEntry() {
        if (!getEntityField().isReferencing()) {
            throw new IllegalStateException();
        }
        final JMenuItem result = new JMenuItem(TEXT_NEW);
        String referencedEntity = getEntityField().getReferencedEntityName();
        boolean bInsertEnabled = isReferencedEntityDisplayable();
        if (bInsertEnabled) {
            if (Modules.getInstance().existModule(referencedEntity)) {
                bInsertEnabled = SecurityCache.getInstance().isNewAllowedForModule(referencedEntity);
            } else {
                bInsertEnabled = SecurityCache.getInstance().isWriteAllowedForMasterData(referencedEntity);
            }

            boolean blnEntityIsEditable = MetaDataClientProvider.getInstance().getEntity(referencedEntity)
                    .isEditable();
            if (!blnEntityIsEditable)
                bInsertEnabled = blnEntityIsEditable;
        }
        if (this instanceof CollectableComboBox)
            result.setEnabled(bInsertEnabled && ((CollectableComboBox) this).getLabeledComponent().isEnabled());
        else
            result.setEnabled(bInsertEnabled && getControlComponent().isEnabled());

        result.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                fireCreateNew();
            }
        });
        return result;
    }

    /**
     * @precondition getEntityField().isReferencing()
     * @return true, if the referenced entity is displayable
     */
    private boolean isReferencedEntityDisplayable() {
        final CollectableEntityField clctef = getEntityField();
        return clctef.isReferencedEntityDisplayable() && isEntityDisplayable(clctef.getReferencedEntityName());

        /** @todo if isReferencedEntityDisplayable() is eliminated, this should be: */
        //      return isEntityDisplayable(clctef.getReferencedEntityName());
    }

    private boolean isEntityDisplayable(String sEntityName) {
        // DefaultCollectableEntityProvider.getInstance() will return type of CollectableEOEntityProvider.
        //  CollectableEOEntityProvider throws UnsupportedOperationException for Method isEntityDisplayable(...)
        //return DefaultCollectableEntityProvider.getInstance().isEntityDisplayable(sEntityName);
        return Modules.getInstance().isModuleEntity(sEntityName)
                || MasterDataLayoutHelper.isLayoutMLAvailable(sEntityName, false);
    }

    /**
     * @return a new "clear" entry for the context menu in edit mode
     */
    protected final JMenuItem newClearEntry() {
        final JMenuItem result = new JMenuItem(TEXT_CLEAR);
        result.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                clear();
            }
        });
        return result;
    }

    /** @todo comment */
    protected Color getBackgroundColor() {
        Color result;

        /** @todo + isEnabled() && */
        if (isMultiEditable() && !getDetailsModel().hasCommonValue() && !getDetailsModel().isValueToBeChanged()) {
            result = colorCommonValues;
        } else {
            boolean hasValue = !getModel().getField().isNull();
            boolean active = true;

            // not editable
            if (isDetailsComponent() && (getControlComponent() instanceof JTextComponent)
                    && !((JTextComponent) getControlComponent()).isEditable()) {
                result = NuclosThemeSettings.BACKGROUND_INACTIVEFIELD;
                active = false;
            } else if (isDetailsComponent() && (getControlComponent() instanceof DateChooser)
                    && !((DateChooser) getControlComponent()).getJTextField().isEditable()) {
                result = NuclosThemeSettings.BACKGROUND_INACTIVEFIELD;
                active = false;
            } else if (isDetailsComponent() && (getControlComponent() instanceof ListOfValues)
                    && !((ListOfValues) getControlComponent()).isEnabled()) {
                result = NuclosThemeSettings.BACKGROUND_INACTIVEFIELD;
                active = false;
            } else if (isDetailsComponent() && (getControlComponent() instanceof JComboBox)
                    && !((JComboBox) getControlComponent()).isEditable()) {
                result = NuclosThemeSettings.BACKGROUND_INACTIVEFIELD;
                active = false;
            } else if (isDetailsComponent() && (getControlComponent() instanceof FileChooserComponent)
                    && !((FileChooserComponent) getControlComponent()).isEnabled()) {
                result = NuclosThemeSettings.BACKGROUND_INACTIVEFIELD;
                active = false;
            } else

            if (isDetailsComponent() && getDetailsModel().isMandatoryAdded()) {
                result = hasValue || hasFocus() ? null
                        : ClientParameterProvider.getInstance().getColorValue(
                                ParameterProvider.KEY_MANDATORY_ADDED_ITEM_BACKGROUND_COLOR,
                                new Color(255, 255, 200));
            } else if (isDetailsComponent() && getDetailsModel().isMandatory()) {
                result = hasValue || hasFocus() ? null : getMandatoryColor();
            } else {
                result = hasFocus() ? null : Color.WHITE;
            }

            if (!NuclosThemeSettings.BACKGROUND_PANEL.equals(comp.getBackground())) {
                result = hasFocus() && active ? null : comp.getBackground();
            }
        }

        //Logger.getLogger(AbstractCollectableComponent.class).debug("getBackgroundColor: result = " + result);
        return result;
    }

    public static Color getMandatoryColor() {
        return ClientParameterProvider.getInstance()
                .getColorValue(ParameterProvider.KEY_MANDATORY_ITEM_BACKGROUND_COLOR, new Color(255, 255, 200));
    }

    private boolean hasFocus() {
        // if the component has focus, other code far, far away sets the nice yellow
        // background which we don't want to overwride here ...
        JComponent focusComponent = getControlComponent();

        if (getJComponent() instanceof LabeledComboBox) {
            JComboBox cb = ((LabeledComboBox) getJComponent()).getJComboBox();
            focusComponent = (JComponent) (cb.getEditor() != null ? cb.getEditor().getEditorComponent() : cb);
        }

        return focusComponent.hasFocus();
    }

    @Override
    public String getDynamicToolTipText() {
        final String result;
        if (isSearchComponent()) {
            result = getToolTipTextForCurrentSearchCondition();
        } else if (isMultiEditable()) {
            result = getToolTipTextForMultiEdit();
        } else {
            // the default tooltip text:
            result = getJComponent().getToolTipText();
        }
        return result;
    }

    /**
     * provides a common implementation of getSearchConditionFromView()
     * @param sLikeComparand
     * @return
     * @throws CollectableFieldFormatException
     */
    protected CollectableSearchCondition getSearchConditionFromViewImpl(String sLikeComparand)
            throws CollectableFieldFormatException {
        if (viewSupportsComparisonWith()) {
            if (getComparisonOtherField() != null) {
                return new CollectableComparisonWithOtherField(getEntityField(), getComparisonOperator(),
                        getComparisonOtherField());
            } else if (getComparisonParameter() != null) {
                return new CollectableComparisonWithParameter(getEntityField(), getComparisonOperator(),
                        getComparisonParameter());
            }
        }
        return CollectableTextComponentHelper.getAtomicSearchConditionFromView(getEntityField(),
                getComparisonOperator(), this, sLikeComparand);
    }

    /**
     * @return Does the view currently contain a comparison with another field or a parameter?
     */
    private boolean viewSupportsComparisonWith() {
        return isSearchComponent() && canDisplayComparisonWithOtherField()
                && getComparisonOperator().getOperandCount() == 2;
    }

    protected DocumentListener newDocumentListenerForTextComponentWithComparisonOperator() {
        return new MyDocumentListener(this);
    }

    /**
     * @return a suitable <code>TableCellRenderer</code> for this component.
     * This default implementation returns a new javax.swing.table.DefaultTableCellRenderer for Details components
     * (and a special renderer for Search components).
     * Successors may provide a more specific renderer here.
     */
    @Override
    public TableCellRenderer getTableCellRenderer(boolean subform) {
        if (isSearchComponent()) {
            return new CollectableComponentDefaultTableCellRenderer();
        } else {
            return new CollectableComponentDetailTableCellRenderer();
        }
    }

    @Override
    public Object getProperty(String sName) {
        return getProperties().get(sName);
    }

    @Override
    public void setProperty(String sName, Object oValue) {
        synchronized (this) {
            if (mpProperties == null) {
                mpProperties = new TreeMap<String, Object>();
            }
        }
        mpProperties.put(sName, oValue);

        assert LangUtils.equals(getProperty(sName), oValue);
    }

    @Override
    public synchronized Map<String, Object> getProperties() {
        final Map<String, Object> result = (mpProperties == null) ? Collections.<String, Object>emptyMap()
                : Collections.unmodifiableMap(mpProperties);
        assert result != null;
        return result;
    }

    /**
     * @return <code>null</code>
     */
    @Override
    public Preferences getPreferences() {
        return null;
    }

    /**
     * NOP.
     * @param prefs
     */
    @Override
    public void setPreferences(Preferences prefs) {
        // do nothing here
    }

    protected final static void setBackground(Component c, NuclosScript ns, final Collectable clct,
            EntityMetaDataVO meta, boolean isEnabled) {
        try {
            String rgb = Integer.toHexString(c.getBackground().getRGB());
            rgb = rgb.substring(2, rgb.length());
            Object o = ScriptEvaluator.getInstance().eval(ns, new CollectableScriptContext(clct), "#" + rgb);

            if (o instanceof String) {
                Color color = Color.decode((String) o);
                if (isEnabled) {
                    c.setBackground(color);
                } else {
                    c.setBackground(new Color(Math.max(0, color.getRed() - (color.getRed() * 15 / 100)),
                            Math.max(0, color.getGreen() - (color.getGreen() * 15 / 100)),
                            Math.max(0, color.getBlue() - (color.getBlue() * 15 / 100))));
                }
            }
        } catch (Exception ex) {
            LOG.warn(ex);
        }
    }

    /**
     * CollectableComponents are equal iff they are identical. This behavior may not be changed by subclasses.
     * @param o
     */
    @Override
    public final boolean equals(Object o) {
        return super.equals(o);
    }

    /**
     * @see #equals(Object)
     */
    @Override
    public final int hashCode() {
        return super.hashCode();
    }

    protected class BackgroundColorProvider implements ColorProvider {
        @Override
        public Color getColor(Color colorDefault) {
            final Color color = getBackgroundColor();
            return (color != null) ? color : colorDefault;
        }
    } // inner class BackgroundColorProvider

    /**
     * DocumentListener for text components
     */
    private static class MyDocumentListener implements DocumentListener {
        private final AbstractCollectableComponent clctcomp;

        MyDocumentListener(AbstractCollectableComponent clctcomp) {
            this.clctcomp = clctcomp;
        }

        @Override
        public void changedUpdate(DocumentEvent ev) {
            // this is never called.
            //         assert false;
            // called from JEditorPane!!!
        }

        @Override
        public void insertUpdate(final DocumentEvent ev) {
            if (clctcomp.isSearchComponent()) {
                clctcomp.runLocked(new Runnable() {
                    @Override
                    public void run() {
                        // We automatically switch to "compare by value" when anything is entered:
                        clctcomp.resetWithComparison();

                        // We automatically switch to LIKE, if a wildcard was entered:
                        final String sInsertedText;
                        try {
                            sInsertedText = ev.getDocument().getText(ev.getOffset(), ev.getLength());
                        } catch (BadLocationException ex) {
                            throw new CommonFatalException(ex);
                        }
                        if (CollectableLikeCondition.containsWildcard(sInsertedText)) {
                            clctcomp.setComparisonOperator(ComparisonOperator.LIKE);
                        } else {
                            // We automatically switch from NONE/IS_NULL/IS_NOT_NULL to EQUAL when a character is entered:
                            if (clctcomp.getComparisonOperator().getOperandCount() < 2) {
                                clctcomp.setComparisonOperator(ComparisonOperator.EQUAL);
                            }
                        }
                        clctcomp.updateSearchConditionInModel();
                    }
                });
            } else {
                updateModel();
            }
        }

        @Override
        public void removeUpdate(final DocumentEvent ev) {
            if (clctcomp.isSearchComponent()) {
                clctcomp.runLocked(new Runnable() {
                    @Override
                    public void run() {
                        final String sText;
                        try {
                            sText = ev.getDocument().getText(0, ev.getDocument().getLength());
                        } catch (BadLocationException ex) {
                            throw new CommonFatalException(ex);
                        }
                        if (StringUtils.isNullOrEmpty(sText)) {
                            if (clctcomp.getComparisonOperator().getOperandCount() >= 2) {
                                clctcomp.setComparisonOperator(ComparisonOperator.NONE);
                            }
                        }
                        clctcomp.updateSearchConditionInModel();
                    }
                });
            } else {
                updateModel();
            }
        }

        private void updateModel() {
            try {
                clctcomp.viewToModel();
            } catch (CollectableFieldFormatException ex) {
                // do nothing. The model can't be updated.
                assert !clctcomp.isConsistent();
            }
        }
    } // inner class MyDocumentListener

    /**
     * default table cell renderer for (search) CollectableComponents.
     *
     * TODO: This REALLY should be static - but how to archive this? (tp)
     */
    protected class CollectableComponentDefaultTableCellRenderer implements TableCellRenderer {

        public CollectableComponentDefaultTableCellRenderer() {
        }

        @Override
        public Component getTableCellRendererComponent(JTable tbl, Object oValue, boolean bSelected,
                boolean bHasFocus, int iRow, int iColumn) {
            setObjectValue(oValue);

            final JComponent result = getControlComponent();

            if (result instanceof JLabel) {
                ((JLabel) result).setVerticalAlignment(SwingConstants.TOP);
            }

            return result;
        }
    }

    protected static class CollectableComponentDetailTableCellRenderer extends DefaultTableCellRenderer {

        // blurfilter to hide data on which the user has no read permission
        private final BoxBlurFilter filter = new BoxBlurFilter(20, 10, 1);
        private final BufferedImageOpEffect blurEffect = new BufferedImageOpEffect(filter);

        public CollectableComponentDetailTableCellRenderer() {
            setVerticalAlignment(SwingConstants.TOP);
        }

        @Override
        public Component getTableCellRendererComponent(JTable tbl, Object oValue, boolean bSelected,
                boolean bHasFocus, int iRow, int iColumn) {

            super.getTableCellRendererComponent(tbl, oValue, bSelected, bHasFocus, iRow, iColumn);

            setAlignmentX(JLabel.CENTER_ALIGNMENT);

            final TableModel tm;
            final int adjustColIndex;
            if (tbl instanceof FixedColumnRowHeader.HeaderTable
                    && ((FixedColumnRowHeader.HeaderTable) tbl).getExternalTable() != null) {
                tm = ((FixedColumnRowHeader.HeaderTable) tbl).getExternalTable().getModel();
                adjustColIndex = FixedRowIndicatorTableModel.ROWMARKERCOLUMN_COUNT;
            } else {
                tm = tbl.getModel();
                adjustColIndex = 0;
            }

            // check whether the data of the component is readable for current user, by asking the security agent of the actual field
            if (tm instanceof SortableCollectableTableModel<?>) {
                final SortableCollectableTableModel<Collectable> tblModel = (SortableCollectableTableModel<Collectable>) tm;
                if (tblModel.getRowCount() > iRow) {
                    final Collectable clct = tblModel.getCollectable(iRow);
                    final Integer iTColumn = tbl.getColumnModel().getColumn(iColumn).getModelIndex()
                            - adjustColIndex;

                    final CollectableEntityField clctef;
                    try {
                        clctef = tblModel.getCollectableEntityField(iTColumn);
                    } catch (IndexOutOfBoundsException e) {
                        //@see  NUCLOS-706 - return same as not readable column. should never occour. may be threading.
                        final BufferedLayerUI<JComponent> layerUI = new BufferedLayerUI<JComponent>();
                        final JXLayer<JComponent> layer = new JXLayer<JComponent>(this, layerUI);
                        layerUI.setLayerEffects(blurEffect);
                        return layer;
                    }

                    if (clctef == null) {
                        throw new NullPointerException("getTableCellRendererComponent failed to find field: " + clct
                                + " tm index " + iTColumn);
                    }

                    CefSecurityAgent sa = clctef.getSecurityAgent();
                    if (sa == null) {
                        // lazy set the security agent
                        GenericObjectClientUtils.setSecurityAgent(clct, clctef,
                                tblModel.getBaseEntityName() != null
                                        ? !tblModel.getBaseEntityName().equals(clctef.getEntityName())
                                        : false);
                        sa = clctef.getSecurityAgent();
                    }
                    sa.setCollectable(clct);
                    if (!clctef.isReadable()) {
                        final BufferedLayerUI<JComponent> layerUI = new BufferedLayerUI<JComponent>();
                        final JXLayer<JComponent> layer = new JXLayer<JComponent>(this, layerUI);
                        layerUI.setLayerEffects(blurEffect);
                        return layer;
                    }
                }

                setBackgroundColor(this, tbl, oValue, bSelected, bHasFocus, iRow, iColumn);
            }
            return this;
        }
    }

    public static void setBackgroundColor(Component cellRendererComponent, JTable tbl, Object oValue,
            boolean bSelected, boolean bHasFocus, int iRow, int iColumn) {
        cellRendererComponent.setBackground(bSelected ? tbl.getSelectionBackground()
                : iRow % 2 == 0 ? tbl.getBackground() : NuclosThemeSettings.BACKGROUND_PANEL);
        cellRendererComponent.setForeground(bSelected ? tbl.getSelectionForeground() : tbl.getForeground());

        final TableModel tm;
        final int adjustColIndex;
        if (tbl instanceof FixedColumnRowHeader.HeaderTable
                && ((FixedColumnRowHeader.HeaderTable) tbl).getExternalTable() != null) {
            tm = ((FixedColumnRowHeader.HeaderTable) tbl).getExternalTable().getModel();
            adjustColIndex = FixedRowIndicatorTableModel.ROWMARKERCOLUMN_COUNT;
        } else {
            tm = tbl.getModel();
            adjustColIndex = 0;
        }

        // check whether the data of the component is readable for current user, by asking the security agent of the actual field
        if (tm instanceof SortableCollectableTableModel<?>) {
            final SortableCollectableTableModel<Collectable> tblModel = (SortableCollectableTableModel<Collectable>) tm;
            if (tblModel.getRowCount() > iRow) {
                final Collectable clct = tblModel.getCollectable(iRow);
                final Integer iTColumn = tbl.getColumnModel().getColumn(iColumn).getModelIndex() - adjustColIndex;
                final CollectableEntityField clctef = tblModel.getCollectableEntityField(iTColumn);
                if (clctef == null) {
                    throw new NullPointerException("getTableCellRendererComponent failed to find field: " + clct
                            + " tm index " + iTColumn);
                }

                boolean isEnabled = true;
                if (!clctef.isNullable() && isNull(oValue)) {
                    cellRendererComponent.setBackground(getMandatoryColor());
                    cellRendererComponent.setForeground(tbl.getForeground());
                } else {
                    //               if (clct.getId() == null) {
                    //                  cellRendererComponent.setBackground(tbl.getBackground());
                    //                  cellRendererComponent.setForeground(tbl.getForeground());
                    //               } else {
                    if (tbl instanceof SubForm.SubFormTable) {
                        SubFormTable subformtable = (SubForm.SubFormTable) tbl;
                        Column subformcolumn = subformtable.getSubForm().getColumn(clctef.getName());
                        if (subformcolumn != null && !subformcolumn.isEnabled()) {
                            isEnabled = false;
                            if (bSelected) {
                                cellRendererComponent
                                        .setBackground(NuclosThemeSettings.BACKGROUND_INACTIVESELECTEDCOLUMN);
                            } else {
                                cellRendererComponent.setBackground(NuclosThemeSettings.BACKGROUND_INACTIVECOLUMN);
                            }
                        }
                    }
                    //               }

                    try {
                        EntityMetaDataVO meta = MetaDataClientProvider.getInstance()
                                .getEntity(clctef.getEntityName());
                        if (meta.getRowColorScript() != null && !bSelected) {
                            AbstractCollectableComponent.setBackground(cellRendererComponent,
                                    meta.getRowColorScript(), clct, meta, isEnabled);
                        }
                    } catch (CommonFatalException ex) {
                        LOG.warn(ex);
                    }
                }
            }
        }

        if (tbl instanceof TableRowMouseOverSupport) {
            TableRowMouseOverSupport trmos = (TableRowMouseOverSupport) tbl;
            if (trmos.isMouseOverRow(iRow)) {
                final Color bgColor = LangUtils.defaultIfNull(cellRendererComponent.getBackground(), Color.WHITE);
                cellRendererComponent
                        .setBackground(new Color(Math.max(0, bgColor.getRed() - (bgColor.getRed() * 8 / 100)),
                                Math.max(0, bgColor.getGreen() - (bgColor.getGreen() * 8 / 100)),
                                Math.max(0, bgColor.getBlue() - (bgColor.getBlue() * 8 / 100))));
                //            cellRendererComponent.setBackground(UIManager.getColor("Table.selectionBackground"));
            }
        }
    }

    private static boolean isNull(Object oValue) {
        if (oValue == null) {
            return true;
        }
        if (oValue instanceof CollectableField) {
            if (((CollectableField) oValue).getFieldType() == CollectableField.TYPE_VALUEIDFIELD) {
                return ((CollectableField) oValue).getValueId() == null;
            } else {
                return ((CollectableField) oValue).getValue() == null;
            }
        }
        return false;
    }

    @Override
    public void setComponentState(ScriptContext ctx, String expression) {
        dynamicallyEnabled = true;
        if (enabledScript != null && !isSearchComponent()) {
            if (expression == null || ExpressionParser.contains(enabledScript, expression)) {
                Object o = ScriptEvaluator.getInstance().eval(enabledScript, ctx, true);
                try {
                    dynamicallyEnabled = LangUtils.defaultIfNull((Boolean) o, Boolean.TRUE);
                } catch (ClassCastException ex) {
                    LOG.warn("Failed to evaluate script expression.", ex);
                }
                setEnabledState(enabled && !readOnly && dynamicallyEnabled);
            }
        }
    }

    protected void setEnabledState(boolean enabled) {
        getJComponent().setEnabled(enabled);
    }
} // class AbstractCollectableComponent