org.nuclos.client.genericobject.GenericObjectCollectController.java Source code

Java tutorial

Introduction

Here is the source code for org.nuclos.client.genericobject.GenericObjectCollectController.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.genericobject;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollBar;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;
import javax.swing.RowSorter.SortKey;
import javax.swing.SwingWorker;
import javax.swing.TransferHandler;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.nuclos.client.attribute.AttributeCache;
import org.nuclos.client.common.ClientParameterProvider;
import org.nuclos.client.common.DependantCollectableMasterDataMap;
import org.nuclos.client.common.DetailsSubFormController;
import org.nuclos.client.common.EntityCollectController;
import org.nuclos.client.common.KeyBindingProvider;
import org.nuclos.client.common.LafParameterProvider;
import org.nuclos.client.common.MetaDataClientProvider;
import org.nuclos.client.common.MultiUpdateOfDependants;
import org.nuclos.client.common.NuclosCollectableStateComboBox;
import org.nuclos.client.common.NuclosFocusTraversalPolicy;
import org.nuclos.client.common.NuclosResultPanel;
import org.nuclos.client.common.SearchConditionSubFormController;
import org.nuclos.client.common.SubFormController;
import org.nuclos.client.common.TableRowIndicator;
import org.nuclos.client.common.Utils;
import org.nuclos.client.common.security.SecurityCache;
import org.nuclos.client.dal.DalSupportForGO;
import org.nuclos.client.entityobject.CollectableEOEntityClientProvider;
import org.nuclos.client.entityobject.CollectableEntityObject;
import org.nuclos.client.entityobject.EntityObjectDelegate;
import org.nuclos.client.eventsupport.EventSupportDelegate;
import org.nuclos.client.explorer.ExplorerController;
import org.nuclos.client.explorer.ExplorerDelegate;
import org.nuclos.client.genericobject.actionlisteners.ResetToTemplateUserActionListener;
import org.nuclos.client.genericobject.datatransfer.GenericObjectIdModuleProcess;
import org.nuclos.client.genericobject.datatransfer.TransferableGenericObjects;
import org.nuclos.client.genericobject.logbook.LogbookController;
import org.nuclos.client.genericobject.resulttemplate.SearchResultTemplate;
import org.nuclos.client.genericobject.statehistory.StateHistoryController;
import org.nuclos.client.genericobject.valuelistprovider.GenericObjectCollectableFieldsProviderFactory;
import org.nuclos.client.main.Main;
import org.nuclos.client.main.mainframe.MainFrameTab;
import org.nuclos.client.masterdata.CollectableMasterData;
import org.nuclos.client.masterdata.MasterDataCache;
import org.nuclos.client.masterdata.MasterDataDelegate;
import org.nuclos.client.masterdata.MasterDataSubFormController;
import org.nuclos.client.masterdata.valuelistprovider.MasterDataCollectableFieldsProviderFactory;
import org.nuclos.client.resource.ResourceCache;
import org.nuclos.client.rule.RuleDelegate;
import org.nuclos.client.scripting.context.CollectControllerScriptContext;
import org.nuclos.client.searchfilter.EntitySearchFilter;
import org.nuclos.client.searchfilter.SearchFilter;
import org.nuclos.client.searchfilter.SearchFilters;
import org.nuclos.client.statemodel.StateDelegate;
import org.nuclos.client.statemodel.StateViewComponent;
import org.nuclos.client.statemodel.StateWrapper;
import org.nuclos.client.ui.ColoredLabel;
import org.nuclos.client.ui.CommonAbstractAction;
import org.nuclos.client.ui.CommonClientWorkerAdapter;
import org.nuclos.client.ui.CommonMultiThreader;
import org.nuclos.client.ui.DateChooser;
import org.nuclos.client.ui.Errors;
import org.nuclos.client.ui.Icons;
import org.nuclos.client.ui.LayoutComponentUtils;
import org.nuclos.client.ui.OvOpAdapter;
import org.nuclos.client.ui.OverlayOptionPane;
import org.nuclos.client.ui.SizeKnownEvent;
import org.nuclos.client.ui.SizeKnownListener;
import org.nuclos.client.ui.UIUtils;
import org.nuclos.client.ui.collect.CollectController;
import org.nuclos.client.ui.collect.CollectPanel;
import org.nuclos.client.ui.collect.CollectState;
import org.nuclos.client.ui.collect.CollectStateAdapter;
import org.nuclos.client.ui.collect.CollectStateConstants;
import org.nuclos.client.ui.collect.CollectStateEvent;
import org.nuclos.client.ui.collect.CollectableComponentsProvider;
import org.nuclos.client.ui.collect.DefaultEditView;
import org.nuclos.client.ui.collect.DeleteSelectedCollectablesController;
import org.nuclos.client.ui.collect.SubForm;
import org.nuclos.client.ui.collect.SubForm.ParameterChangeListener;
import org.nuclos.client.ui.collect.UpdateSelectedCollectablesController.UpdateAction;
import org.nuclos.client.ui.collect.UserCancelledException;
import org.nuclos.client.ui.collect.component.CollectableComboBox;
import org.nuclos.client.ui.collect.component.CollectableComponent;
import org.nuclos.client.ui.collect.component.CollectableComponentTableCellEditor;
import org.nuclos.client.ui.collect.component.CollectableComponentWithValueListProvider;
import org.nuclos.client.ui.collect.component.CollectableDateChooser;
import org.nuclos.client.ui.collect.component.model.CollectableComponentModel;
import org.nuclos.client.ui.collect.component.model.CollectableComponentModelAdapter;
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.CollectableComponentModelProvider;
import org.nuclos.client.ui.collect.component.model.DefaultDetailsEditModel;
import org.nuclos.client.ui.collect.component.model.DefaultSearchEditModel;
import org.nuclos.client.ui.collect.component.model.DetailsComponentModel;
import org.nuclos.client.ui.collect.component.model.DetailsEditModel;
import org.nuclos.client.ui.collect.component.model.EditModel;
import org.nuclos.client.ui.collect.component.model.SearchComponentModel;
import org.nuclos.client.ui.collect.component.model.SearchComponentModelEvent;
import org.nuclos.client.ui.collect.component.model.SearchEditModel;
import org.nuclos.client.ui.collect.detail.DetailsPanel;
import org.nuclos.client.ui.collect.model.CollectableTableModel;
import org.nuclos.client.ui.collect.model.GenericObjectsResultTableModel;
import org.nuclos.client.ui.collect.model.SortableCollectableTableModel;
import org.nuclos.client.ui.collect.result.GenericObjectResultController;
import org.nuclos.client.ui.collect.result.NuclosSearchResultStrategy;
import org.nuclos.client.ui.collect.result.ResultActionCollection;
import org.nuclos.client.ui.collect.result.ResultController;
import org.nuclos.client.ui.collect.result.ResultPanel;
import org.nuclos.client.ui.collect.search.ISearchStrategy;
import org.nuclos.client.ui.collect.search.SearchPanel;
import org.nuclos.client.ui.gc.ListenerUtil;
import org.nuclos.client.ui.labeled.LabeledComponent;
import org.nuclos.client.ui.layoutml.LayoutRoot;
import org.nuclos.client.ui.multiaction.MultiActionProgressLine;
import org.nuclos.client.ui.multiaction.MultiActionProgressPanel;
import org.nuclos.client.ui.multiaction.MultiActionProgressResultHandler;
import org.nuclos.client.ui.multiaction.MultiCollectablesActionController;
import org.nuclos.client.ui.table.TableUtils;
import org.nuclos.client.valuelistprovider.cache.ManagedCollectableFieldsProvider;
import org.nuclos.common.Actions;
import org.nuclos.common.CollectableEntityFieldWithEntity;
import org.nuclos.common.LafParameter;
import org.nuclos.common.MetaDataProvider;
import org.nuclos.common.NuclosBusinessException;
import org.nuclos.common.NuclosEOField;
import org.nuclos.common.NuclosEntity;
import org.nuclos.common.NuclosFatalException;
import org.nuclos.common.NuclosImage;
import org.nuclos.common.ParameterProvider;
import org.nuclos.common.PointerException;
import org.nuclos.common.SpringApplicationContextHolder;
import org.nuclos.common.UsageCriteria;
import org.nuclos.common.attribute.DynamicAttributeVO;
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.CollectableFieldFormat;
import org.nuclos.common.collect.collectable.CollectableFieldsProvider;
import org.nuclos.common.collect.collectable.CollectableFieldsProviderFactory;
import org.nuclos.common.collect.collectable.CollectableSorting;
import org.nuclos.common.collect.collectable.CollectableUtils;
import org.nuclos.common.collect.collectable.CollectableValueField;
import org.nuclos.common.collect.collectable.CollectableValueIdField;
import org.nuclos.common.collect.collectable.DefaultCollectableEntityField;
import org.nuclos.common.collect.collectable.searchcondition.AtomicCollectableSearchCondition;
import org.nuclos.common.collect.collectable.searchcondition.CollectableComparison;
import org.nuclos.common.collect.collectable.searchcondition.CollectableLikeCondition;
import org.nuclos.common.collect.collectable.searchcondition.CollectableSearchCondition;
import org.nuclos.common.collect.collectable.searchcondition.CollectableSubCondition;
import org.nuclos.common.collect.collectable.searchcondition.CompositeCollectableSearchCondition;
import org.nuclos.common.collect.collectable.searchcondition.LogicalOperator;
import org.nuclos.common.collect.collectable.searchcondition.SearchConditionUtils;
import org.nuclos.common.collect.exception.CollectableFieldFormatException;
import org.nuclos.common.collection.BinaryPredicate;
import org.nuclos.common.collection.CollectionUtils;
import org.nuclos.common.collection.Predicate;
import org.nuclos.common.collection.PredicateUtils;
import org.nuclos.common.collection.Transformer;
import org.nuclos.common.dal.vo.EntityFieldMetaDataVO;
import org.nuclos.common.dal.vo.EntityMetaDataVO;
import org.nuclos.common.dal.vo.EntityObjectVO;
import org.nuclos.common.entityobject.CollectableEOEntity;
import org.nuclos.common.entityobject.CollectableEOEntityField;
import org.nuclos.common.format.FormattingTransformer;
import org.nuclos.common.genericobject.CollectableGenericObjectEntityField;
import org.nuclos.common.security.Permission;
import org.nuclos.common2.CommonRunnable;
import org.nuclos.common2.DateUtils;
import org.nuclos.common2.EntityAndFieldName;
import org.nuclos.common2.IdUtils;
import org.nuclos.common2.LangUtils;
import org.nuclos.common2.SpringLocaleDelegate;
import org.nuclos.common2.StringUtils;
import org.nuclos.common2.XStreamSupport;
import org.nuclos.common2.exception.CommonBusinessException;
import org.nuclos.common2.exception.CommonFatalException;
import org.nuclos.common2.exception.CommonFinderException;
import org.nuclos.common2.exception.CommonPermissionException;
import org.nuclos.common2.exception.CommonRemoveException;
import org.nuclos.common2.exception.CommonValidationException;
import org.nuclos.server.attribute.valueobject.AttributeCVO;
import org.nuclos.server.genericobject.ProxyList;
import org.nuclos.server.genericobject.searchcondition.CollectableGenericObjectSearchExpression;
import org.nuclos.server.genericobject.valueobject.GeneratorActionVO;
import org.nuclos.server.genericobject.valueobject.GenericObjectDocumentFile;
import org.nuclos.server.genericobject.valueobject.GenericObjectVO;
import org.nuclos.server.genericobject.valueobject.GenericObjectWithDependantsVO;
import org.nuclos.server.genericobject.valueobject.LogbookVO;
import org.nuclos.server.masterdata.valueobject.DependantMasterDataMap;
import org.nuclos.server.masterdata.valueobject.DependantMasterDataMapImpl;
import org.nuclos.server.navigation.treenode.EntitySearchResultTreeNode;
import org.nuclos.server.navigation.treenode.GenericObjectTreeNode;
import org.nuclos.server.resource.valueobject.ResourceVO;
import org.nuclos.server.ruleengine.NuclosBusinessRuleException;
import org.nuclos.server.ruleengine.valueobject.RuleEventUsageVO;
import org.nuclos.server.ruleengine.valueobject.RuleVO;
import org.nuclos.server.statemodel.valueobject.MandatoryFieldVO;
import org.nuclos.server.statemodel.valueobject.StateVO;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * Controller for collecting generic objects.
 * Contains the necessary logic to search for, view and edit generic objects.
 * <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 class GenericObjectCollectController
        extends EntityCollectController<CollectableGenericObjectWithDependants> {

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

    private static final Collection<String> collUsageCriteriaFieldNames = Collections
            .unmodifiableCollection(UsageCriteria.getContainedAttributeNames());

    /**
     * @return the names of the attributes contained in a quintuple. Note that "module" isn't an attribute, so it's not
     * part of the result.
     *
     * @deprecated Move to a better place.
     */
    public static Collection<String> getUsageCriteriaFieldNames() {
        return collUsageCriteriaFieldNames;
    }

    private final Logger logSecurity = Logger.getLogger(LOG.getName() + ".security");

    private static final String PREFS_KEY_FILTERNAME = "filterName";

    private static final String PREFS_KEY_SEARCHRESULTTEMPLATENAME = "searchResultTemplateName";

    private static final String CALCULATED_ATTRS_WORKER_NAME = "calculatedAttrsWorker";

    private static final String TABSELECTED = "tabselected";

    private CollectableComboBox clctSearchState;

    private Set<ReloadLayoutWorker> runningRefreshs = Collections
            .synchronizedSet(new HashSet<ReloadLayoutWorker>());

    private final class ReloadLayoutWorker extends SwingWorker<Object, Object> {

        private final Runnable runnable;

        private ReloadLayoutWorker(Runnable runnable) {
            this.runnable = runnable;
        }

        @Override
        protected Object doInBackground() throws Exception {
            try {
                Thread.sleep(500);
            } catch (Exception ex) {
                // ignore.
            }
            return null;
        }

        @Override
        protected void done() {
            try {
                if (!isCancelled()) {
                    UIUtils.runCommandLater(getTab(), runnable);
                }
            } finally {
                runningRefreshs.remove(this);
            }
        }
    }

    private void releasePreviousRefreshs() {
        synchronized (runningRefreshs) {
            for (ReloadLayoutWorker refresh : new HashSet<ReloadLayoutWorker>(runningRefreshs)) {
                if (refresh.isDone())
                    continue;

                if (!refresh.isCancelled()) {
                    if (!refresh.cancel(true)) {
                        LOG.debug("Failed to cancel refresh for layout");
                    }
                }
            }
        }
    };

    private final CollectableComponentModelListener ccmlistenerUsageCriteriaFieldsForDetails = new CollectableComponentModelAdapter() {
        @Override
        public void collectableFieldChangedInModel(CollectableComponentModelEvent ev) {
            if (ev.collectableFieldHasChanged() || ev.getNewValue().getValue() == null) {
                final String sFieldName = ev.getCollectableComponentModel().getFieldName();
                LOG.debug("UsageCriteria field " + sFieldName + " changed in Details panel.");

                releasePreviousRefreshs();

                ReloadLayoutWorker rvlr = new ReloadLayoutWorker(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //assert getCollectStateModel().getOuterState() == CollectStateConstants.OUTERSTATE_DETAILS;
                            if (getCollectStateModel().getOuterState() != CollectStateConstants.OUTERSTATE_DETAILS)
                                return; // @see     NUCLOS-708 assertation error on delete object physically. invoke later...

                            GenericObjectCollectController.this.stopEditingInDetails();

                            CollectableGenericObjectWithDependants selectedCollectable = GenericObjectCollectController.this
                                    .getSelectedCollectable();
                            if (selectedCollectable != null && selectedCollectable.getId() != null) {
                                subFormsLoader.suspendRunningClientWorkers();
                                subFormsLoader.addSuspendedClientWorker(CALCULATED_ATTRS_WORKER_NAME); //explicit mark for reloading of calc. attrs. for new layout
                            }

                            synchronized (this) {
                                iCurrentLayoutId = null; // reset current loaded layout. @see NUCLOS-1085
                                reloadLayoutForDetailsTab(true);

                                if (sFieldName.equals(NuclosEOField.PROCESS.getMetaData().getField()))
                                    showCustomActions(getCollectStateModel().getDetailsMode());
                            }
                            if (selectedCollectable != null && selectedCollectable.getId() != null) {
                                subFormsLoader.startLoading();
                                for (final SubFormController subformctl : GenericObjectCollectController.this
                                        .getSubFormControllersInDetails()) {
                                    // TODO try to eliminate this cast
                                    final MasterDataSubFormController mdsubformctl = (MasterDataSubFormController) subformctl;
                                    fillSubformMultithreaded(selectedCollectable, mdsubformctl);
                                }
                                subFormsLoader.resume();
                            }

                            UIUtils.runShortCommandLater(getTab(), new CommonRunnable() {
                                @Override
                                public void run() throws CommonBusinessException {
                                    Utils.setComponentFocus(sFieldName, getDetailsPanel().getEditView(), null,
                                            false);
                                }
                            });
                        } catch (Exception ex) {
                            Errors.getInstance().showExceptionDialog(getTab(),
                                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.15",
                                            "Beim Nachladen eines Layouts ist ein Fehler aufgetreten."),
                                    ex);
                        }
                    }
                });
                runningRefreshs.add(rvlr);

                rvlr.execute();
            }
        }
    };

    private final CollectableComponentModelListener ccmlistenerUsageCriteriaFieldsForSearch = new CollectableComponentModelAdapter() {
        @Override
        public void collectableFieldChangedInModel(final CollectableComponentModelEvent ev) {
            if (ev.collectableFieldHasChanged()) {
                final String sFieldName = ev.getCollectableComponentModel().getFieldName();
                LOG.debug("UsageCriteria field " + sFieldName + " changed in Search panel. New value: "
                        + ev.getNewValue());

                releasePreviousRefreshs();

                ReloadLayoutWorker rvlr = new ReloadLayoutWorker(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            reloadLayoutForSearchTab();

                            Utils.setComponentFocus(sFieldName, getSearchPanel().getEditView(), null, false);
                        } catch (Exception ex) {
                            Errors.getInstance().showExceptionDialog(getTab(),
                                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.16",
                                            "Beim Nachladen eines Layouts ist ein Fehler aufgetreten."),
                                    ex);
                        }
                    }
                });
                runningRefreshs.add(rvlr);

                rvlr.execute();
            }
        }

    };

    private final CollectableComponentModelListener ccmlistenerSearchChanged = new CollectableComponentModelAdapter() {
        @Override
        public void searchConditionChangedInModel(SearchComponentModelEvent ev) {
            // not true/valid for BMW_FDM, hence commented out (tp)
            // assert GenericObjectCollectController.this.getCollectStateModel().getOuterState() == CollectStateConstants.OUTERSTATE_SEARCH;

            // Note that we want to call "searchChanged()" on every change, not only valid changes:
            GenericObjectCollectController.this.searchChanged(ev.getSearchComponentModel());
        }
    };

    private final CollectableEventListener collectableEventListener = new CollectableEventListener() {
        @Override
        public void handleCollectableEvent(Collectable collectable, MessageType messageType) {
            switch (messageType) {
            case REFRESH_DONE:
                resetTransferedDetailsData();
                break;
            case NEW_DONE:
            case EDIT_DONE:
                resetTransferedDetailsData();
                break;
            }
        }
    };

    private static int iFilter;

    protected boolean bUseInvalidMasterData = false;

    private final Integer iModuleId;

    private boolean bGenerated = false;
    private GeneratorActionVO oGeneratorAction;
    private Collection<Long> iGenericObjectIdSources;

    private final GenericObjectDelegate lodelegate = GenericObjectDelegate.getInstance();

    //protected final JButton btnDeletePhysicallyInDetails = new JButton();
    protected final JMenuItem btnDeletePhysicallyInDetails = new JMenuItem();
    protected final JMenuItem btnDeletePhysicallyInResult = new JMenuItem();
    //protected final JComboBox cmbbxSearchDeleted = new JComboBox();
    protected final ButtonGroup btnGrpSearchDeleted = new ButtonGroup();
    protected final JRadioButtonMenuItem[] miSearchDeleted = new JRadioButtonMenuItem[] {
            new JRadioButtonMenuItem(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.59",
                    "Nur ungel\u00f6schte suchen")),
            new JRadioButtonMenuItem(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.58",
                    "Nur gel\u00f6schte suchen")),
            new JRadioButtonMenuItem(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.49",
                    "Gel\u00f6schte und ungel\u00f6schte suchen")) };

    // The following should be final but can't because they are
    // reset to null in close() to avoid memory leaks. (tp)

    private JCheckBoxMenuItem chkbxUseInvalidMasterData = new JCheckBoxMenuItem();
    private JMenuItem btnMakeTreeRoot = new JMenuItem();
    private JMenuItem btnShowStateHistory = new JMenuItem();
    private JMenuItem btnShowLogBook = new JMenuItem();
    private JButton btnPrintDetails = new JButton();
    private JMenuItem btnResetViewToTemplateUser = new JMenuItem();
    protected JButton btnPrintResults = new JButton();
    private JMenuItem btnExecuteRule = new JMenuItem();
    protected JMenuItem btnShowResultInExplorer = new JMenuItem();

    // end of Menu Items

    private final JComboBox cmbbxCurrentState = new JComboBox() {
        @Override
        protected void selectedItemChanged() {
            super.selectedItemChanged();
            Object oSelected = getSelectedItem();
            if (oSelected != null) {
                StateWrapper sw = (StateWrapper) oSelected;
                lbCurrentState.setColoredBackground(sw.getColor());
            }
        }
    };
    private final ColoredLabel lbCurrentState = new ColoredLabel(cmbbxCurrentState,
            getSpringLocaleDelegate().getMessage("GenericObjectCollectController.106", "Status"));
    private final StateViewComponent cmpStateStandardView = new StateViewComponent();
    private final List<Component> toolbarCustomActionsDetails = new ArrayList<Component>();
    private int toolbarCustomActionsDetailsIndex = -1;
    private LayoutRoot layoutrootDetails;

    private static final Color colorHistoricalNotTracked = Utils
            .translateColorFromParameter(ParameterProvider.KEY_HISTORICAL_STATE_NOT_TRACKED_COLOR);
    private static final Color colorHistoricalChanged = Utils
            .translateColorFromParameter(ParameterProvider.KEY_HISTORICAL_STATE_CHANGED_COLOR);

    /**
     * Map<String sEntityName, DetailsSubFormController>
     */
    private Map<String, DetailsSubFormController<CollectableEntityObject>> mpsubformctlDetails;

    /**
     * Map<String sEntityName, SearchConditionSubFormController>
     */

    private Date dateHistorical;
    //private boolean bEnteringHistoricalMode = false;

    private final CollectableEntityField clctefHistoricalDate = new DefaultCollectableEntityField("historicalDate",
            Date.class, null, null, null, null, true, CollectableField.TYPE_VALUEFIELD, null, null,
            "historicalDate", null);

    private final CollectableDateChooser clctdatechooserHistorical = new CollectableDateChooser(
            clctefHistoricalDate, false);

    private final CollectableComponentModelListener documentlistenerHistoricalDateChooser = new CollectableComponentModelAdapter() {
        @Override
        public void collectableFieldChangedInModel(CollectableComponentModelEvent ev) {
            cmdHistoricalDateChanged();
        }
    };

    /**
     * shortcut for performance. After update, the layout needn't be reloaded if the quintuple fields didn't change.
     */
    protected boolean bReloadLayout = true;

    private boolean bInitialSearchLayout = false;

    /**
     * avoids recursively calling reloadLayout
     */
    private boolean bReloadingLayout = false;

    /**
     * remember adding/removing quintuple field listeners
     */
    private boolean[] abUsageCriteriaFieldListenersAdded = new boolean[2];

    /**
     * remember if current record is readable/writable
     */
    private Object lockCurrRecReadable = new Object();
    private Boolean blnCurrentRecordReadable = null;
    private Object lockCurrRecWritable = new Object();
    private Boolean blnCurrentRecordWritable = null;

    private Map<String, DetailsComponentModel> transferredDetailsData = new HashMap<String, DetailsComponentModel>();

    private Integer iSearchDeleted = CollectableGenericObjectSearchExpression.SEARCH_UNDELETED;

    /**
     * action: Delete selected Collectable (in Result panel)
     */
    final Action actDeleteSelectedCollectablesPhysically = new CommonAbstractAction(
            getSpringLocaleDelegate().getMessage("GenericObjectCollectController.11",
                    "Ausgew\u00e4hlte Datens\u00e4tze endg\u00fcltig l\u00f6schen"),
            Icons.getInstance().getIconRealDelete16(),
            getSpringLocaleDelegate().getMessage("GenericObjectCollectController.11",
                    "Ausgew\u00e4hlte Datens\u00e4tze endg\u00fcltig l\u00f6schen")) {

        @Override
        public void actionPerformed(ActionEvent ev) {
            cmdDeleteSelectedCollectablesPhysically();
        }
    };

    final Action actDeleteSelectedCollectables = new CommonAbstractAction(
            getSpringLocaleDelegate().getMessage("GenericObjectCollectController.52", "L\u00f6schen..."),
            Icons.getInstance().getIconDelete16(), getSpringLocaleDelegate().getMessage(
                    "GenericObjectCollectController.12", "Ausgew\u00e4hlte Datens\u00e4tze l\u00f6schen")) {

        @Override
        public void actionPerformed(ActionEvent ev) {
            cmdDeleteSelectedCollectables();
        }
    };

    final Action actDeleteCurrentCollectableInDetails = new CommonAbstractAction(
            getSpringLocaleDelegate().getMessage("GenericObjectCollectController.53", "L\u00f6schen..."),
            Icons.getInstance().getIconDelete16(), getSpringLocaleDelegate()
                    .getMessage("GenericObjectCollectController.37", "Diesen Datensatz l\u00f6schen")) {

        @Override
        public void actionPerformed(ActionEvent ev) {
            cmdDeleteCurrentCollectableInDetails();
        }
    };

    final Action actRestoreSelectedCollectables = new CommonAbstractAction(
            getSpringLocaleDelegate().getMessage("GenericObjectCollectController.98", "Wiederherstellen..."),
            Icons.getInstance().getIconDelete16(), getSpringLocaleDelegate().getMessage(
                    "GenericObjectCollectController.13", "Ausgew\u00e4hlte Datens\u00e4tze wiederherstellen")) {

        @Override
        public void actionPerformed(ActionEvent ev) {
            cmdRestoreSelectedCollectables();
        }
    };

    final Action actRestoreCurrentCollectableInDetails = new CommonAbstractAction(
            getSpringLocaleDelegate().getMessage("GenericObjectCollectController.99", "Wiederherstellen"),
            Icons.getInstance().getIconDelete16(), getSpringLocaleDelegate()
                    .getMessage("GenericObjectCollectController.38", "Diesen Datensatz wiederherstellen")) {

        @Override
        public void actionPerformed(ActionEvent ev) {
            cmdRestoreCurrentCollectableInDetails();
        }
    };

    private MultiUpdateOfDependants multiupdateofdependants;

    /**
     * controller for search result templates
     *
     * @deprecated Move to GenericObjectResultController.
     */
    private SearchResultTemplateController searchResultTemplatesController;

    /**
     * is current thread (multi-update?) processing a state change?
     */
    private final ThreadLocal<Boolean> isProcessingStateChange = new ThreadLocal<Boolean>() {
        @Override
        protected Boolean initialValue() {
            return Boolean.FALSE;
        }
    };

    private CollectableField process;

    /**
     * Use the static method <code>newGenericObjectCollectController</code> to create new instances.
     *
     * You should use {@link org.nuclos.client.ui.collect.CollectControllerFactorySingleton}
     * to get an instance.
     *
     * @param parent
     * @param iModuleId
     * @param bAutoInit TODO
     *
     * @deprecated You should normally do sth. like this:<code><pre>
     * ResultController<~> rc = new ResultController<~>();
     * *CollectController<~> cc = new *CollectController<~>(.., rc);
     * </code></pre>
     *
     * @deprecated bAutoInit is deprecated
     */
    public GenericObjectCollectController(Integer iModuleId, boolean bAutoInit, MainFrameTab tabIfAny,
            String customUsage) {
        super(CollectableGenericObjectEntity.getByModuleId(iModuleId), tabIfAny,
                new GenericObjectResultController<CollectableGenericObjectWithDependants>(
                        CollectableGenericObjectEntity.getByModuleId(iModuleId),
                        new NuclosSearchResultStrategy<CollectableGenericObjectWithDependants>()),
                customUsage);
        this.iModuleId = iModuleId;
        // getSearchStrategy().setCompleteCollectablesStrategy(new CompleteGenericObjectsStrategy());
        if (bAutoInit)
            init();
    }

    /**
     * You should use {@link org.nuclos.client.ui.collect.CollectControllerFactorySingleton}
     * to get an instance.
     *
     * @deprecated You should normally do sth. like this:<code><pre>
     * ResultController<~> rc = new ResultController<~>();
     * *CollectController<~> cc = new *CollectController<~>(.., rc);
     * </code></pre>
     */
    protected GenericObjectCollectController(Integer iModuleId, boolean bAutoInit, MainFrameTab tabIfAny,
            ResultController<CollectableGenericObjectWithDependants> rc, String customUsage) {
        super(CollectableGenericObjectEntity.getByModuleId(iModuleId), tabIfAny, rc, customUsage);
        this.iModuleId = iModuleId;
        // getSearchStrategy().setCompleteCollectablesStrategy(new CompleteGenericObjectsStrategy());
        if (bAutoInit)
            init();
    }

    protected CollectableComboBox getSearchStateBox() {
        if (clctSearchState == null) {
            final CollectableEntityField clctefSearchState = new DefaultCollectableEntityField(
                    "[status_num_plus_name]", String.class,
                    getSpringLocaleDelegate().getLabelFromAttributeCVO(AttributeCache.getInstance()
                            .getAttribute(NuclosEOField.STATE.getMetaData().getId().intValue())),
                    null, 255, null, true, CollectableField.TYPE_VALUEIDFIELD, null, null,
                    getCollectableEntity().getName(), null);
            clctSearchState = new NuclosCollectableStateComboBox(clctefSearchState, true);
        }
        return clctSearchState;
    }

    public final Integer getSearchDeleted() {
        return iSearchDeleted;
    }

    /**
     * @deprecated Move to GenericObjectResultController.
     */
    public final SearchResultTemplateController getSearchResultTemplateController() {
        return searchResultTemplatesController;
    }

    /**
     * refactoring to avoid dirty use of SwingUtilities.invokeLater()
     * allows local variable initialization of extenders [FS]
     *
     * TODO: Make this protected again.
     */
    public void init() {
        _setCollectPanel(newCollectPanel(getModuleId().longValue()));
        super.init();

        final CollectPanel<CollectableGenericObjectWithDependants> collectPanel = getCollectPanel();
        initialize(collectPanel);

        final MainFrameTab tab = getTab();
        tab.setLayeredComponent(collectPanel);

        setupEditPanels();
        setupKeyActionsForResultPanelVerticalScrollBar();
        setupShortcutsForTabs(tab);
        setupToolbars();

        setupResultTableHeaderRenderer();
        setupAdditionalActions();
        setupDataTransfer();
        getCollectStateModel().addCollectStateListener(new GenericObjectCollectStateListener());

        addCollectableEventListener(collectableEventListener);

        setupResultContextMenuGeneration();
        setupResultContextMenuStates();
    }

    private void setupToolbars() {
        if (this.isSearchPanelAvailable())
            setupSearchToolBar();
        setupResultToolBar();
        setupDetailsToolBar();
        refreshFastFilter();
    }

    @Override
    protected void setupSearchToolBar() {
        // additional functionality in Search panel:
        //final JPanel pnlCustomToolBarAreaSearch = new JPanel();
        //pnlCustomToolBarAreaSearch.setLayout(new GridBagLayout());

        super.setupSearchToolBar();

        //final JToolBar toolbarSearchCustom = newCustomSearchToolBar();
        if (SecurityCache.getInstance().isActionAllowed(Actions.ACTION_READ_DELETED_RECORD)) {
            //toolbarSearchCustom.add(Box.createHorizontalStrut(5));

            //cmbbxSearchDeleted.addItem(SpringLocaleDelegate.getMessage("GenericObjectCollectController.59","Nur ungel\u00f6schte suchen"));
            //cmbbxSearchDeleted.addItem(SpringLocaleDelegate.getMessage("GenericObjectCollectController.58","Nur gel\u00f6schte suchen"));
            //cmbbxSearchDeleted.addItem(SpringLocaleDelegate.getMessage("GenericObjectCollectController.49","Gel\u00f6schte und ungel\u00f6schte suchen"));

            btnGrpSearchDeleted.add(miSearchDeleted[0]);
            btnGrpSearchDeleted.add(miSearchDeleted[1]);
            btnGrpSearchDeleted.add(miSearchDeleted[2]);
            btnGrpSearchDeleted.setSelected(miSearchDeleted[0].getModel(), true);

            //UIUtils.setMaximumSizeToPreferredSize(cmbbxSearchDeleted);
            //toolbarSearchCustom.add(cmbbxSearchDeleted);
            //this.getSearchPanel().addToolBarComponent(cmbbxSearchDeleted);
            this.getSearchPanel().addPopupExtraSeparator();
            this.getSearchPanel().addPopupExtraMenuItem(miSearchDeleted[0]);
            this.getSearchPanel().addPopupExtraMenuItem(miSearchDeleted[1]);
            this.getSearchPanel().addPopupExtraMenuItem(miSearchDeleted[2]);

            /*cmbbxSearchDeleted.addItemListener(new ItemListener() {
               @Override
               public void itemStateChanged(ItemEvent ev) {
                  if (ev.getStateChange() == ItemEvent.SELECTED)
              iSearchDeleted = cmbbxSearchDeleted.getSelectedIndex();
               }
            });*/
            ItemListener il = new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent ev) {
                    if (ev.getStateChange() == ItemEvent.SELECTED) {
                        for (int i = 0; i < miSearchDeleted.length; i++) {
                            if (ev.getSource() == miSearchDeleted[i])
                                iSearchDeleted = i;
                        }
                    }
                }
            };
            miSearchDeleted[0].addItemListener(il);
            miSearchDeleted[1].addItemListener(il);
            miSearchDeleted[2].addItemListener(il);
        }

        refreshFilterView();

        // glue:
        //final JToolBar toolbarGlue = UIUtils.createNonFloatableToolBar();

        // add toolbars to custom toolbar area:
        /*final GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.VERTICAL;
        gbc.weightx = 0.0;
        gbc.weighty = 1.0;
        pnlCustomToolBarAreaSearch.add(toolbarSearchCustom, gbc);
        gbc.fill = GridBagConstraints.BOTH;
        gbc.weightx = 1.0;
        gbc.weighty = 1.0;
        pnlCustomToolBarAreaSearch.add(toolbarGlue, gbc);
        gbc.fill = GridBagConstraints.VERTICAL;
        gbc.weightx = 0.0;
        gbc.weighty = 1.0;
        JToolBar toolSearchState = UIUtils.createNonFloatableToolBar();
        */
        JComboBox jComboBox = getSearchStateBox().getJComboBox();
        //UIUtils.setMaximumSizeToPreferredSize(jComboBox);
        jComboBox.setToolTipText(
                getSpringLocaleDelegate().getMessage("GenericObjectCollectController.8", "Aktueller Status"));
        //jComboBox.setOpaque(false);
        //CenteringPanel cpSearchState = new CenteringPanel(jComboBox);
        //cpSearchState.setOpaque(false);
        //toolSearchState.add(cpSearchState);
        //toolSearchState.add(Box.createHorizontalStrut(5));
        ColoredLabel lbSearchBox = new ColoredLabel(jComboBox,
                getSpringLocaleDelegate().getMessage("nuclos.entityfield.eo.state.label", "Status"));
        lbSearchBox.setName("blSearchState");
        this.getSearchPanel().addToolBarComponent(lbSearchBox, 6);

        //pnlCustomToolBarAreaSearch.add(toolSearchState, gbc);
        setSearchStatesAccordingToUsageCriteria(new UsageCriteria(iModuleId, null, null, getCustomUsage()));
        //getSearchPanel().setCustomToolBarArea(pnlCustomToolBarAreaSearch);
    }

    /**
     * sets up a modified renderer for the result table header that uses the HTML formatted toString() value of the
     * column's entity field rather than its unformatted getLabel() value.
     */
    private void setupResultTableHeaderRenderer() {
        final JTableHeader header = getResultTable().getTableHeader();
        final TableCellRenderer headerrendererDefault = header.getDefaultRenderer();
        header.setDefaultRenderer(new TableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable tbl, Object oValue, boolean bSelected,
                    boolean bHasFocus, int iRow, int iColumn) {
                final CollectableEntityField f = GenericObjectCollectController.this.getResultTableModel()
                        .getCollectableEntityField(tbl.convertColumnIndexToModel(iColumn));
                final String value;
                if (f instanceof CollectableEOEntityField) {
                    // In the pivot case, we want to separate the column name
                    // from the tool top.
                    final CollectableEOEntityField field = (CollectableEOEntityField) f;
                    final EntityFieldMetaDataVO ef = field.getMeta();
                    if (ef.getPivotInfo() != null) {
                        value = ef.getField() + ":" + ef.getPivotInfo().getValueField();
                    } else {
                        value = f.getLabel();
                    }
                } else {
                    value = f.getLabel();
                }
                return headerrendererDefault.getTableCellRendererComponent(tbl, value, bSelected, bHasFocus, iRow,
                        iColumn);
            }
        });
    }

    @Override
    public void close() {
        closeSubFormControllersInSearch();
        closeSubFormControllers(getSubFormControllersInDetails());
        resetTransferedDetailsData();

        toolbarCustomActionsDetails.clear();

        chkbxUseInvalidMasterData = null;
        btnMakeTreeRoot = null;
        btnShowStateHistory = null;
        btnShowLogBook = null;
        btnPrintDetails = null;
        btnResetViewToTemplateUser = null;
        btnPrintResults = null;
        btnExecuteRule = null;
        btnShowResultInExplorer = null;

        super.close();
    }

    private void setupKeyActionsForResultPanelVerticalScrollBar() {
        // maps the default key strokes for JTable to set the vertical scrollbar, so we can intervent:
        final InputMap inputmap = getResultTable().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        inputmap.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, InputEvent.CTRL_MASK), "last");
        inputmap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "nextrow");
        inputmap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "nextpage");

        final JScrollBar scrlbarVertical = getResultPanel().getResultTableScrollPane().getVerticalScrollBar();
        final DefaultBoundedRangeModel model = (DefaultBoundedRangeModel) scrlbarVertical.getModel();

        getResultTable().getActionMap().put("last", new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent ev) {
                final int iSupposedValue = model.getMaximum() - model.getExtent();
                model.setValue(iSupposedValue);
                // this causes the necessary rows to be loaded. Loading may be cancelled by the user.
                LOG.debug("NOW it's time to select the row...");
                if (model.getValue() == iSupposedValue)
                    getCollectNavigationModel().selectLastElement();
            }
        });

        getResultTable().getActionMap().put("nextrow", new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent ev) {
                final int iSelectedRow = getResultTable().getSelectedRow();
                final int iLastVisibleRow = TableUtils.getLastVisibleRow(getResultTable());
                if (iSelectedRow + 1 < iLastVisibleRow) {
                    // next row is still visible: just select it:
                    if (!getCollectNavigationModel().isLastElementSelected())
                        getCollectNavigationModel().selectNextElement();
                } else {
                    // we have to move the viewport before we can select the next row:
                    final int iSupposedValue = Math.min(model.getValue() + getResultTable().getRowHeight(),
                            model.getMaximum() - model.getExtent());
                    model.setValue(iSupposedValue);
                    // this causes the necessary rows to be loaded. Loading may be cancelled by the user.
                    LOG.debug("NOW it's time to select the row...");
                    if (model.getValue() == iSupposedValue)
                        if (!getCollectNavigationModel().isLastElementSelected())
                            getCollectNavigationModel().selectNextElement();
                }
            }
        });

        getResultTable().getActionMap().put("nextpage", new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent ev) {
                final int iSupposedValue = Math.min(model.getValue() + model.getExtent(),
                        model.getMaximum() - model.getExtent());
                model.setValue(iSupposedValue);
                // this causes the necessary rows to be loaded. Loading may be cancelled by the user.
                LOG.debug("NOW it's time to select the row...");
                if (model.getValue() == iSupposedValue) {
                    final int iShiftRowCount = (int) Math
                            .ceil((double) model.getExtent() / (double) getResultTable().getRowHeight());
                    final int iRow = Math.min(
                            getResultTable().getSelectionModel().getAnchorSelectionIndex() + iShiftRowCount,
                            getResultTable().getRowCount() - 1);
                    getResultTable().setRowSelectionInterval(iRow, iRow);
                }
            }
        });

        final Action actShowLogBook = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent ev) {
                cmdShowLogBook();
                getDetailsPanel().grabFocus();
            }
        };
        KeyBindingProvider.bindActionToComponent(KeyBindingProvider.SHOW_LOGBOOK, actShowLogBook,
                getDetailsPanel());

        final Action actShowStateHistory = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent ev) {
                cmdShowStateHistory();
                getDetailsPanel().grabFocus();
            }
        };
        KeyBindingProvider.bindActionToComponent(KeyBindingProvider.SHOW_STATE_HISTORIE, actShowStateHistory,
                getDetailsPanel());

        final Action actPrintCurrentGenericObject = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent ev) {
                cmdPrintCurrentGenericObject();
                getDetailsPanel().grabFocus();
            }
        };
        KeyBindingProvider.bindActionToComponent(KeyBindingProvider.PRINT_LEASED_OBJECT,
                actPrintCurrentGenericObject, getDetailsPanel());
    }

    /**
     * enables drag and copy from rows.
     */
    private void setupDataTransfer() {
        // enable drag&drop:
        prepareTableForDragAndDrop(getResultPanel().getResultTable());
        prepareTableForDragAndDrop((getResultPanel()).getFixedResultTable());
    }

    private void prepareTableForDragAndDrop(final JTable tbl) {
        tbl.setDragEnabled(true);
        tbl.setTransferHandler(new TransferHandler() {

            @Override
            public int getSourceActions(JComponent comp) {
                int result = NONE;
                if (comp == tbl)
                    if (GenericObjectCollectController.this.getSelectedCollectable() != null)
                        result = COPY;
                return result;
            }

            @Override
            protected Transferable createTransferable(JComponent comp) {
                Transferable result = null;
                if (comp == tbl) {
                    final int[] aiSelectedRows = tbl.getSelectedRows();

                    final List<GenericObjectIdModuleProcess> lstgoimp = new ArrayList<GenericObjectIdModuleProcess>(
                            aiSelectedRows.length);
                    for (int iSelectedRow : aiSelectedRows) {
                        final CollectableGenericObjectWithDependants clct = getResultTableModel()
                                .getCollectable(iSelectedRow);
                        GenericObjectIdModuleProcess transferableObject = getTransferableObject(tbl, iSelectedRow,
                                clct);
                        lstgoimp.add(transferableObject);
                    }
                    if (!lstgoimp.isEmpty())
                        result = new TransferableGenericObjects(lstgoimp);
                }
                return result;
            }

            private GenericObjectIdModuleProcess getTransferableObject(final JTable tbl, int iSelectedRow,
                    final CollectableGenericObjectWithDependants clct) {
                return new GenericObjectIdModuleProcess(clct, getTreeViewIdentifier(clct),
                        getContents(tbl, iSelectedRow));
            }

            private String getContents(JTable tbl, int iRow) {
                final StringBuffer sb = new StringBuffer();
                final int iColumnCount = tbl.getColumnCount();

                for (int iColumn = 0; iColumn < iColumnCount; iColumn++) {
                    final String sValue = tbl.getValueAt(iRow, iColumn).toString();
                    if (sValue.indexOf("\n") >= 0 || sValue.indexOf("\t") >= 0) {
                        sb.append("\"");
                        sb.append(sValue.replaceAll("\"", "\"\""));
                        sb.append("\"");
                    } else {
                        sb.append(sValue);
                    }
                    if (iColumn < iColumnCount - 1)
                        sb.append("\t");
                }
                return sb.toString();
            }
        });
    }

    protected final boolean isHistoricalView() {
        return dateHistorical != null;
    }

    protected void setHistoricalDate(Date dateHistorical) {
        this.dateHistorical = dateHistorical;

        updateHistoricalDateChooser(dateHistorical);
    }

    protected CollectableDateChooser getHistoricalCollectableDateChooser() {
        return clctdatechooserHistorical;
    }

    private void updateHistoricalDateChooser(final Date date) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                synchronized (GenericObjectCollectController.this) {
                    unregisterHistoricalDateChooserListener();
                    clctdatechooserHistorical.getDateChooser().setDate(date);
                    registerHistoricalDateChooserListener();
                }
            }
        });
    }

    private void registerHistoricalDateChooserListener() {
        clctdatechooserHistorical.getModel()
                .addCollectableComponentModelListener(documentlistenerHistoricalDateChooser);
    }

    private void unregisterHistoricalDateChooserListener() {
        clctdatechooserHistorical.getModel()
                .removeCollectableComponentModelListener(documentlistenerHistoricalDateChooser);
    }

    private synchronized void cmdHistoricalDateChanged() {
        boolean bDateValid;
        Date dateHistorical = null;
        try {
            dateHistorical = (Date) clctdatechooserHistorical.getField().getValue();
            if (dateHistorical == null && getHistoricalCollectableDateChooser().getDateChooser().getDate() != null)
                dateHistorical = getHistoricalCollectableDateChooser().getDateChooser().getDate();
            bDateValid = true;
        } catch (CommonValidationException ex) {
            bDateValid = false;
        }

        if (bDateValid) {
            // if the historical date is greater than "today", set it to null,
            // in order to show the current object:
            if (dateHistorical != null)
                if (DateUtils.getPureDate(dateHistorical).compareTo(DateUtils.getPureDate(new Date())) >= 0)
                    dateHistorical = null;

            if (!LangUtils.equals(this.dateHistorical, dateHistorical)) {
                final Date dateLastValid = this.dateHistorical;
                setHistoricalDate(dateHistorical);

                UIUtils.runCommandLater(getTab(), new CommonRunnable() {
                    @Override
                    public void run() throws CommonBusinessException {
                        final CollectableGenericObjectWithDependants clct;
                        if (GenericObjectCollectController.this.dateHistorical == null)
                            clct = readSelectedCollectable();
                        else {
                            final GenericObjectWithDependantsVO lowdcvoHistorical;
                            try {
                                assert GenericObjectCollectController.this.dateHistorical != null;
                                lowdcvoHistorical = lodelegate.getHistorical(getSelectedGenericObjectId(),
                                        GenericObjectCollectController.this.dateHistorical, getCustomUsage());

                                // remember if the layout needs to be reloaded afterwards:
                                if (lowdcvoHistorical
                                        .getUsageCriteria(AttributeCache.getInstance(), getCustomUsage())
                                        .equals(getUsageCriteria(getSelectedCollectable())))
                                    //GenericObjectCollectController.this.bReloadLayout = false;
                                    removeUsageCriteriaFieldListeners(false);

                            } catch (CommonFinderException ex) {
                                // "rollback":
                                setHistoricalDate(dateLastValid);
                                throw ex;
                            }
                            clct = new CollectableGenericObjectWithDependants(lowdcvoHistorical);
                        }
                        getResultController().replaceSelectedCollectableInTableModel(clct);
                        cmdEnterViewMode();
                        disableToolbarButtonsForHistoricalView();
                    }
                });
            } else
                try {
                    // check if we need to update the datechooser view:
                    if (!LangUtils.equals(clctdatechooserHistorical.getDateChooser().getDate(), dateHistorical))
                        updateHistoricalDateChooser(dateHistorical);
                } catch (CommonValidationException ex) {
                    // Historical date chooser can contain only valid dates, as it is only possible to choose them from the popup; so this may not occur!
                    throw new CommonFatalException(ex);
                }
        }
    }

    @Override
    protected boolean isSaveAllowed() {
        synchronized (lockCurrRecWritable) {
            blnCurrentRecordWritable = null;
        }
        return isCurrentRecordWritable() && isNotLoadingSubForms();
    }

    /**
     * @return true
     */
    @Override
    protected boolean isMultiEditAllowed() {
        return true;
    }

    private boolean isCurrentRecordReadable() {
        CollectableGenericObjectWithDependants clctgowd = getSelectedCollectable();

        synchronized (lockCurrRecReadable) {
            if (blnCurrentRecordReadable == null)
                blnCurrentRecordReadable = this.isReadAllowed(clctgowd);
            return blnCurrentRecordReadable;
        }
    }

    protected boolean isCurrentRecordWritable() {
        if (isHistoricalView() || isSelectedCollectableMarkedAsDeleted())
            return false;

        if (!MetaDataClientProvider.getInstance().getEntity(getEntityName()).isEditable())
            return false;
        final Integer iModuleIdSelected = getSelectedCollectableModuleId();
        final Integer iGenericObjectId = (Integer) getSelectedCollectableId();

        synchronized (lockCurrRecWritable) {
            if (blnCurrentRecordWritable == null)
                blnCurrentRecordWritable = SecurityCache.getInstance()
                        .isWriteAllowedForModule(this.getEntityName(iModuleIdSelected), iGenericObjectId);

            if (!blnCurrentRecordWritable) {
                LOG.debug("Speichern nicht erlaubt fuer das Modul " + getModuleLabel(iModuleIdSelected));
                return false;
            }
        }
        return true;
    }

    /**
     * generic objects may deleted if we are not in historical view <code>AND</code>
     * ((Delete is allowed for this module and the current user and the user has the right to write the object)
     * <code>OR</code> this object is in its initial state and the current user created this object.)
     * @param clct
     * @return Is the "Delete" action for the given Collectable allowed?
     */
    @Override
    protected boolean isDeleteAllowed(CollectableGenericObjectWithDependants clct) {
        final boolean result;

        if (isHistoricalView()) {
            LOG.debug("isDeleteAllowed: historical view");
            result = false;
        } else if (!MetaDataClientProvider.getInstance().getEntity(getEntityName()).isEditable())
            return false;
        else
            result = hasCurrentUserDeletionRights(clct, false);
        LOG.debug("isDeleteAllowed == " + result);

        return result;
    }

    /**
     * @return Is the "Read" action for the given Collectable allowed? May be overridden by subclasses.
     * @precondition clct != null
     */
    @Override
    protected boolean isReadAllowed(CollectableGenericObjectWithDependants clct) {
        return SecurityCache.getInstance().isReadAllowedForModule(getEntityName(),
                (clct == null) ? null : clct.getId());
    }

    /**
     * @return Is the "Read" action for the given set of Collectables allowed? May be overridden by subclasses.
     * @precondition clct != null
     */
    @Override
    protected boolean isReadAllowed(List<CollectableGenericObjectWithDependants> lsClct) {
        for (CollectableGenericObjectWithDependants collgowd : lsClct)
            if (!SecurityCache.getInstance().isReadAllowedForModule(getEntityName(), collgowd.getId()))
                return false;
        return true;
    }

    private boolean hasCurrentUserDeletionRights(CollectableGenericObject clct, boolean physically) {
        final GenericObjectVO govo = clct.getGenericObjectCVO();
        boolean result = false;
        if (getCollectStateModel().getOuterState() == CollectState.OUTERSTATE_RESULT)
            result = SecurityCache.getInstance().isDeleteAllowedForModule(this.getEntityName(), null, physically);
        else
            result = SecurityCache.getInstance().isDeleteAllowedForModule(this.getEntityName(), govo.getId(),
                    physically);
        if (result)
            LOG.debug("isDeleteAllowed: delete allowed for module and permission is readwrite.");
        else {
            // objects in initial state may always be deleted by their creator:
            final String sCurrentUser = getMainController().getUserName();
            final String sCreator = govo.getCreatedBy();
            LOG.debug("isDeleteAllowed: current user: " + sCurrentUser + " - creator: " + sCreator);

            if (sCurrentUser.equals(sCreator)) {
                final Integer iInitialStateId = getInitialStateId(getUsageCriteria(clct));
                final Integer iCurrentStateId = getSystemAttributeId(clct,
                        NuclosEOField.STATE.getMetaData().getField());
                assert iInitialStateId != null;
                result = iInitialStateId.equals(iCurrentStateId);
                LOG.debug("isDeleteAllowed: current state: " + iCurrentStateId + " - initial state: "
                        + iInitialStateId);
            }
        }
        return result;
    }

    protected boolean isPhysicallyDeleteAllowed(CollectableGenericObjectWithDependants clct) {
        boolean result = SecurityCache.getInstance().isActionAllowed(Actions.ACTION_DELETE_RECORD);
        if (clct != null)
            result = result && isDeleteAllowed(clct) && hasCurrentUserDeletionRights(clct, true);
        return result;
    }

    @Override
    public void setSearchDeleted(Integer iSearchDeleted) {
        if (SecurityCache.getInstance().isActionAllowed(Actions.ACTION_READ_DELETED_RECORD)) {
            this.iSearchDeleted = iSearchDeleted;
            //cmbbxSearchDeleted.setSelectedIndex(this.iSearchDeleted);   // Must not be set when user has no right (cmbbx is not filled then)!
            btnGrpSearchDeleted.setSelected(miSearchDeleted[this.iSearchDeleted].getModel(), true);
        } else
            this.iSearchDeleted = CollectableGenericObjectSearchExpression.SEARCH_UNDELETED;
    }

    /**
     * @deprecated Move to SearchController and make protected again.
     */
    @Override
    public CollectableFieldsProviderFactory getCollectableFieldsProviderFactoryForSearchEditor() {
        return GenericObjectCollectableFieldsProviderFactory.newFactory(getCollectableEntity().getName(),
                valueListProviderCache);
    }

    /**
     * This method is called by <code>cmdClearSearchFields</code>, that is when the user clicks
     * the "Clear Search Fields" button. This implementation selects the default search filter.
     */
    @Override
    protected void clearSearchCondition() {
        super.clearSearchCondition();
        // select the default filter (the first entry):
        selectDefaultFilter();
    }

    /**
     * @return the search filters that can be edited in this collect controller.
     *         By default, it's the search filters for this collect controller's module.
     */
    @Override
    protected SearchFilters getSearchFilters() {
        return SearchFilters.forEntity(this.getEntityName());
    }

    @Override
    protected SearchFilter getCurrentSearchFilterFromSearchPanel() throws CommonBusinessException {
        final EntitySearchFilter result = (EntitySearchFilter) super.getCurrentSearchFilterFromSearchPanel();
        result.setSearchDeleted(iSearchDeleted);

        // set selected columns:
        result.setVisibleColumns(getSelectedFields());

        /** @todo set sorting column names */

        return result;
    }

    private void setupResultToolBar() {
        // additional functionality in Result panel:
        //final JToolBar toolbar = UIUtils.createNonFloatableToolBar();

        //toolbar.add(Box.createHorizontalStrut(5));
        getResultPanel().addPopupExtraSeparator();

        getResultPanel().addPopupExtraMenuItem(btnShowResultInExplorer);
        if (isPhysicallyDeleteAllowed(getSelectedCollectable())) {
            //toolbar.add(btnDeletePhysicallyInResult);
            //toolbar.add(Box.createHorizontalStrut(5));
            getResultPanel().addPopupExtraMenuItem(btnDeletePhysicallyInResult);
        }
        //toolbar.add(btnShowResultInExplorer);
        //toolbar.addSeparator();
        //toolbar.add(btnPrintResults);

        getResultPanel().addToolBarComponent(btnPrintResults);

        btnShowResultInExplorer.setIcon(Icons.getInstance().getIconTree16());
        btnShowResultInExplorer.setText(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.43",
                "Ergebnis in Explorer anzeigen"));

        btnPrintResults.setIcon(Icons.getInstance().getIconPrintReport16());

        if (SecurityCache.getInstance().isActionAllowed(Actions.ACTION_PRINT_SEARCHRESULT)) {
            btnPrintResults.setEnabled(true);
            btnPrintResults.setToolTipText(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.44",
                    "Ergebnisliste drucken"));
            // action: Print results
            btnPrintResults.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent ev) {
                    cmdPrint();
                }
            });
        } else {
            btnPrintResults.setEnabled(false);
            btnPrintResults.setToolTipText(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.45",
                    "Ergebnisliste drucken - Sie verf\u00fcgen nicht \u00fcber ausreichende Rechte."));
        }

        btnDeletePhysicallyInResult.setEnabled(true);
        btnDeletePhysicallyInResult.setAction(actDeleteSelectedCollectablesPhysically);
        //btnDeletePhysicallyInResult.setText(null);

        // action: show results in explorer:
        btnShowResultInExplorer.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                cmdShowResultsInExplorer();
            }
        });

        searchResultTemplatesController = new SearchResultTemplateController(getResultPanel(), this);

        getResultTable().getSelectionModel().addListSelectionListener(deleteToggleResultListener);

        //toolbar.add(Box.createGlue());
        // adding reset to template user to toolbar in result view
        String templateUser = ClientParameterProvider.getInstance().getValue(ParameterProvider.KEY_TEMPLATE_USER);
        if (templateUser != null) {
            btnResetViewToTemplateUser.setText("Standardansicht wiederherstellen");
            btnResetViewToTemplateUser.addActionListener(new ResetToTemplateUserActionListener(this));
            getResultPanel().addPopupExtraMenuItem(btnResetViewToTemplateUser);
        }
        //getResultPanel().setCustomToolBarArea(toolbar);
    }

    /**
     * Command: Delete selected <code>Collectable</code>s in the Result panel.
     * This is mainly a copy of cmdDeleteSelectedCollectables from the ResultController, but with different messages and different actions.
     */
    protected void cmdDeleteSelectedCollectablesPhysically() {
        assert getCollectStateModel().getOuterState() == CollectState.OUTERSTATE_RESULT;
        assert CollectState.isResultModeSelected(getCollectStateModel().getResultMode());

        if (multipleCollectablesSelected()) {
            final int iCount = getResultTable().getSelectedRowCount();
            final String sMessage = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.81",
                    "Sollen die ausgew\u00e4hlten {0} Datens\u00e4tze wirklich endg\u00fcltig gel\u00f6scht werden?\nDieser Vorgang kann nicht r\u00fcckg\u00e4ngig gemacht werden!",
                    iCount);
            final int btn = JOptionPane.showConfirmDialog(getTab(), sMessage, getSpringLocaleDelegate()
                    .getMessage("GenericObjectCollectController.20", "Datens\u00e4tze endg\u00fcltig l\u00f6schen"),
                    JOptionPane.YES_NO_OPTION);
            if (btn == JOptionPane.YES_OPTION)
                new DeleteSelectedCollectablesPhysicallyController(this).run(getMultiActionProgressPanel(iCount));
        } else {
            final String sMessage = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.76",
                    "Soll der ausgew\u00e4hlte Datensatz ({0}) wirklich endg\u00fcltig gel\u00f6scht werden?\nDieser Vorgang kann nicht r\u00fcckg\u00e4ngig gemacht werden!",
                    getSelectedCollectable().getIdentifierLabel());
            final int btn = JOptionPane.showConfirmDialog(getTab(), sMessage, getSpringLocaleDelegate()
                    .getMessage("GenericObjectCollectController.23", "Datensatz endg\u00fcltig l\u00f6schen"),
                    JOptionPane.YES_NO_OPTION);

            if (btn == JOptionPane.YES_OPTION)
                UIUtils.runCommand(getTab(), new Runnable() {
                    @Override
                    public void run() {
                        try {
                            checkedDeleteCollectablePhysically(getSelectedCollectable());
                        } catch (CommonPermissionException ex) {
                            final String sErrorMsg = "Sie verf\u00fcgen nicht \u00fcber die ausreichenden Rechte, um diesen Datensatz endg\u00fcltig zu l\u00f6schen.";
                            Errors.getInstance().showExceptionDialog(getTab(), sErrorMsg, ex);
                        } catch (CommonBusinessException ex) {
                            Errors.getInstance().showExceptionDialog(getTab(),
                                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.29",
                                            "Der Datensatz konnte nicht endg\u00fcltig gel\u00f6scht werden."),
                                    ex);
                        }
                    }
                });
        }
    }

    /**
     * Command: Delete selected <code>Collectable</code>s in the Result panel.
     */
    private void cmdDeleteSelectedCollectables() {
        assert getCollectStateModel().getOuterState() == CollectState.OUTERSTATE_RESULT;
        assert CollectState.isResultModeSelected(getCollectStateModel().getResultMode());

        if (multipleCollectablesSelected()) {
            final int iCount = getResultTable().getSelectedRowCount();
            final String sMessage = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.82",
                    "Sollen die ausgew\u00e4hlten {0} Datens\u00e4tze wirklich gel\u00f6scht werden?", iCount);
            final int btn = JOptionPane.showConfirmDialog(getTab(), sMessage, getSpringLocaleDelegate()
                    .getMessage("GenericObjectCollectController.21", "Datens\u00e4tze l\u00f6schen"),
                    JOptionPane.YES_NO_OPTION);
            if (btn == JOptionPane.YES_OPTION)
                new DeleteSelectedCollectablesController<CollectableGenericObjectWithDependants>(this)
                        .run(getMultiActionProgressPanel(iCount));
            else if (btn == JOptionPane.NO_OPTION)
                getResultPanel().btnDelete.setSelected(false);
        } else {
            final String sMessage = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.77",
                    "Soll der ausgew\u00e4hlte Datensatz ({0}) wirklich gel\u00f6scht werden?",
                    getSelectedCollectable().getIdentifierLabel());
            final int btn = JOptionPane.showConfirmDialog(getTab(), sMessage, getSpringLocaleDelegate().getMessage(
                    "GenericObjectCollectController.25", "Datensatz l\u00f6schen"), JOptionPane.YES_NO_OPTION);

            if (btn == JOptionPane.YES_OPTION)
                UIUtils.runCommand(getTab(), new Runnable() {
                    @Override
                    public void run() {
                        try {
                            checkedDeleteSelectedCollectable();
                            //refreshResult();
                        } catch (CommonPermissionException ex) {
                            final String sErrorMsg = getSpringLocaleDelegate().getMessage(
                                    "GenericObjectCollectController.68",
                                    "Sie verf\u00fcgen nicht \u00fcber die ausreichenden Rechte, um diesen Datensatz zu l\u00f6schen.");
                            getResultPanel().btnDelete.setSelected(false);
                            Errors.getInstance().showExceptionDialog(getTab(), sErrorMsg, ex);
                        } catch (CommonBusinessException ex) {
                            getResultPanel().btnDelete.setSelected(false);
                            //Errors.getInstance().showExceptionDialog(getFrame(), SpringLocaleDelegate.getMessage("GenericObjectCollectController.30","Der Datensatz konnte nicht gel\u00f6scht werden."), ex);
                            Errors.getInstance().showExceptionDialog(getTab(),
                                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.30",
                                            "Der Datensatz konnte nicht gel\u00f6scht werden."),
                                    new CommonRemoveException());
                        }
                    }
                });
            else if (btn == JOptionPane.NO_OPTION)
                getResultPanel().btnDelete.setSelected(false);
        }
    }

    /**
     * @deprecated Move to ResultController hierarchy.
     */
    private void cmdRestoreSelectedCollectables() {
        assert getCollectStateModel().getOuterState() == CollectState.OUTERSTATE_RESULT;
        assert CollectState.isResultModeSelected(getCollectStateModel().getResultMode());

        if (multipleCollectablesSelected()) {
            final int iCount = getResultTable().getSelectedRowCount();
            final String sMessage = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.83",
                    "Sollen die ausgew\u00e4hlten {0} Datens\u00e4tze wirklich wiederhergestellt werden?", iCount);
            final int btn = JOptionPane.showConfirmDialog(getTab(), sMessage, getSpringLocaleDelegate()
                    .getMessage("GenericObjectCollectController.22", "Datens\u00e4tze wiederherstellen"),
                    JOptionPane.YES_NO_OPTION);
            if (btn == JOptionPane.YES_OPTION)
                new RestoreSelectedCollectablesController(GenericObjectCollectController.this)
                        .run(getMultiActionProgressPanel(iCount));
            else if (btn == JOptionPane.NO_OPTION)
                getResultPanel().btnDelete.setSelected(true);
        } else {
            final String sMessage = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.78",
                    "Soll der ausgew\u00e4hlte Datensatz ({0}) wirklich wiederhergestellt werden?",
                    getSelectedCollectable().getIdentifierLabel());
            final int btn = JOptionPane.showConfirmDialog(getTab(), sMessage, getSpringLocaleDelegate().getMessage(
                    "GenericObjectCollectController.27", "Datensatz wiederherstellen"), JOptionPane.YES_NO_OPTION);

            if (btn == JOptionPane.YES_OPTION)
                UIUtils.runCommand(getTab(), new Runnable() {
                    @Override
                    public void run() {
                        try {
                            checkedRestoreCollectable(getSelectedCollectable());
                            getResultController().getSearchResultStrategy().refreshResult();
                        } catch (CommonPermissionException ex) {
                            final String sErrorMsg = getSpringLocaleDelegate().getMessage(
                                    "GenericObjectCollectController.66",
                                    "Sie verf\u00fcgen nicht \u00fcber die ausreichenden Rechte, um diesen Datensatz wiederherzustellen.");
                            getResultPanel().btnDelete.setSelected(true);
                            Errors.getInstance().showExceptionDialog(getTab(), sErrorMsg, ex);
                        } catch (CommonBusinessException ex) {
                            getResultPanel().btnDelete.setSelected(true);
                            Errors.getInstance().showExceptionDialog(getTab(),
                                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.32",
                                            "Der Datensatz konnte nicht wiederhergestellt werden."),
                                    ex);
                        }
                    }
                });
            else if (btn == JOptionPane.NO_OPTION)
                getResultPanel().btnDelete.setSelected(true);
        }
    }

    /**
     * @deprecated Move to ResultController hierarchy.
     */
    private void cmdRestoreCurrentCollectableInDetails() {
        assert getCollectStateModel().getCollectState()
                .equals(new CollectState(CollectState.OUTERSTATE_DETAILS, CollectState.DETAILSMODE_VIEW));

        if (stopEditingInDetails()) {
            final String sMessage = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.75",
                    "Soll der angezeigte Datensatz ({0}) wirklich wiederhergestellt werden?",
                    getSelectedCollectable().getIdentifierLabel());
            final int iBtn = JOptionPane.showConfirmDialog(getTab(), sMessage, getSpringLocaleDelegate().getMessage(
                    "GenericObjectCollectController.28", "Datensatz wiederherstellen"), JOptionPane.YES_NO_OPTION);

            if (iBtn == JOptionPane.OK_OPTION)
                UIUtils.runCommand(getTab(), new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // try to find next or previous object:
                            final JTable tblResult = getResultTable();
                            final int iSelectedRow = tblResult.getSelectedRow();
                            if (iSelectedRow < 0)
                                throw new IllegalStateException();

                            final int iNewSelectedRow;
                            if (iSelectedRow < tblResult.getRowCount() - 1)
                                // the selected row is not the last row: select the next row
                                iNewSelectedRow = iSelectedRow;
                            else if (iSelectedRow > 0)
                                // the selected row is not the first row: select the previous row
                                iNewSelectedRow = iSelectedRow - 1;
                            else {
                                // the selected row is the single row: don't select a row
                                assert tblResult.getRowCount() == 1;
                                assert iSelectedRow == 0;
                                iNewSelectedRow = -1;
                            }

                            checkedRestoreCollectable(getSelectedCollectable());
                            getResultTableModel().remove(getSelectedCollectable());
                            getSearchStrategy().search(true);

                            if (iNewSelectedRow == -1) {
                                tblResult.clearSelection();
                                // switch to new mode:
                                getResultController().getSearchResultStrategy().refreshResult();
                            } else {
                                tblResult.setRowSelectionInterval(iNewSelectedRow, iNewSelectedRow);
                                // go into view mode again:
                                cmdEnterViewMode();
                            }
                        } catch (CommonPermissionException ex) {
                            final String sErrorMessage = getSpringLocaleDelegate().getMessage(
                                    "GenericObjectCollectController.67",
                                    "Sie verf\u00fcgen nicht \u00fcber die ausreichenden Rechte, um diesen Datensatz wiederherzustellen.");
                            Errors.getInstance().showExceptionDialog(getTab(), sErrorMessage, ex);
                        } catch (CommonBusinessException ex) {
                            Errors.getInstance().showExceptionDialog(getTab(),
                                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.33",
                                            "Der Datensatz konnte nicht wiederhergestellt werden."),
                                    ex);
                        }
                    }
                });
        }
        setDeleteButtonToggleInDetails();
    }

    /**
     * @deprecated Move to ResultController hierarchy.
     *
     * @deprecated Move to a specialization of DetailsController and make private again.
     */
    public void cmdDeleteCurrentCollectableInDetails() {
        assert getCollectStateModel().getCollectState()
                .equals(new CollectState(CollectState.OUTERSTATE_DETAILS, CollectState.DETAILSMODE_VIEW));

        if (stopEditingInDetails()) {
            final String sMessage = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.74",
                    "Soll der angezeigte Datensatz ({0}) wirklich gel\u00f6scht werden?",
                    getSelectedCollectable().getIdentifierLabel());
            final int iBtn = JOptionPane.showConfirmDialog(getTab(), sMessage, getSpringLocaleDelegate().getMessage(
                    "GenericObjectCollectController.26", "Datensatz l\u00f6schen"), JOptionPane.YES_NO_OPTION);

            if (iBtn == JOptionPane.OK_OPTION)
                UIUtils.runCommand(getTab(), new Runnable() {
                    @Override
                    public void run() {
                        cmdDeleteCurrentCollectableInDetailsImpl();
                    }
                });
        }
        setDeleteButtonToggleInDetails();
    }

    /**
     * @return the toolbar that contains the buttons in the fixed left part of the custom toolbar area).
     *         Successors may add their own buttons here and should call revalidate() afterwards.
     */
    /*protected final JToolBar getFixedCustomDetailsToolBar() {
       return toolbarFixCustomDetails;
    }*/

    private void setupDetailsToolBar() {
        final SecurityCache securitycache = SecurityCache.getInstance();

        // additional functionality in Details panel:
        //final JPanel pnlCustomToolBarAreaDetails = new JPanel();
        //pnlCustomToolBarAreaDetails.setLayout(new GridBagLayout());

        // fix custom buttons:
        btnMakeTreeRoot.setName("btnMakeTreeRoot");
        btnMakeTreeRoot.setIcon(Icons.getInstance().getIconMakeTreeRoot16());
        btnMakeTreeRoot.setText(
                getSpringLocaleDelegate().getMessage("GenericObjectCollectController.50", "In Explorer anzeigen"));
        btnMakeTreeRoot.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                cmdJumpToTree();
            }
        });
        //getFixedCustomDetailsToolBar().add(btnMakeTreeRoot);
        //this.getDetailsPanel().addToolBarComponent(btnMakeTreeRoot);
        this.getDetailsPanel().addPopupExtraMenuItem(btnMakeTreeRoot);

        if (securitycache.isActionAllowed(Actions.ACTION_DELETE_RECORD)) {
            btnDeletePhysicallyInDetails.setName("btnDeletePhysicallyInDetails");
            btnDeletePhysicallyInDetails.setIcon(Icons.getInstance().getIconRealDelete16());
            btnDeletePhysicallyInDetails.setText(getSpringLocaleDelegate().getMessage(
                    "GenericObjectCollectController.60", "Objekt endg\u00fcltig aus der Datenbank l\u00f6schen"));
            btnDeletePhysicallyInDetails.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent ev) {
                    cmdDeletePhysically();
                }
            });
            //getFixedCustomDetailsToolBar().add(btnDeletePhysicallyInDetails);
            //this.getDetailsPanel().addToolBarComponent(btnDeletePhysicallyInDetails);
            this.getDetailsPanel().addPopupExtraMenuItem(btnDeletePhysicallyInDetails);
        }

        // Execute rule by user only for authorized personnel
        if (securitycache.isActionAllowed(Actions.ACTION_EXECUTE_RULE_BY_USER)) {
            btnExecuteRule.setName("btnExecuteRule");
            btnExecuteRule.setText(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.62",
                    "Regeln ausf\u00fchren"));
            btnExecuteRule.setIcon(Icons.getInstance().getIconExecuteRule16());
            btnExecuteRule.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent ev) {
                    cmdExecuteRuleByUser(GenericObjectCollectController.this.getTab(),
                            GenericObjectCollectController.this.getEntityName(),
                            GenericObjectCollectController.this.getSelectedCollectable());
                }
            });
            //getFixedCustomDetailsToolBar().add(btnExecuteRule);
            //this.getDetailsPanel().addToolBarComponent(btnExecuteRule);
            this.getDetailsPanel().addPopupExtraSeparator();
            this.getDetailsPanel().addPopupExtraMenuItem(btnExecuteRule);
            btnExecuteRule.setEnabled(securitycache.isActionAllowed(Actions.ACTION_EXECUTE_RULE_BY_USER));
        }

        if (SecurityCache.getInstance().isActionAllowed(Actions.ACTION_USE_INVALID_MASTERDATA)) {
            chkbxUseInvalidMasterData.setText(getSpringLocaleDelegate()
                    .getMessage("GenericObjectCollectController.96", "Ung\u00fcltige Stammdaten anzeigen?"));
            chkbxUseInvalidMasterData.setOpaque(false);
            //chkbxUseInvalidMasterData.setForeground(Color.white);
            chkbxUseInvalidMasterData.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    bUseInvalidMasterData = chkbxUseInvalidMasterData.isSelected();
                    //loadSpecializedLayoutForDetails();
                    Collection<CollectableComponent> collectableComponents = getDetailsPanel().getEditView()
                            .getCollectableComponents();
                    for (CollectableComponent clcmp : collectableComponents) {
                        if (clcmp instanceof CollectableComponentWithValueListProvider) {
                            CollectableComponentWithValueListProvider clcmpWithVLP = (CollectableComponentWithValueListProvider) clcmp;
                            CollectableFieldsProvider valueListProvider = clcmpWithVLP.getValueListProvider();
                            if (valueListProvider instanceof ManagedCollectableFieldsProvider) {
                                ((ManagedCollectableFieldsProvider) valueListProvider)
                                        .setIgnoreValidity(bUseInvalidMasterData);
                                clcmpWithVLP.setValueListProvider(valueListProvider);
                                clcmpWithVLP.refreshValueList(false);
                            }
                        }
                    }
                }
            });
            //         getFixedCustomDetailsToolBar().add(Box.createHorizontalStrut(5));
            //         getFixedCustomDetailsToolBar().add(chkbxUseInvalidMasterData);
            //         getFixedCustomDetailsToolBar().add(Box.createHorizontalStrut(5));
            this.getDetailsPanel().addPopupExtraMenuItem(chkbxUseInvalidMasterData);
        }

        btnShowStateHistory.setName("btnShowStateHistory");
        btnShowStateHistory.setIcon(Icons.getInstance().getIconStateHistory16());
        btnShowStateHistory.setText(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.84",
                "Statushistorie anzeigen"));
        btnShowStateHistory.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                cmdShowStateHistory();
            }
        });
        //getFixedCustomDetailsToolBar().add(btnShowStateHistory);
        //this.getDetailsPanel().addToolBarComponent(btnShowStateHistory);
        this.getDetailsPanel().addPopupExtraSeparator();
        this.getDetailsPanel().addPopupExtraMenuItem(btnShowStateHistory);

        //add the loogbook and historical components only if loogbook tracking is enabled in this module
        if (iModuleId != null && iModuleId > 0 && Modules.getInstance().isLogbookTracking(iModuleId)) {
            btnShowLogBook.setName("btnShowLogBook");
            btnShowLogBook.setIcon(Icons.getInstance().getIconLogBook16());
            btnShowLogBook.setText(
                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.54", "Logbuch anzeigen"));
            btnShowLogBook.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent ev) {
                    cmdShowLogBook();
                }
            });
            //getFixedCustomDetailsToolBar().add(btnShowLogBook);
            //this.getDetailsPanel().addToolBarComponent(btnShowLogBook);
            this.getDetailsPanel().addPopupExtraMenuItem(btnShowLogBook);

            final DateChooser datechooserHistorical = clctdatechooserHistorical.getDateChooser();
            datechooserHistorical.setHistoricalState(true);

            this.getDetailsPanel()
                    .addPopupExtraComponent(new ColoredLabel(datechooserHistorical, getSpringLocaleDelegate()
                            .getMessage("GenericObjectCollectController.7", "Aktueller/historischer Zustand")));
            registerHistoricalDateChooserListener();
        }

        cmbbxCurrentState.setName("cmbbxCurrentState");
        cmbbxCurrentState.setVisible(false);
        cmbbxCurrentState.setToolTipText(
                getSpringLocaleDelegate().getMessage("GenericObjectCollectController.8", "Aktueller Status"));

        cmpStateStandardView.setName("cmpStateStandardView");
        cmpStateStandardView.setVisible(false);
        cmpStateStandardView.setToolTipText(
                getSpringLocaleDelegate().getMessage("GenericObjectCollectController.105", "Standard Statuspfad"));

        toolbarCustomActionsDetailsIndex = this.getDetailsPanel().getToolBarNextIndex();

        this.getDetailsPanel().addToolBarComponent(btnPointer);
    }

    /**
     * Reload layout when specialized flags (e.g. bUseInvalidMasterData) are set (method only for overriding).
     */
    protected void loadSpecializedLayoutForDetails() {
        try {
            reloadLayoutForDetailsTab(true);
        } catch (CommonBusinessException ex) {
            throw new NuclosFatalException(ex);
        }
    }

    @Override
    public void executeBusinessRules(final List<RuleVO> lstRuleVO, final boolean bSaveAfterRuleExecution)
            throws CommonBusinessException {
        invoke(new CommonRunnable() {
            @Override
            public void run() throws CommonBusinessException {
                try {
                    setDetailsChangedIgnored(true);

                    // get the edited state
                    CollectableGenericObjectWithDependants clct = getCollectStateModel().getEditedCollectable();
                    if (clct == null) {
                        // .. or the selected state if the details have not been edited.
                        clct = getSelectedCollectable();
                    }

                    readValuesFromEditPanel(clct, false);
                    final GenericObjectWithDependantsVO go = new GenericObjectWithDependantsVO(
                            clct.getGenericObjectCVO(), getAllSubFormData(clct).toDependantMasterDataMap());

                    GenericObjectDelegate.getInstance().executeBusinessRules(lstRuleVO, go, bSaveAfterRuleExecution,
                            getCustomUsage());

                    broadcastCollectableEvent(clct, MessageType.EDIT_DONE);
                } finally {
                    setDetailsChangedIgnored(false);
                }
            }
        });
    }

    public void setupAdditionalActions() {
        // extend popupmenu for rows in result panel:
        btnPrintDetails.setIcon(Icons.getInstance().getIconPrintReport16());
        btnPrintDetails.setToolTipText(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.48",
                "Formular erzeugen / drucken"));
        btnPrintDetails.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                cmdPrintCurrentGenericObject();
            }
        });
    }

    /** @todo pull down to CollectController */
    protected WeakReference<CollectPanel<CollectableGenericObjectWithDependants>> newCollectPanel(Long entityId) {
        boolean bSearch = MetaDataClientProvider.getInstance().getEntity(this.sEntity).isSearchable();
        return new WeakReference<CollectPanel<CollectableGenericObjectWithDependants>>(
                new GenericObjectCollectPanel(entityId, getSearchStateBox(), bSearch, LafParameterProvider
                        .getInstance().getValue(LafParameter.nuclos_LAF_Details_Overlay, entityId)));
    }

    @Override
    public GenericObjectSearchPanel getSearchPanel() {
        return (GenericObjectSearchPanel) super.getSearchPanel();
    }

    @Override
    public GenericObjectDetailsPanel getDetailsPanel() {
        return (GenericObjectDetailsPanel) super.getDetailsPanel();
    }

    @Override
    public GenericObjectResultPanel getResultPanel() {
        return (GenericObjectResultPanel) super.getResultPanel();
    }

    private void setupEditPanels() {
        // get the layout for the Search and Details panels out of the Layout ML definition:
        if (this.isSearchPanelAvailable())
            setupEditPanelForSearchTab(LayoutRoot.newEmptyLayoutRoot(true));
        setupEditPanelForDetailsTab();
    }

    private void setupEditPanelForSearchTab(LayoutRoot layoutrootSearch) {
        layoutrootSearch.getRootComponent().setFocusCycleRoot(true);
        getSearchPanel().setEditView(newSearchEditView(layoutrootSearch));
        // create a controller for each subform:
        closeSubFormControllersInSearch();
        Map<String, SubForm> mpSubForm = layoutrootSearch.getMapOfSubForms();
        mpsubformctlSearch = newSearchConditionSubFormControllers(mpSubForm);
        getSearchPanel().getEditView().setComponentsEnabled(true);
        if (!getUsageCriteriaFieldListenersAdded(true))
            this.addUsageCriteriaFieldListeners(true);
        setupSubFormController(mpSubForm, mpsubformctlSearch);
        addAdditionalChangeListeners(true);
    }

    private void setupEditPanelForDetailsTab() {
        // Optimization: loading the details panel isn't necessary here, so we install dummies instead.
        final LayoutRoot layoutrootDetails = getInitialLayoutMLDefinitionForDetailsPanel();
        final JComponent compEdit = getDetailsPanel().newEditComponent(layoutrootDetails.getRootComponent());
        getDetailsPanel().setEditView(DefaultEditView.newDetailsEditView(compEdit, layoutrootDetails,
                layoutrootDetails.getInitialFocusEntityAndFieldName()));
        this.layoutrootDetails = layoutrootDetails;
        // create a controller for each subform (after closing the old ones):
        closeSubFormControllers(getSubFormControllersInDetails());

        Map<String, SubForm> mpSubForm = layoutrootDetails.getMapOfSubForms();
        Map<String, DetailsSubFormController<CollectableEntityObject>> mpSubFormController = newDetailsSubFormControllers(
                mpSubForm);
        setMapOfSubFormControllersInDetails(mpSubFormController);
        setupSubFormController(mpSubForm, mpSubFormController);
    }

    @Override
    protected void setupSubFormController(Map<String, SubForm> mpSubForm,
            Map<String, ? extends SubFormController> mpSubFormController) {
        Map<SubForm, MasterDataSubFormController> mpSubFormController_tmp = new HashMap<SubForm, MasterDataSubFormController>();
        final ParameterChangeListener changeListener = new ParameterChangeListener() {
            @Override
            public void stateChanged(final ChangeEvent e) {
                if (e != null && e.getSource() instanceof SubForm) {
                    UIUtils.invokeOnDispatchThread(new Runnable() {
                        @Override
                        public void run() {
                            SubForm subform = (SubForm) e.getSource();
                            GenericObjectCollectController.this.getSubFormsLoader()
                                    .startLoading(subform.getEntityName());
                        }
                    });
                }
            }
        };

        // create a map of subforms and their controllers
        for (String sSubFormEntityName : mpSubFormController.keySet()) {
            SubFormController subformcontroller = mpSubFormController.get(sSubFormEntityName);
            if (subformcontroller instanceof SearchConditionSubFormController) {
                ((SearchConditionSubFormController) subformcontroller).clear();

                subformcontroller.getSubForm()
                        .setRowHeight(subformcontroller.getPrefs().getInt(TableRowIndicator.SUBFORM_ROW_HEIGHT,
                                subformcontroller.getSubForm().isDynamicRowHeightsDefault()
                                        ? SubForm.DYNAMIC_ROW_HEIGHTS
                                        : subformcontroller.getSubForm().getMinRowHeight()));
            }

            SubForm subform = subformcontroller.getSubForm();
            if (!GenericObjectCollectController.this.getSubFormsLoader().isLoadingSubForms()
                    && !(subformcontroller instanceof SearchConditionSubFormController)) {
                SizeKnownListener listener = subform.getSizeKnownListener();
                if (listener != null) {
                    listener.actionPerformed(new SizeKnownEvent(subform, subform.getJTable().getRowCount()));
                }
            }

            if (subformcontroller instanceof DetailsSubFormController<?>) {
                subform.addParameterListener(changeListener);
                ((DetailsSubFormController<CollectableGenericObjectWithDependants>) subformcontroller)
                        .setCollectController(this);
                mpSubFormController_tmp.put(subform, (MasterDataSubFormController) subformcontroller);
            }
            // disable child subforms in searchpanel, because it's not possible to search for data in those subforms
            else if (subformcontroller instanceof SearchConditionSubFormController)
                if (subform.getParentSubForm() != null)
                    subform.setEnabled(false);
        }

        // assign child subforms to their parents
        for (SubForm subform : mpSubFormController_tmp.keySet()) {
            SubForm parentsubform = mpSubForm.get(subform.getParentSubForm());
            if (parentsubform != null) {
                MasterDataSubFormController subformcontroller = mpSubFormController_tmp.get(parentsubform);
                subformcontroller.addChildSubFormController(mpSubFormController_tmp.get(subform));
            }
        }
    }

    @Override
    protected void selectTab() {
        super.selectTab();

        setInitialComponentFocusInSearchTab();
    }

    private void setInitialComponentFocusInSearchTab() {
        if (getSearchPanel() != null)
            Utils.setInitialComponentFocus(getSearchPanel().getEditView(), mpsubformctlSearch);
    }

    private void setInitialComponentFocusInDetailsTab() {
        if (getDetailsPanel() != null)
            Utils.setInitialComponentFocus(getDetailsPanel().getEditView(), mpsubformctlDetails);
    }

    /**
     * @return Map<String sFieldName, DetailsComponentModel> the map of parsed/constructed collectable component models.
     *         Maps a field name to a <code>DetailsComponentModel</code>.
     * @todo move to LayoutRoot
     */
    private static Map<String, DetailsComponentModel> getMapOfDetailsComponentModels(LayoutRoot layoutroot) {
        return CollectionUtils.typecheck(layoutroot.getMapOfCollectableComponentModels(),
                DetailsComponentModel.class);
    }

    /**
     * parses the LayoutML definition and gets the layout information
     * @return the LayoutRoot containing the layout information
     */
    @Override
    protected LayoutRoot getInitialLayoutMLDefinitionForSearchPanel() {
        final LayoutRoot layoutRoot = getLayoutFromCache(
                new UsageCriteria(getModuleId(), null, null, getCustomUsage()),
                new CollectState(CollectState.OUTERSTATE_SEARCH, CollectState.SEARCHMODE_UNSYNCHED));
        getLayoutMLButtonsActionListener().setComponentsEnabled(false);

        // adjustTabbedPanes - remove empty tabs.
        JComponent jcomp = layoutRoot.getRootComponent();
        List<JTabbedPane> lst = new ArrayList<JTabbedPane>();

        searchTabbedPanes(jcomp, lst);

        adjustTabbedPanes(lst);
        return layoutRoot;
    }

    protected LayoutRoot getInitialLayoutMLDefinitionForDetailsPanel() {
        return LayoutRoot.newEmptyLayoutRoot(false);
    }

    /**
     * creates a searchable subform ctl for each subform. If the subform is disabled, the controller will be disabled.
     * @param mpSubForms
     */
    @Override
    protected Map<String, SearchConditionSubFormController> newSearchConditionSubFormControllers(
            Map<String, SubForm> mpSubForms) {
        final String sParentEntityName = this.getEntityName();

        final SearchEditModel editmodelSearch = getSearchPanel().getEditModel();

        return CollectionUtils.transformMap(mpSubForms,
                new Transformer<SubForm, SearchConditionSubFormController>() {
                    @Override
                    public SearchConditionSubFormController transform(SubForm subform) {
                        return newSearchConditionSubFormController(subform, sParentEntityName, editmodelSearch);
                    }
                });
    }

    /**
     * creates an editable subform ctl for each subform. If the subform is disabled, the controller will be disabled.
     * @param mpSubForms
     */
    protected Map<String, DetailsSubFormController<CollectableEntityObject>> newDetailsSubFormControllers(
            Map<String, SubForm> mpSubForms) {
        final Integer iParentId;
        final String sParentEntityName = this.getEntityName(getSelectedCollectableModuleId());

        final DetailsEditModel editmodelDetails = getDetailsPanel().getEditModel();
        if (getCollectState().isDetailsModeNew()) {
            iParentId = null;
        } else {
            iParentId = getSelectedGenericObjectId();
        }

        Map<String, DetailsSubFormController<CollectableEntityObject>> result = CollectionUtils.transformMap(
                mpSubForms, new Transformer<SubForm, DetailsSubFormController<CollectableEntityObject>>() {
                    @Override
                    public DetailsSubFormController<CollectableEntityObject> transform(SubForm subform) {
                        final DetailsSubFormController<CollectableEntityObject> result = newDetailsSubFormController(
                                subform, sParentEntityName, editmodelDetails);
                        result.setParentId(iParentId);
                        return result;
                    }
                });
        getDetailsConroller().setSubFormControllers(result.values());
        return result;
    }

    /**
     * @todo maybe move to CollectController?
     * @param subform
     * @param clctcompmodelprovider
     */
    @Override
    protected SearchConditionSubFormController newSearchConditionSubFormController(SubForm subform,
            String sParentEntityName, CollectableComponentModelProvider clctcompmodelprovider) {

        final String sControllerType = subform.getControllerType();
        if (sControllerType != null && !sControllerType.equals("default"))
            LOG.warn("Kein spezieller SearchConditionSubFormController f?r Controllertyp " + sControllerType
                    + " vorhanden.");
        return _newSearchConditionSubFormController(clctcompmodelprovider, sParentEntityName, subform);
    }

    @Override
    protected SearchConditionSubFormController _newSearchConditionSubFormController(
            CollectableComponentModelProvider clctcompmodelprovider, String sParentEntityName, SubForm subform) {

        // if parent of subform is another subform, change given parent entity name
        String sParentSubForm = subform.getParentSubForm();
        if (sParentSubForm != null)
            sParentEntityName = sParentSubForm;

        return new SearchConditionSubFormController(getTab(), clctcompmodelprovider, sParentEntityName, subform,
                getPreferences(), getEntityPreferences(),
                MasterDataCollectableFieldsProviderFactory.newFactory(null, valueListProviderCache));
    }

    /**
     * @todo maybe pull down to CollectController?
     * @param subform
     * @param clctcompmodelprovider
     * @postcondition result != null
     */
    private MasterDataSubFormController newDetailsSubFormController(SubForm subform, String sParentEntityName,
            CollectableComponentModelProvider clctcompmodelprovider) {

        // if parent of subform is another subform, change given parent entity name
        String sParentSubForm = subform.getParentSubForm();
        if (sParentSubForm != null)
            sParentEntityName = sParentSubForm;

        MasterDataSubFormController result = newDetailsSubFormController(subform, sParentEntityName,
                clctcompmodelprovider, getTab(), getDetailsPanel(), getPreferences(), getEntityPreferences());

        //      if (bUseInvalidMasterData)
        //         result.setCollectableComponentFactory(new NuclosValidityTolerantCollectableComponentFactory());

        return result;
    }

    private void removeAdditionalChangeListeners(boolean bSearchable) {
        for (SubFormController subformctl : getSubFormControllers(bSearchable))
            subformctl.getSubForm().removeChangeListener(getChangeListener(bSearchable));
    }

    /**
     * @deprecated Move to SearchController specialization.
     */
    @Override
    public void addAdditionalChangeListenersForSearch() {
        super.addAdditionalChangeListenersForSearch();
        // getSearchStateBox().getSearchModel().addCollectableComponentModelListener(ccmlistenerSearchChanged);
        ListenerUtil.registerCollectableComponentModelListener(getSearchStateBox().getSearchModel(), null,
                ccmlistenerSearchChanged);
    }

    /**
     * @deprecated Move to SearchController and make protected again.
     */
    @Override
    public void removeAdditionalChangeListenersForSearch() {
        removeAdditionalChangeListeners(true);
        getSearchStateBox().getSearchModel().removeCollectableComponentModelListener(ccmlistenerSearchChanged);
    }

    /**
     * @deprecated Move to DetailsController hierarchy and make protected again.
     */
    @Override
    public void addAdditionalChangeListenersForDetails() {
        addAdditionalChangeListeners(false);
    }

    /**
     * @deprecated Move to DetailsController and make protected again.
     */
    @Override
    public void removeAdditionalChangeListenersForDetails() {
        removeAdditionalChangeListeners(false);
    }

    @Override
    protected Collection<? extends SubFormController> getSubFormControllers(boolean bSearchTab) {
        return bSearchTab ? getSubFormControllersInSearch() : getSubFormControllersInDetails();
    }

    /**
     * @return Map<String sEntity, DetailsSubFormController>. May be <code>null</code>.
     */
    @Override
    protected Map<String, DetailsSubFormController<CollectableEntityObject>> getMapOfSubFormControllersInDetails() {
        return mpsubformctlDetails;
    }

    public void setMapOfSubFormControllersInDetails(
            Map<String, DetailsSubFormController<CollectableEntityObject>> mpSubFormControllersInDetails) {
        mpsubformctlDetails = mpSubFormControllersInDetails;
    }

    @Override
    protected Map<String, SearchConditionSubFormController> getMapOfSubFormControllersInSearch() {
        return this.mpsubformctlSearch;
    }

    /**
     * @postcondition result != null
     */
    private Collection<SearchConditionSubFormController> getSubFormControllersInSearch() {
        return CollectionUtils.valuesOrEmptySet(getMapOfSubFormControllersInSearch());
    }

    /**
     * fills the subform controllers with collectablemasterdata found in the given DependantCollectableMasterDataMap
     * @param mpDependants
     * @throws NuclosBusinessException
     */
    public void fillSubForm(Integer iParentId, DependantCollectableMasterDataMap mpDependants)
            throws NuclosBusinessException {
        for (String sEntityName : mpDependants.getEntityNames()) {
            DetailsSubFormController<CollectableEntityObject> sfcontroller = getMapOfSubFormControllersInDetails()
                    .get(sEntityName);
            if (sfcontroller instanceof MasterDataSubFormController)
                ((MasterDataSubFormController) sfcontroller).fillSubForm(iParentId,
                        mpDependants.toDependantMasterDataMap().getData(sEntityName));
        }
    }

    /**
     * @param bMakeConsistent
     * @return the search condition contained in the search panel's fields (including the subforms' search fields).
     * @precondition this.isSearchPanelAvailable()
     * @postcondition result == null || result.isSyntacticallyCorrect()
     *
     * @deprecated Move to SearchController or SearchPanel and make protected again.
     */
    @Override
    public CollectableSearchCondition getCollectableSearchConditionFromSearchFields(boolean bMakeConsistent)
            throws CollectableFieldFormatException {
        if (!isSearchPanelAvailable())
            throw new IllegalStateException("!this.isSearchPanelAvailable()");
        final CollectableSearchCondition cond = super.getCollectableSearchConditionFromSearchFields(
                bMakeConsistent);

        final CompositeCollectableSearchCondition condAnd = new CompositeCollectableSearchCondition(
                LogicalOperator.AND);
        if (cond != null)
            condAnd.addOperand(cond);

        assert getMapOfSubFormControllersInSearch() != null;
        for (SearchConditionSubFormController subformctl : getMapOfSubFormControllersInSearch().values()) {
            for (CollectableSearchCondition subCond : subformctl.getCollectableSubformSearchConditions()) {
                if (subCond != null)
                    condAnd.addOperand(new CollectableSubCondition(subformctl.getCollectableEntity().getName(),
                            subformctl.getForeignKeyFieldName(), subCond));
            }
            /*  Subcondition or old code do not delete please
            final CollectableSearchCondition condSub = subformctl.getCollectableSearchCondition();
            if (condSub != null)
               condAnd.addOperand(new CollectableSubCondition(subformctl.getCollectableEntity().getName(), subformctl.getForeignKeyFieldName(), condSub));
            */
        }

        final CollectableSearchCondition result = SearchConditionUtils.simplified(condAnd);
        assert result == null || result.isSyntacticallyCorrect();
        return result;
    }

    /**
     * @return the search condition to display. Includes the currently selected global search filter's search condition (if any).
     * @throws CollectableFieldFormatException
     *
     * @deprecated Move to SearchController and make protected again.
     */
    @Override
    public CollectableSearchCondition getCollectableSearchConditionToDisplay()
            throws CollectableFieldFormatException {
        final CompositeCollectableSearchCondition compositecond = new CompositeCollectableSearchCondition(
                LogicalOperator.AND);

        final CollectableSearchCondition clctcond = super.getCollectableSearchConditionToDisplay();
        if (clctcond != null)
            compositecond.addOperand(clctcond);

        return SearchConditionUtils.simplified(compositecond);
    }

    /**
     * @return a Comparator that compares the entity labels first, then the field labels.
     *         Fields of the main entity are sorted lower than all other fields.
     *
     * @deprecated Remove this.
    private Comparator<CollectableEntityField> getCollectableEntityFieldComparator() {
       return new Comparator<CollectableEntityField>() {
     final Collator collator = LangUtils.getDefaultCollator();
        
     @Override
     public int compare(CollectableEntityField clctefwe1, CollectableEntityField clctefwe2) {
        final int iCompareEntities = LangUtils.compare(getEntityLabel(clctefwe1), getEntityLabel(clctefwe2), collator);
        return (iCompareEntities != 0) ? iCompareEntities : LangUtils.compare(clctefwe1.getLabel(), clctefwe2.getLabel(), collator);
     }
        
     private String getEntityLabel(CollectableEntityField field) {
        final CollectableEntityFieldWithEntity clctefwe = (CollectableEntityFieldWithEntity) field;
        return clctefwe.getCollectableEntityName().equals(GenericObjectCollectController.this.getCollectableEntity().getName()) ?
           null : clctefwe.getCollectableEntityLabel();
     }
       };
    }
     */

    /**
     * @return a specific table model with an overridden getValueAt method, providing access to subform entries.
     *
     * @deprecated Move to ResultController hierarchy.
     */
    @Override
    protected SortableCollectableTableModel<CollectableGenericObjectWithDependants> newResultTableModel() {
        final SortableCollectableTableModel<CollectableGenericObjectWithDependants> result = new GenericObjectsResultTableModel<CollectableGenericObjectWithDependants>(
                getCollectableEntity(), getFields().getSelectedFields());

        /**
         * @deprecated Move to ResultController hierarchy.
         */
        class GenericObjectSortingRunnable implements CommonRunnable {
            @Override
            public void run() throws CommonBusinessException {
                final String baseEntity = getCollectableEntity().getName();
                boolean canSort = true;
                for (SortKey sk : result.getSortKeys()) {
                    final CollectableEntityField sortField = result.getCollectableEntityField(sk.getColumn());
                    if (!sortField.getEntityName().equals(baseEntity)) {
                        if (sortField instanceof CollectableEOEntityField) {
                            final CollectableEOEntityField f = (CollectableEOEntityField) sortField;
                            if (f.getMeta().getPivotInfo() == null) {
                                canSort = false;
                                break;
                            }
                        } else {
                            canSort = false;
                            break;
                        }
                    }
                }
                if (canSort) {
                    //NUCLEUSINT-1039
                    getResultController().getSearchResultStrategy().cmdSearch();
                } else {
                    result.setSortKeys(Collections.<SortKey>emptyList(), false);
                    throw new CommonBusinessException(
                            getSpringLocaleDelegate().getMessage("GenericObjectCollectController.19",
                                    "Das Suchergebnis kann nicht nach Unterformularspalten sortiert werden."));
                }
            }
        }

        final JTable tbl = getResultTable();
        // clicking header does not mean column selection, but sorting:
        tbl.setColumnSelectionAllowed(false);

        final GenericObjectSortingRunnable runnable = new GenericObjectSortingRunnable();

        // clicking a column header is to cause a new search on the server:
        TableUtils.addMouseListenerForSortingToTableHeader(tbl, result, runnable);

        // @todo: this is not so nice a construct... maybe this should be moved to the result panel, because we use insight into the implementation of fixed tables here.
        TableUtils.addMouseListenerForSortingToTableHeader(getFixedResultTable(), result, runnable);

        return result;
    }

    /**
     * Get the fixed part of the result table, if any.
     */
    public final JTable getFixedResultTable() {
        return getResultPanel().getFixedResultTable();
    }

    /**
     * TODO: Make private again.
     */
    public List<CollectableEntityField> getSelectedFields() {
        // TODO: avoid unnecessary check.
        return CollectionUtils.typecheck(getFields().getSelectedFields(), CollectableEntityField.class);
    }

    /**
     * sets up the change listener for the vertical scrollbar of the result table,
     * only if the proxy list has been set (that is not before the first search).
     *
     * TODO: Make this protected again.
     */
    public void setupChangeListenerForResultTableVerticalScrollBar() {
        final ProxyList<? extends Collectable> lstclct = getSearchStrategy().getCollectableProxyList();
        if (lstclct != null)
            getResultPanel().setupChangeListenerForResultTableVerticalScrollBar(lstclct, getTab());
    }

    /**
     * TODO: Make this proteced again.
     *
     * @deprecated Move to ResultPanel???
     */
    public void removePreviousChangeListenersForResultTableVerticalScrollBar() {
        final JScrollBar scrlbarVertical = getResultPanel().getResultTableScrollPane().getVerticalScrollBar();
        final DefaultBoundedRangeModel model = (DefaultBoundedRangeModel) scrlbarVertical.getModel();
        GenericObjectResultPanel.removePreviousChangeListenersForResultTableVerticalScrollBar(model);
    }

    @Override
    protected void unsafeFillDetailsPanel(CollectableGenericObjectWithDependants clct)
            throws CommonBusinessException {
        if (getCollectState().isDetailsModeNew())
            iCurrentLayoutId = null; // reset current loaded layout. @see NUCLOS-1085
        this.unsafeFillDetailsPanel(clct, getCollectState().isDetailsModeNew() /*&& getProcess() != null*/); //@see NUCLOS-656 respecting getProcess is not needed here. if process is not null, it will set via newCollectableWithDefaultValues
    }

    protected void unsafeFillDetailsPanel(CollectableGenericObjectWithDependants clct,
            boolean getUsageCriteriaFromClctOnly) throws CommonBusinessException {
        LOG.debug("GenericObjectCollectController.unsafeFillDetailsPanel start");

        // Don't reload the layout after update - only if quintuple fields changed. */
        if (bReloadLayout)
            // Load the right layout if it is not already there:
            // Note that quintuple field listeners are removed here:
            loadLayoutForDetailsTab(clct, getCollectStateModel().getCollectState(), getUsageCriteriaFromClctOnly);
        else
            // Restore the default setting (Next time, reload the layout):
            bReloadLayout = true;

        /** @todo this doesn't seem right: when the layout changes, the fields are transferred
         * from the old mask (in loadLayoutForDetailsTab), then they are overwritten with the values of clct? */

        try {
            CollectableGenericObjectWithDependants lowdCurrent = null;
            if (isHistoricalView())
                lowdCurrent = new CollectableGenericObjectWithDependants(GenericObjectDelegate.getInstance()
                        .getWithDependants((Integer) getSelectedCollectableId(), getCustomUsage()));

            for (String sFieldName : getOrderedFieldNamesInDetails()) {
                // iterate over the models rather than over the components:
                final CollectableComponentModel clctcompmodel = layoutrootDetails
                        .getCollectableComponentModelFor(sFieldName);
                final CollectableField clctfShown = clct.getField(sFieldName);
                clctcompmodel.setFieldInitial(clctfShown, true);

                markFieldInHistoricalView(lowdCurrent, sFieldName, clctfShown);
            }

            // fill subforms:
            LOG.debug("fill subforms start");
            Collection<LogbookVO> colllogbookvo = null;
            if (isHistoricalView())
                try {
                    colllogbookvo = GenericObjectDelegate.getInstance().getLogbook(lowdCurrent.getId());
                } catch (CommonFinderException ex) {
                    // No logbook, no changes
                }

            if (!getCollectState().isDetailsModeMultiViewOrEdit()) {
                if (clct.getId() != null) {
                    UIUtils.invokeOnDispatchThread(new Runnable() {
                        @Override
                        public void run() {
                            GenericObjectCollectController.this.getSubFormsLoader().startLoading();
                        }
                    });
                    if (isHistoricalView())
                        showLoading(false);
                }

                // loading calculated attributes

                // loading subforms
                for (final SubFormController subformctl : getSubFormControllersInDetails()) {
                    // TODO try to eliminate this cast
                    final MasterDataSubFormController mdsubformctl = (MasterDataSubFormController) subformctl;
                    if (isHistoricalView())
                        markSubformInHistoricalView(mdsubformctl, clct, colllogbookvo);
                    else {
                        //by object generation
                        DependantMasterDataMap dependants = clct.getGenericObjectWithDependantsCVO()
                                .getDependants();
                        if (clct.getId() == null && dependants.getAllData().size() != 0) {
                            for (String entity : dependants.getEntityNames())
                                if (entity.equals(mdsubformctl.getCollectableEntity().getName())) {
                                    mdsubformctl.fillSubForm(null, dependants.getData(entity));
                                    mdsubformctl.getSubForm()
                                            .setNewEnabled(new CollectControllerScriptContext(
                                                    GenericObjectCollectController.this,
                                                    new ArrayList<DetailsSubFormController<?>>(
                                                            getSubFormControllersInDetails())));
                                }
                        } else if (clct.getId() == null) {
                            mdsubformctl.clear();
                            mdsubformctl.getSubForm().getJTable().setBackground(Color.WHITE);
                            mdsubformctl.fillSubForm(null, new ArrayList<EntityObjectVO>());
                            mdsubformctl.getSubForm().setNewEnabled(new CollectControllerScriptContext(
                                    GenericObjectCollectController.this,
                                    new ArrayList<DetailsSubFormController<?>>(getSubFormControllersInDetails())));
                        } else {
                            if (mdsubformctl.isChildSubForm())
                                continue;

                            fillSubformMultithreaded(clct, mdsubformctl);
                        }
                    }
                }
            }
            LOG.debug("fill subforms done");

            selectTabPane(clct);

            getLayoutMLButtonsActionListener().fireComponentEnabledStateUpdate(false);
        } finally {
            /** @todo this doesn't seem to belong here... */
            if (!getUsageCriteriaFieldListenersAdded(false))
                this.addUsageCriteriaFieldListeners(false);
        }
        LOG.debug("GenericObjectCollectController.unsafeFillDetailsPanel done");
    }

    private void selectTabPane(CollectableGenericObjectWithDependants clct) {
        // select tab in state
        //Integer iState = (Integer)clct.getField(NuclosEOField.STATENUMBER.getMetaData().getField()).getValueId();
        DynamicAttributeVO vo = clct.getGenericObjectCVO()
                .getAttribute(NuclosEOField.STATENUMBER.getMetaData().getId().intValue());
        if (vo == null)
            return;
        Integer iState = (Integer) vo.getValue();

        String strTabName = getPreferences().get(TABSELECTED, "");
        for (StateVO voState : StateDelegate.getInstance().getStatesByModule(getModuleId()))
            if (voState.getNumeral().equals(iState)) {
                if (voState.getTabbedPaneName() != null)
                    strTabName = voState.getTabbedPaneName();
                break;
            }

        JComponent jcomp = layoutrootDetails.getRootComponent();
        List<JTabbedPane> lst = new ArrayList<JTabbedPane>();

        searchTabbedPanes(jcomp, lst);

        for (JTabbedPane tabPane : lst)
            for (int i = 0; i < tabPane.getTabCount(); i++)
                if (org.apache.commons.lang.StringUtils.equals(tabPane.getComponentAt(i).getName(), strTabName)
                        || org.apache.commons.lang.StringUtils.equals(tabPane.getTitleAt(i), strTabName)) {
                    tabPane.setSelectedIndex(i);
                    break;
                }

        adjustTabbedPanes(lst);
    }

    private void adjustTabbedPanes(List<JTabbedPane> lst) {
        for (JTabbedPane tabPane : lst) {
            List<Component> lstTabToRemove = new ArrayList<Component>(tabPane.getTabCount());
            for (int i = 0; i < tabPane.getTabCount(); i++) {
                Component c = tabPane.getComponentAt(i);
                if (c instanceof JComponent) {
                    List<JComponent> lstComponents = new ArrayList<JComponent>();
                    collectComponents((JComponent) c, lstComponents);
                    if (lstComponents.size() == 0) {
                        tabPane.setEnabledAt(i, false);
                        lstTabToRemove.add(c);
                        break;
                    }

                    boolean blnVisible = false;
                    for (JComponent jc : lstComponents) {
                        if (jc instanceof LabeledComponent) {
                            LabeledComponent lc = (LabeledComponent) jc;
                            blnVisible = lc.isVisible();
                        } else if (jc instanceof JPanel)
                            blnVisible = false;
                        else
                            blnVisible = jc.isVisible();
                        if (blnVisible)
                            break;
                    }

                    tabPane.setEnabledAt(i, !blnVisible ? blnVisible : blnVisible && tabPane.isEnabledAt(i));
                    if (!blnVisible)
                        lstTabToRemove.add(c);
                }
            }
            for (Component c : lstTabToRemove) {
                tabPane.remove(c);
            }
        }
    }

    private void searchTabbedPanes(JComponent comp, List<JTabbedPane> lst) {
        if (comp instanceof JTabbedPane)
            lst.add((JTabbedPane) comp);
        if (comp.getComponents().length == 0)
            return;
        for (Component c : comp.getComponents())
            if (c instanceof JComponent)
                searchTabbedPanes((JComponent) c, lst);
    }

    private void collectComponents(JComponent comp, List<JComponent> lst) {
        for (Component c : comp.getComponents())
            if (c instanceof JComponent) {
                JComponent jc = (JComponent) c;
                if (jc.getComponentCount() == 0)
                    lst.add(jc);
                if (jc instanceof LabeledComponent) {
                    lst.add(jc);
                    continue;
                }
                collectComponents(jc, lst);
            }
    }

    private void fillSubformMultithreaded(final CollectableGenericObjectWithDependants clct,
            final MasterDataSubFormController mdsubformctl) throws NuclosBusinessException {
        final SubFormsInterruptableClientWorker sfClientWorker = new SubFormsInterruptableClientWorker() {
            Collection<EntityObjectVO> collmdcvo;
            Integer iParentId;

            @Override
            public void init() throws CommonBusinessException {
                if (!interrupted) {
                    boolean wasDetailsChangedIgnored = isDetailsChangedIgnored();
                    setDetailsChangedIgnored(true);
                    mdsubformctl.clear();
                    setDetailsChangedIgnored(wasDetailsChangedIgnored);
                    mdsubformctl.getSubForm().setLockedLayer();
                }
            }

            @Override
            public void work() throws NuclosBusinessException {
                if (interrupted)
                    return;
                else {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        // ignore
                    }
                    iParentId = clct.getId();
                    if (iParentId == null)
                        collmdcvo = new ArrayList<EntityObjectVO>();
                    else {
                        boolean bLoad = false;
                        if (clct instanceof CollectableGenericObjectWithDependants) {
                            if (((CollectableGenericObjectWithDependants) clct).getGenericObjectWithDependantsCVO()
                                    .getDependants().hasData(mdsubformctl.getCollectableEntity().getName())) {
                                collmdcvo = ((CollectableGenericObjectWithDependants) clct)
                                        .getGenericObjectWithDependantsCVO().getDependants()
                                        .getData(mdsubformctl.getCollectableEntity().getName());
                                bLoad = false;
                            }
                        }
                        if (bLoad || collmdcvo == null)
                            collmdcvo = MasterDataDelegate.getInstance().getDependantMasterData(
                                    mdsubformctl.getCollectableEntity().getName(),
                                    mdsubformctl.getForeignKeyFieldName(), clct.getId(),
                                    mdsubformctl.getSubForm().getMapParams());
                    }
                    /*collmdcvo = (clct.getId() == null)
                    ? new ArrayList<EntityObjectVO>()
                       : MasterDataDelegate.getInstance().getDependantMasterData(
                          mdsubformctl.getCollectableEntity().getName(),
                          mdsubformctl.getForeignKeyFieldName(), clct.getId(), mdsubformctl.getSubForm().getMapParams());*/
                }
            }

            @Override
            public void handleError(Exception ex) {
                if (!interrupted)
                    Errors.getInstance().showExceptionDialog(Main.getInstance().getMainFrame(), ex);
            }

            @Override
            public JComponent getResultsComponent() {
                return mdsubformctl.getSubForm();
            }

            @Override
            public void paint() throws CommonBusinessException {
                // The data schould not be published to sub form! otherwise we
                // will see a sub form data of another object!
                if (!interrupted)
                    synchronized (GenericObjectCollectController.this) {
                        // GenericObjectCollectController.this.wait();
                        final boolean bWasDetailsChangedIgnored = GenericObjectCollectController.this
                                .isDetailsChangedIgnored();
                        GenericObjectCollectController.this.setDetailsChangedIgnored(true);
                        try {
                            final CollectableField clctfield = clct
                                    .getField(NuclosEOField.STATE.getMetaData().getField());
                            final Integer iStatusId = (clctfield != null) ? (Integer) clctfield.getValueId() : null;

                            if (!mdsubformctl.isClosed()) {
                                String entityname = mdsubformctl.getEntityAndForeignKeyFieldName().getEntityName();
                                Permission permission = SecurityCache.getInstance().getSubFormPermission(entityname,
                                        iStatusId);

                                if (permission == null)
                                    mdsubformctl.clear();
                                else
                                    mdsubformctl.fillSubForm(iParentId, collmdcvo);

                                mdsubformctl.getSubForm()
                                        .setNewEnabled(new CollectControllerScriptContext(
                                                GenericObjectCollectController.this,
                                                new ArrayList<DetailsSubFormController<?>>(
                                                        getSubFormControllersInDetails())));
                            }
                        } finally {
                            if (!bWasDetailsChangedIgnored)
                                GenericObjectCollectController.this
                                        .setDetailsChangedIgnored(bWasDetailsChangedIgnored);
                        }
                        GenericObjectCollectController.this.getSubFormsLoader()
                                .setSubFormLoaded(mdsubformctl.getCollectableEntity().getName(), true);
                        if (!mdsubformctl.isClosed()) {
                            mdsubformctl.getSubForm().forceUnlockFrame();
                            mdsubformctl.selectFirstRow();
                        }
                    }
            }
        };
        GenericObjectCollectController.this.getSubFormsLoader()
                .addSubFormClientWorker(mdsubformctl.getCollectableEntity().getName(), sfClientWorker);
    }

    private void markSubformInHistoricalView(MasterDataSubFormController subformctl,
            CollectableGenericObjectWithDependants clct, Collection<LogbookVO> logbookEntries)
            throws NuclosBusinessException {

        String entityName = subformctl.getCollectableEntity().getName();
        Collection<EntityObjectVO> collmdvo = clct.getGenericObjectWithDependantsCVO().getDependants()
                .getData(entityName);
        /** @todo check if this is correct: Shouldn't we initialize the subform even if it is empty? */
        if (!collmdvo.isEmpty())
            subformctl.fillSubForm(null, collmdvo);

        EntityMetaDataVO entityMeta = MetaDataClientProvider.getInstance().getEntity(entityName);
        boolean hasLoggingFields = false;
        for (EntityFieldMetaDataVO fieldMeta : MetaDataClientProvider.getInstance()
                .getAllEntityFieldsByEntity(entityName).values())
            hasLoggingFields |= fieldMeta.isLogBookTracking();

        if (!hasLoggingFields)
            // if no field of the entity is logged, mark subform table as not logged
            subformctl.getSubForm().getJTable().setBackground(colorHistoricalNotTracked);
        else
            for (LogbookVO logbookvo : logbookEntries) {
                Integer mmId = logbookvo.getMasterDataMetaId();
                if (mmId != null && mmId.equals(entityMeta.getId()))
                    // if there is any entry in the logbook for this entity done
                    // after the date of the historical view, mark the subform
                    // table as changed
                    if (dateHistorical.compareTo(logbookvo.getCreatedAt()) < 0) {
                        subformctl.getSubForm().getJTable().setBackground(colorHistoricalChanged);
                        break;
                    }
            }
    }

    private void markFieldInHistoricalView(CollectableGenericObjectWithDependants clctlowdCurrent,
            String sFieldName, CollectableField clctfShown) {
        if (clctlowdCurrent != null) {
            final CollectableField clctf = clctlowdCurrent.getField(sFieldName);
            if (!clctfShown.equals(clctf)) {
                final Collection<CollectableComponent> collclctcomp = layoutrootDetails
                        .getCollectableComponentsFor(sFieldName);
                if (!collclctcomp.isEmpty()) {
                    final CollectableComponent clctcomp = collclctcomp.iterator().next();
                    final JComponent compFocussable = clctcomp.getFocusableComponent();
                    String initialToolTip = (String) compFocussable.getClientProperty("initialToolTip");
                    if (initialToolTip == null) {
                        initialToolTip = StringUtils.emptyIfNull(compFocussable.getToolTipText());
                        compFocussable.putClientProperty("initialToolTip", initialToolTip);
                    }
                    if (clctcomp instanceof CollectableComboBox) {
                        CollectableComboBox clctcmbx = (CollectableComboBox) clctcomp;
                        clctcmbx.getJComboBox().setRenderer(clctcmbx.new CollectableFieldRenderer() {

                            @Override
                            protected void paintComponent(Graphics g) {
                                setBackground(colorHistoricalChanged);
                                super.paintComponent(g);
                            }
                        });
                    } else
                        compFocussable.setBackground(colorHistoricalChanged);
                    final String sToolTip = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.4",
                            "{0} [Ge\u00e4ndert; aktueller Wert: \"{1}\"]", initialToolTip, clctf.toString());
                    compFocussable.setToolTipText(sToolTip);
                    clctcomp.getJComponent().setToolTipText(sToolTip);
                    // todo: mark fields which are not tracked in logbook?
                }
            }
        }
    }

    private List<String> getOrderedFieldNamesInDetails() {
        return layoutrootDetails.getOrderedFieldNames();
    }

    @Override
    protected void unsafeFillMultiEditDetailsPanel(Collection<CollectableGenericObjectWithDependants> collclct)
            throws CommonBusinessException {
        // set the right layout:
        final UsageCriteria usagecriteria = getGreatestCommonUsageCriteriaFromCollectables(collclct);
        this.reloadLayout(usagecriteria, getCollectStateModel().getCollectState(), true, false);

        // fill the details panel with the common values:
        super.unsafeFillMultiEditDetailsPanel(collclct);

        // load dependent data
        List<EntityAndFieldName> requiredDependants = new ArrayList<EntityAndFieldName>();
        for (DetailsSubFormController<CollectableEntityObject> subformctl : getSubFormControllersInDetails()) {
            if (StringUtils.isNullOrEmpty(subformctl.getSubForm().getParentSubForm())) {
                requiredDependants.add(subformctl.getEntityAndForeignKeyFieldName());
            }
        }

        for (CollectableGenericObjectWithDependants clct : collclct) {
            DependantMasterDataMap dependants = MasterDataDelegate.getInstance().getDependants(clct.getId(),
                    requiredDependants);
            for (String entityname : dependants.getEntityNames()) {
                clct.getGenericObjectWithDependantsCVO().getDependants().setData(entityname,
                        dependants.getData(entityname));
            }
        }

        // begin multi-update of dependants:
        multiupdateofdependants = new MultiUpdateOfDependants(getSubFormControllersInDetails(), collclct);

        getLayoutMLButtonsActionListener().setComponentsEnabled(false);
    }

    /**
     * Get also changes in subforms
     * @todo move to DetailsPanel
     *
     * TODO: Make this protected again.
     */
    @Override
    public String getMultiEditChangeString() {
        final String sChangesSoFar = super.getMultiEditChangeString();
        final StringBuilder sbResult = new StringBuilder(sChangesSoFar);

        try {
            for (DetailsSubFormController<CollectableEntityObject> subformctl : getSubFormControllersInDetails())
                if (subformctl.getSubForm().isEnabled()) {
                    boolean bChanged = false;
                    boolean bNew = false;
                    boolean bRemoved = false;
                    if (!subformctl.getSubForm().getJTable().isEditing())
                        for (CollectableEntityObject clctmd : subformctl.getCollectables(true, true, false)) {
                            EntityObjectVO mdvo = clctmd.getEntityObjectVO();
                            if (mdvo.isFlagUpdated())
                                if (mdvo.getCreatedBy() == null)
                                    bNew = true;
                                else
                                    bChanged = true;
                            if (mdvo.isFlagRemoved())
                                bRemoved = true;
                        }
                    else
                        bChanged = true;

                    if (bNew || bChanged || bRemoved) {
                        if (sbResult.length() > 0)
                            sbResult.append(", ");
                        sbResult.append("Eintr?ge in ").append(subformctl.getCollectableEntity().getLabel());
                        if (bNew)
                            sbResult.append(" hinzugef?gt");
                        if (bChanged) {
                            sbResult.append(bNew ? "/" : " ");
                            sbResult.append("ver?ndert");
                        }
                        if (bRemoved) {
                            sbResult.append(bNew || bChanged ? "/" : " ");
                            sbResult.append("gel?scht");
                        }
                    }
                }
        } catch (CommonValidationException e) {
            // This will never occur, as there will not be validated in getCollectables, when bPrepareForSavingAndValidate ist false
            LOG.warn("getMultiEditChangeString failed: " + e, e);
        }

        return sbResult.toString();
    }

    private void loadLayoutForDetailsTab(CollectableGenericObject clct, CollectState collectstate,
            boolean getUsageCriteriaFromClctOnly) throws CommonBusinessException {
        LOG.debug("loadLayoutForDetailsTab start");

        boolean transferContents = !collectstate.isDetailsModeNew();
        UsageCriteria uc = null;
        if (collectstate.isDetailsModeNew())
            uc = getUsageCriteriaFromClctOnly ? getUsageCriteria(clct) : getUsageCriteriaFromView(false);
        else
            uc = getUsageCriteria(clct);

        this.reloadLayout(uc, collectstate, transferContents, false);

        LOG.debug("loadLayoutForDetailsTab done");
    }

    @Override
    protected CollectableGenericObjectWithDependants readSelectedCollectable() throws CommonBusinessException {
        final CollectableGenericObjectWithDependants result;

        if (isHistoricalView()) {
            /** @todo OPTIMIZE: Is it really necessary to read the current object first? */
            // What about this? UA/020206
            // final int iGenericObjectId = ((Integer) this.getSelectedCollectableId()).intValue();
            // final int iModuleId = this.getSelectedCollectableModuleId().intValue();
            final GenericObjectVO govo = getSelectedGenericObjectCVO();
            final int iGenericObjectId = govo.getId();
            assert dateHistorical != null;
            result = new CollectableGenericObjectWithDependants(
                    lodelegate.getHistorical(iGenericObjectId, dateHistorical, getCustomUsage()));
        } else
            result = super.readSelectedCollectable();

        return result;
    }

    @Override
    public CollectableGenericObjectWithDependants readCollectable(CollectableGenericObjectWithDependants clct)
            throws CommonBusinessException {
        return findCollectableById(clct.getCollectableEntity().getName(), clct.getId());
    }

    @Override
    public CollectableGenericObjectWithDependants findCollectableById(String sEntityName, Object oId)
            throws CommonBusinessException {
        if (sEntityName == null)
            throw new IllegalArgumentException("sEntityName");
        if (oId == null)
            throw new IllegalArgumentException("oId");

        if (oId instanceof Long) // Compatability to new EntityObjectVO
            oId = new Integer(((Long) oId).intValue());

        final int iGenericObjectId = (Integer) oId;

        final CollectableGenericObjectWithDependants result = new CollectableGenericObjectWithDependants(
                lodelegate.getWithDependants(iGenericObjectId, getCustomUsage()));

        assert getSearchStrategy().isCollectableComplete(result);
        return result;
    }

    @Override
    protected CollectableGenericObjectWithDependants findCollectableById(String sEntity, Object oId,
            Collection<EntityFieldMetaDataVO> fields) throws CommonBusinessException {
        if (fields == null) {
            return findCollectableById(sEntity, oId);
        }
        Collection<EntityObjectVO> eovos = EntityObjectDelegate.getInstance().getEntityObjectsMore(
                MetaDataClientProvider.getInstance().getEntity(sEntity).getId(),
                Collections.singletonList(IdUtils.toLongId(oId)), fields, getCustomUsage());
        if (eovos.isEmpty()) {
            return null;
        } else {
            return new CollectableGenericObjectWithDependants(DalSupportForGO.getGenericObjectWithDependantsVO(
                    eovos.iterator().next(), (CollectableEOEntity) CollectableEOEntityClientProvider.getInstance()
                            .getCollectableEntity(sEntity)));
        }
    }

    @Override
    protected CollectableGenericObjectWithDependants findCollectableByIdWithoutDependants(String sEntityName,
            Object oId) throws CommonBusinessException {
        if (sEntityName == null)
            throw new IllegalArgumentException("sEntityName");
        if (oId == null)
            throw new IllegalArgumentException("oId");

        if (oId instanceof Long) {
            oId = new Integer(((Long) oId).intValue());
        }
        final int iGenericObjectId = (Integer) oId;

        final CollectableGenericObjectWithDependants result = CollectableGenericObjectWithDependants
                .newCollectableGenericObject(lodelegate.get(iGenericObjectId));

        assert getSearchStrategy().isCollectableComplete(result);
        return result;
    }

    @Override
    protected boolean isDetailsModeViewLoadingWithoutDependants() {
        return true;
    }

    @Override
    public Integer getVersionOfCollectableById(String sEntityName, Object oId) throws CommonBusinessException {
        if (sEntityName == null)
            throw new IllegalArgumentException("sEntityName");
        if (oId == null)
            throw new IllegalArgumentException("oId");

        final int iGenericObjectId = (Integer) oId;

        return lodelegate.getVersion(iGenericObjectId);
    }

    @Override
    protected CollectableGenericObjectWithDependants updateCurrentCollectable(
            CollectableGenericObjectWithDependants clctCurrent) throws CommonBusinessException {
        assert !isHistoricalView();

        final CollectableGenericObjectWithDependants result = updateCollectable(clctCurrent,
                getAllSubFormData(clctCurrent));

        // remember if the layout needs to be reloaded afterwards:
        if (!isProcessingStateChange.get() && getUsageCriteria(result).equals(getUsageCriteria(clctCurrent)))
            bReloadLayout = false;

        return result;
    }

    @Override
    protected CollectableGenericObjectWithDependants updateCollectable(CollectableGenericObjectWithDependants clct,
            Object oAdditionalData) throws CommonBusinessException {
        final DependantCollectableMasterDataMap mpclctDependants = (DependantCollectableMasterDataMap) oAdditionalData;

        // @todo validate?

        if (mpclctDependants != null)
            Utils.prepareDependantsForSaving(mpclctDependants);

        final GenericObjectWithDependantsVO lowdcvo = clct.getGenericObjectWithDependantsCVO();
        final GenericObjectWithDependantsVO lowdcvoCurrent = new GenericObjectWithDependantsVO(lowdcvo,
                mpclctDependants.toDependantMasterDataMap());

        // update the whole thing (only if no state change is processing):
        final AtomicReference<GenericObjectWithDependantsVO> lowdcvoUpdated = new AtomicReference<GenericObjectWithDependantsVO>();
        if (isProcessingStateChange.get()) {
            lowdcvoUpdated.set(lowdcvoCurrent);
        } else {
            invoke(new CommonRunnable() {
                @Override
                public void run() throws CommonBusinessException {
                    lowdcvoUpdated.set(lodelegate.update(lowdcvoCurrent, getCustomUsage()));
                }
            });
        }
        // and return the updated version:
        return new CollectableGenericObjectWithDependants(lowdcvoUpdated.get());
    }

    /**
     * @param govo
     * @return a new <code>CollectableGenericObjectWithDependants</code> wrapping <code>govo</code>.
     */
    private static CollectableGenericObjectWithDependants newCollectableGenericObject(GenericObjectVO govo) {
        return CollectableGenericObjectWithDependants.newCollectableGenericObjectWithDependants(govo);
    }

    /**
     * @param clct
     * @return DependantCollectableMap containing the dependants of the given Collectable relevant for multiple updates
     *         additional data (if any) needed for multiple updates.
     * @throws CommonValidationException if some subform data is invalid.
     */
    @Override
    protected DependantCollectableMasterDataMap getAdditionalDataForMultiUpdate(
            CollectableGenericObjectWithDependants clct) throws CommonValidationException {
        return multiupdateofdependants.getDependantCollectableMapForUpdate(getSubFormControllersInDetails(), clct);
    }

    /**
     * @return the CollectableEntity that contains exactly the fields that are contained in the details tab.
     */
    @Override
    protected final CollectableEntity getCollectableEntityForDetails() {
        final Collection<String> collFieldNames = layoutrootDetails.getFieldNames();
        return new CollectableGenericObjectEntity(this.getEntityName(), getEntityLabel(), collFieldNames);
    }

    @Override
    protected CollectableGenericObjectWithDependants insertCollectable(
            CollectableGenericObjectWithDependants clctNew) throws CommonBusinessException {
        if (clctNew.getId() != null)
            throw new IllegalArgumentException("clctNew");

        final DependantCollectableMasterDataMap mpclctDependants = getAllSubFormData(null);

        // We have to clear the ids for cloned objects:
        // @todo eliminate this workaround - this is the wrong place. The right place is the Clone action!
        final DependantMasterDataMap mpDependants = org.nuclos.common.Utils
                .clearIds(mpclctDependants.toDependantMasterDataMap());

        //clctNew.validate(getCollectableEntityForDetails());

        final GenericObjectVO govo = clctNew.getGenericObjectCVO();
        final GenericObjectWithDependantsVO lowdcvoNew = new GenericObjectWithDependantsVO(govo, mpDependants);

        final AtomicReference<GenericObjectWithDependantsVO> lowdcvoInserted = new AtomicReference<GenericObjectWithDependantsVO>();
        invoke(new CommonRunnable() {
            @Override
            public void run() throws CommonBusinessException {
                lowdcvoInserted.set(lodelegate.create(lowdcvoNew, getSelectedSubEntityNames(), getCustomUsage()));
            }
        });
        return new CollectableGenericObjectWithDependants(lowdcvoInserted.get());
    }

    @Override
    protected void cloneSelectedCollectable() throws CommonBusinessException {
        super.cloneSelectedCollectable();

        EditModel modelDetails = getDetailsPanel().getEditModel();

        // Some attributes must be cleared for a new entity object:

        // Remember the origin of the cloned object
        CollectableComponentModel clctcompmodelIdentifier = modelDetails
                .getCollectableComponentModelFor(NuclosEOField.SYSTEMIDENTIFIER.getMetaData().getField());
        if (clctcompmodelIdentifier != null) {
            CollectableComponentModel clctcompmodelOrigin = modelDetails
                    .getCollectableComponentModelFor(NuclosEOField.ORIGIN.getMetaData().getField());
            if (clctcompmodelOrigin != null)
                clctcompmodelOrigin.setField(clctcompmodelIdentifier.getField());
            clctcompmodelIdentifier.clear();
        }

        CollectableComponentModel clctcompmodelStatus = modelDetails
                .getCollectableComponentModelFor(NuclosEOField.STATE.getMetaData().getField());
        if (clctcompmodelStatus != null)
            clctcompmodelStatus.clear();
        CollectableComponentModel clctcompmodelStatusNumeral = modelDetails
                .getCollectableComponentModelFor(NuclosEOField.STATENUMBER.getMetaData().getField());
        if (clctcompmodelStatusNumeral != null)
            clctcompmodelStatusNumeral.clear();
        CollectableComponentModel clctcompmodelStatusIcon = modelDetails
                .getCollectableComponentModelFor(NuclosEOField.STATEICON.getMetaData().getField());
        if (clctcompmodelStatusIcon != null)
            clctcompmodelStatusIcon.clear();

        CollectableField clctfield = getSelectedCollectable()
                .getField(NuclosEOField.STATE.getMetaData().getField());
        Integer statusId = (clctfield != null) ? (Integer) clctfield.getValueId() : null;

        if (statusId != null)
            // check permission of attributes
            for (DynamicAttributeVO attribute : getSelectedCollectable().getGenericObjectCVO().getAttributes()) {
                EntityFieldMetaDataVO fieldMeta = MetaDataClientProvider.getInstance().getEntityField(sEntity,
                        attribute.getAttributeId().longValue());
                CollectableComponentModel clctcompmodel = modelDetails
                        .getCollectableComponentModelFor(fieldMeta.getField());
                if (clctcompmodel != null) {
                    CollectableEntityField clctef = clctcompmodel.getEntityField();
                    Permission permission = SecurityCache.getInstance().getAttributePermission(sEntity,
                            clctef.getName(), statusId);

                    if (permission == null)
                        clctcompmodel.clear();
                }
            }
    }

    /**
     * gathers the data from all enabled subforms. All rows are gathered, even the removed ones.
     * @param oParentId set as the parent id for each subform row.
     * @return the data from all subforms
     */
    protected DependantCollectableMasterDataMap getAllSubFormData(CollectableGenericObjectWithDependants oParent)
            throws CommonValidationException {
        final DependantCollectableMasterDataMap result = new DependantCollectableMasterDataMap();
        final Collection<DetailsSubFormController<CollectableEntityObject>> sfControllers = getSubFormControllersInDetails();

        for (DetailsSubFormController<CollectableEntityObject> subformctl : sfControllers) {
            final String subEntity = subformctl.getCollectableEntity().getName();
            EntityMetaDataVO eMeta = MetaDataClientProvider.getInstance().getEntity(subEntity);
            if (!eMeta.isEditable()) {
                continue;
            }

            if ("nuclos_generalsearchdocument".equals(subEntity)) {
                String sPath = (String) Modules.getInstance().getModuleById(iModuleId).getField("documentPath");
                sPath = getDirectoryPath(StringUtils.emptyIfNull(sPath), oParent);
                if (sPath == null) {
                    continue;
                }
                for (CollectableMasterData md : subformctl.getCollectables()) {
                    if (md.getField("file").getValue() != null) {
                        GenericObjectDocumentFile docFile = (GenericObjectDocumentFile) md.getField("file")
                                .getValue();
                        docFile.setDirectoryPath(sPath);
                        CollectableValueField clctfield = new CollectableValueField(sPath);
                        if (md.getField("path") != null && !md.getField("path").equals(clctfield)) {
                            md.setField("path", clctfield);
                        }
                    }
                }
            }
            //NUCLEUSINT-1119
            if (subformctl.getSubForm().getParentSubForm() == null) {
                final List<CollectableEntityObject> dependants;
                if (oParent != null) {
                    dependants = subformctl.getAllCollectables(oParent.getId(), sfControllers, true, null);
                } else {
                    dependants = subformctl.getAllCollectables(null, sfControllers, true, null);
                }
                result.addValues(subEntity, dependants);
            }
        }

        return result;
    }

    private String getDirectoryPath(String path, CollectableGenericObjectWithDependants oParent) {
        return StringUtils.replaceParameters(path, new FormattingTransformer() {
            @Override
            protected Object getValue(String field) {
                return getDetailsEditView().getModel().getCollectableComponentModelFor(field).getField().getValue();
            }

            @Override
            protected String getEntity() {
                return getEntityName();
            }
        });
    }

    /**
     * @return
     * @postcondition result != null
     * @postcondition result.isComplete()
     */
    @Override
    public CollectableGenericObjectWithDependants newCollectable() {
        final Integer iModuleId = getModuleId();
        assert iModuleId != null;
        final CollectableGenericObjectWithDependants result = newCollectableGenericObject(
                new GenericObjectVO(iModuleId, null, null, null));
        assert result != null;
        assert getSearchStrategy().isCollectableComplete(result);
        setSubsequentStatesVisible(false, false);
        setStatesDefaultPathVisible(true, false);
        ;
        return result;
    }

    @Override
    protected void deleteCollectable(CollectableGenericObjectWithDependants clct) throws CommonBusinessException {
        assert !isHistoricalView();

        markCollectableAsDeleted(clct);
    }

    private GenericObjectVO getSelectedGenericObjectCVO() {
        final CollectableGenericObject clctlo = getSelectedCollectable();
        return (clctlo == null) ? null : clctlo.getGenericObjectCVO();
    }

    private void markCollectableAsDeleted(final CollectableGenericObjectWithDependants clctlo)
            throws CommonBusinessException {
        invoke(new CommonRunnable() {
            @Override
            public void run() throws CommonBusinessException {
                lodelegate.remove(clctlo.getGenericObjectWithDependantsCVO(), false, getCustomUsage());
            }
        });
    }

    /**
     * @deprecated Move to ResultController hierarchy.
     */
    protected void checkedDeleteCollectablePhysically(final CollectableGenericObjectWithDependants clctlo)
            throws CommonBusinessException {
        if (!isPhysicallyDeleteAllowed(clctlo))
            throw new CommonPermissionException(getSpringLocaleDelegate().getMessage(
                    "GenericObjectCollectController.41", "Endg\u00fcltiges L\u00f6schen ist nicht erlaubt."));

        invoke(new CommonRunnable() {
            @Override
            public void run() throws CommonBusinessException {
                lodelegate.remove(clctlo.getGenericObjectWithDependantsCVO(), true, getCustomUsage());
            }
        });
        broadcastCollectableEvent(clctlo, MessageType.DELETE_DONE);

        UIUtils.invokeOnDispatchThread(new Runnable() {
            @Override
            public void run() {
                GenericObjectCollectController.this.getResultTableModel().remove(clctlo);
            }
        });

        setHistoricalDate(null);

        getResultController().getSearchResultStrategy().refreshResult();
    }

    protected void checkedRestoreCollectable(final CollectableGenericObjectWithDependants clct)
            throws CommonBusinessException {
        if (!isPhysicallyDeleteAllowed(clct))
            throw new CommonPermissionException(getSpringLocaleDelegate()
                    .getMessage("GenericObjectCollectController.100", "Wiederherstellen ist nicht erlaubt."));
        invoke(new CommonRunnable() {
            @Override
            public void run() throws CommonBusinessException {
                lodelegate.restore(clct.getGenericObjectWithDependantsCVO(), getCustomUsage());
            }
        });
    }

    /**
     * @return ?
     * @deprecated This is a workaround for visibility issues of getSelectedCollectables(), only used for deletion of objects.
     * @todo Check if we need to call getCompleteSelectedCollectables for deletion
     */
    @Deprecated
    public final List<CollectableGenericObjectWithDependants> getListOfSelectedCollectables() {
        return super.getSelectedCollectables();
    }

    /**
     * @return the module id for leased objects collected in this controller. <code>null</code> for "general search".
     */
    public final Integer getModuleId() {
        return iModuleId;
    }

    /**
     * @return the module id of the selected Collectable, if any. The module id of this controller, otherwise.
     */
    protected Integer getSelectedCollectableModuleId() {
        final GenericObjectVO govo = getSelectedGenericObjectCVO();
        return (govo == null) ? getModuleId() : Integer.valueOf(govo.getModuleId());
    }

    /**
     * @deprecated Not in use - remove it.
     */
    private Set<String> getSelectedSubEntityNames() {
        return Collections.emptySet();
    }

    @Override
    public final String getEntityName() {
        return this.getEntityName(getModuleId());
    }

    protected final String getEntityName(Integer iModuleId) {
        return Modules.getInstance().getEntityNameByModuleId(iModuleId);
    }

    @Override
    protected String getEntityLabel() {
        return getModuleLabel(getModuleId());
    }

    protected final String getModuleLabel(Integer iModuleId) {
        return Modules.getInstance().getEntityLabelByModuleId(iModuleId);
    }

    /**
     * @return the id of the current (selected) entity object, if any.
     */
    private Integer getSelectedGenericObjectId() {
        return (Integer) getSelectedCollectableId();
    }

    /**
     * @return the state of the selected entity object, if any.
     */
    private CollectableField getSelectedGenericObjectState() {
        final CollectableField result;
        final Collectable clct = getSelectedCollectable();
        if (clct == null)
            result = null;
        else {
            result = clct.getField(NuclosEOField.STATE.getMetaData().getField());
            LOG.debug("getCurrentGenericObjectState: (value=" + result.getValue() + ", id=" + result.getValueId()
                    + ")");
        }
        return result;
    }

    /**
     * @return the state name of the selected entity object, if any.
     */
    private String getSelectedGenericObjectStateName() {
        final CollectableField clctfState = getSelectedGenericObjectState();
        return (clctfState == null) ? null
                : getSpringLocaleDelegate()
                        .getResource(
                                StateDelegate.getInstance().getStatemodelClosure(getModuleId())
                                        .getResourceSIdForLabel((Integer) clctfState.getValueId()),
                                LangUtils.toString(clctfState.getValue()));
    }

    /**
     * @return the state id of the selected entity object, if any.
     */
    private Integer getSelectedGenericObjectStateId() {
        final CollectableField clctfState = getSelectedGenericObjectState();
        return (clctfState == null) ? null : (Integer) clctfState.getValueId();
    }

    /**
     * @return the state numeral of the selected entity object, if any.
     */
    private Integer getSelectedGenericObjectStateNumeral() {
        final Integer result;
        final Collectable clct = getSelectedCollectable();
        if (clct == null)
            result = null;
        else
            result = (Integer) clct.getValue(NuclosEOField.STATENUMBER.getMetaData().getField());

        return result;
    }

    /**
     * @return the state icon of the selected entity object, if any.
     */
    private NuclosImage getSelectedGenericObjectStateIcon() {
        final NuclosImage result;
        final Collectable clct = getSelectedCollectable();
        if (clct == null)
            result = null;
        else
            try {
                result = (NuclosImage) clct.getValue(NuclosEOField.STATEICON.getMetaData().getField());
            } catch (CommonFatalException e) {
                // ignore here.
                return null;
            }
        return result;
    }

    /**
     * @return the state color of the selected entity object, if any.
     */
    private String getSelectedGenericObjectStateColor() {
        final String result;
        final Collectable clct = getSelectedCollectable();
        if (clct == null)
            result = null;
        else
            try {
                result = StateDelegate.getInstance().getState(getModuleId(),
                        (Integer) clct.getValueId(NuclosEOField.STATE.getMetaData().getField())).getColor();
            } catch (CommonFatalException e) {
                // ignore here.
                return null;
            }
        return result;
    }

    private StateWrapper getStateWrapperFromSelectedGenericObject() {
        Integer id = null;
        Integer iNumeral = null;
        String sName = null;
        String sDescription = null;
        NuclosImage icon = null;
        String color = null;
        ResourceVO resButtonIcon = null;

        final Collectable clct = getSelectedCollectable();
        if (clct != null)
            try {
                StateVO statevo = StateDelegate.getInstance().getState(getModuleId(),
                        (Integer) clct.getValueId(NuclosEOField.STATE.getMetaData().getField()));
                if (statevo == null)
                    return null;
                id = statevo.getId();
                iNumeral = statevo.getNumeral();
                sName = statevo.getStatename();
                sDescription = statevo.getDescription();
                icon = statevo.getIcon();
                color = statevo.getColor();
                resButtonIcon = statevo.getButtonIcon();
            } catch (CommonFatalException e) {
                // ignore here.
            }

        return new StateWrapper(id, iNumeral, sName, icon, sDescription, color, resButtonIcon);
    }

    /**
     * Delete the selected object physically.
     * This is mostly a copy from CollectController.cmdDelete; just the message and the called delete method are different.
     */
    protected void cmdDeletePhysically() {
        assert getCollectStateModel().getCollectState()
                .equals(new CollectState(CollectState.OUTERSTATE_DETAILS, CollectState.DETAILSMODE_VIEW));

        if (stopEditingInDetails()) {
            final String sMessage = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.73",
                    "Soll der angezeigte Datensatz ({0}) wirklich endg\u00fcltig aus der Datenbank gel\u00f6scht werden?\nDieser Vorgang kann nicht r\u00fcckg\u00e4ngig gemacht werden! ",
                    getSelectedCollectable().getIdentifierLabel());
            final int iBtn = JOptionPane.showConfirmDialog(getTab(), sMessage, getSpringLocaleDelegate()
                    .getMessage("GenericObjectCollectController.24", "Datensatz endg\u00fcltig l\u00f6schen"),
                    JOptionPane.YES_NO_OPTION);

            if (iBtn == JOptionPane.OK_OPTION)
                UIUtils.runCommand(getTab(), new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // try to find next or previous object:
                            final JTable tblResult = getResultTable();
                            final int iSelectedRow = tblResult.getSelectedRow();
                            if (iSelectedRow < 0)
                                throw new IllegalStateException();

                            final int iNewSelectedRow;
                            if (iSelectedRow < tblResult.getRowCount() - 1)
                                // the selected row is not the last row: select the next row
                                iNewSelectedRow = iSelectedRow;
                            else if (iSelectedRow > 0)
                                // the selected row is not the first row: select the previous row
                                iNewSelectedRow = iSelectedRow - 1;
                            else {
                                // the selected row is the single row: don't select a row
                                assert tblResult.getRowCount() == 1;
                                assert iSelectedRow == 0;
                                iNewSelectedRow = -1;
                            }

                            checkedDeleteCollectablePhysically(getSelectedCollectable());

                            if (iNewSelectedRow == -1) {
                                tblResult.clearSelection();
                                // switch to new mode:
                                setCollectState(CollectState.OUTERSTATE_DETAILS, CollectState.DETAILSMODE_NEW);
                            } else {
                                tblResult.setRowSelectionInterval(iNewSelectedRow, iNewSelectedRow);
                                // go into view mode again:
                                setCollectState(CollectState.OUTERSTATE_DETAILS, CollectState.DETAILSMODE_VIEW);
                            }
                        } catch (CommonPermissionException ex) {
                            final String sErrorMessage = getSpringLocaleDelegate().getMessage(
                                    "GenericObjectCollectController.70",
                                    "Sie verf\u00fcgen nicht \u00fcber die ausreichenden Rechte, um dieses Objekt zu l\u00f6schen.");
                            Errors.getInstance().showExceptionDialog(getTab(), sErrorMessage, ex);
                        } catch (CommonBusinessException ex) {
                            if (!handlePointerException(ex))
                                Errors.getInstance().showExceptionDialog(getTab(),
                                        getSpringLocaleDelegate().getMessage("GenericObjectCollectController.18",
                                                "Das Objekt konnte nicht gel\u00f6scht werden."),
                                        ex);
                        }
                    }
                });
        }
    }

    private void cmdJumpToTree() {
        UIUtils.runCommand(getTab(), new CommonRunnable() {
            @Override
            public void run() throws CommonFinderException {
                CollectableGenericObjectWithDependants cgo = getSelectedCollectable();
                if (cgo != null)
                    getExplorerController().showInOwnTab(ExplorerDelegate.getInstance().getGenericObjectTreeNode(
                            cgo.getId(), cgo.getGenericObjectWithDependantsCVO().getModuleId(), null));
            }
        });
    }

    private void cmdShowStateHistory() {
        UIUtils.runCommand(getTab(), new CommonRunnable() {
            @Override
            public void run() throws CommonBusinessException {
                final CollectableGenericObject clctSelected = getSelectedCollectable();

                final int iGenericObjectId = clctSelected.getGenericObjectCVO().getId();
                final String sIdentifier = clctSelected.getIdentifierLabel();

                new StateHistoryController(getTab()).run(getSelectedCollectableModuleId(), iGenericObjectId,
                        sIdentifier);
            }
        });
    }

    private void cmdShowLogBook() {
        UIUtils.runCommand(getTab(), new Runnable() {
            @Override
            public void run() {
                final CollectableGenericObject clctSelected = getSelectedCollectable();

                assert clctSelected != null;

                final int iGenericObjectId = clctSelected.getGenericObjectCVO().getId();
                final String sIdentifier = clctSelected.getIdentifierLabel();

                try {
                    /** @todo frame vs. parent */
                    new LogbookController(getTab(), getSelectedCollectableModuleId(), iGenericObjectId,
                            getPreferences()).run(sIdentifier);
                } catch (CommonBusinessException ex) {
                    Errors.getInstance().showExceptionDialog(getTab(), ex);
                }
            }
        });
    }

    /**
     * command: switch to View mode
     */
    @Override
    protected void cmdEnterViewMode() {
        if (isSelectedCollectableMarkedAsDeleted())
            if (!SecurityCache.getInstance().isActionAllowed(Actions.ACTION_READ_DELETED_RECORD))
                throw new NuclosFatalException(getSpringLocaleDelegate().getMessage(
                        "GenericObjectCollectController.65",
                        "Sie haben nicht die erforderlichen Rechte, gel\u00f6schte Datens\u00e4tze zu lesen."));

        super.cmdEnterViewMode();
    }

    /**
     * alternative entry point: view single historical object
     * @param clct the object to view in details
     */
    public final void runViewSingleHistoricalCollectable(CollectableGenericObjectWithDependants clct,
            Date dateHistorical) {
        viewSingleHistoricalCollectable(clct, dateHistorical);
        getTab().setVisible(true);
    }

    private void viewSingleHistoricalCollectable(CollectableGenericObjectWithDependants clct, Date dateHistorical) {
        // fill result table:
        final List<CollectableGenericObjectWithDependants> lstResult = new ArrayList<CollectableGenericObjectWithDependants>();
        lstResult.add(clct);
        this.fillResultPanel(lstResult);

        getCollectPanel().getResultPanel().getResultTable().setRowSelectionInterval(0, 0);
        // select the one result row

        setHistoricalDate(dateHistorical);

        cmdEnterViewMode();
        disableToolbarButtonsForHistoricalView();

    }

    private void disableToolbarButtonsForHistoricalView() {
        final CollectPanel<CollectableGenericObjectWithDependants> collectPanel = getCollectPanel();
        collectPanel.setTabbedPaneEnabledAt(CollectState.OUTERSTATE_SEARCH, false);
        collectPanel.setTabbedPaneEnabledAt(CollectState.OUTERSTATE_RESULT, false);
        getDetailsPanel().btnDelete.setEnabled(false);
        actDeleteSelectedCollectablesPhysically.setEnabled(false);
        btnExecuteRule.setEnabled(false);
        btnMakeTreeRoot.setEnabled(false);
        getRefreshCurrentCollectableAction().setEnabled(false);
        btnShowLogBook.setEnabled(false);
        btnShowStateHistory.setEnabled(false);
        getLastAction().setEnabled(false);
        getFirstAction().setEnabled(false);
        getNewAction().setEnabled(false);
    }

    /**
     * alternative entry point: view multiple objects in Details (multi edit) or result panel
     * @param lstclct the objects to view in Details
     * @param bShowInDetails Show in Details panel? (false: show in Result panel)
     */
    public final void runViewMultipleCollectables(List<CollectableGenericObjectWithDependants> lstclct,
            boolean bShowInDetails) {
        viewMultipleCollectables(lstclct, bShowInDetails);
        getTab().setVisible(true);
    }

    /**
     * @param lstclct the objects to view in Result or Details panel
     * @param bShowInDetails Show in Details panel? (false: show in Result panel)
     */
    private void viewMultipleCollectables(List<CollectableGenericObjectWithDependants> lstclct,
            boolean bShowInDetails) {
        // set search condition to match the result (so that refresh will give the same result):
        final Collection<Object> collIds = CollectionUtils.transform(lstclct, new Collectable.GetId());
        final CollectableSearchCondition cond = SearchConditionUtils.getCollectableSearchConditionForIds(collIds);
        setCollectableSearchConditionInSearchPanel(cond);

        // fill result table:
        this.fillResultPanel(lstclct);

        // select all result rows:
        getCollectPanel().getResultPanel().getResultTable().setRowSelectionInterval(0, lstclct.size() - 1);

        if (bShowInDetails)
            cmdEnterMultiViewMode();
        else
            UIUtils.runCommand(getTab(), new CommonRunnable() {
                @Override
                public void run() throws CommonBusinessException {
                    GenericObjectCollectController.this.setCollectState(CollectState.OUTERSTATE_RESULT,
                            CollectState.RESULTMODE_NOSELECTION);
                }
            });
    }

    /**
     * shows/hides the buttons for switching to subsequent states
     * @param bVisible
     * @param bEnableButtons
     */
    private void setSubsequentStatesVisible(boolean bVisible, boolean bEnableButtons) {
        // remove all previous listeners:
        for (ActionListener listener : cmbbxCurrentState.getActionListeners())
            cmbbxCurrentState.removeActionListener(listener);

        // initialize:
        cmbbxCurrentState.removeAllItems();

        if (!bVisible)
            cmbbxCurrentState.setVisible(false);
        else {
            lbCurrentState.setVisible(true); // label although needed to be visible. otherwise nothing is shown if this is a new collectable (without previuos editing an existing record.)
            cmbbxCurrentState.setVisible(true);
            cmbbxCurrentState.setEnabled(bEnableButtons);
            final Integer iGenericObjectId = getSelectedGenericObjectId();
            if (iGenericObjectId != null) {
                // Create a temporary list for sorting the entries before entering into combo box
                final List<StateWrapper> lstComboEntries = new ArrayList<StateWrapper>();

                final StateWrapper stateCurrent = getStateWrapperFromSelectedGenericObject();
                lstComboEntries.add(stateCurrent);

                UsageCriteria uc = getUsageCriteria(getSelectedCollectable());
                DynamicAttributeVO av = getSelectedCollectable().getGenericObjectCVO()
                        .getAttribute(NuclosEOField.STATE.getMetaData().getId().intValue());
                List<StateVO> lstSubsequentStates = StateDelegate.getInstance().getStatemodel(uc)
                        .getSubsequentStates(av.getValueId(), false);

                // Copy all subsequent states to the sorting list:
                for (StateVO statevo : lstSubsequentStates)
                    if (statevo == null)
                        // we don't want to throw an exception here, so we just log the error:
                        LOG.error("Die Liste der Folgestati enth\u00e4lt ein null-Objekt.");
                    else if (!lstComboEntries.contains(new StateWrapper(statevo.getId(), statevo.getNumeral(),
                            statevo.getStatename(), statevo.getIcon(), statevo.getDescription(), null, null)))
                        lstComboEntries.add(new StateWrapper(statevo.getId(), statevo.getNumeral(),
                                getSpringLocaleDelegate()
                                        .getResource(/*StateDelegate.getInstance().getResourceSIdForName(statevo.getId()*/
                                                StateDelegate.getInstance().getStatemodelClosure(getModuleId())
                                                        .getResourceSIdForLabel(statevo.getId()),
                                                statevo.getStatename()),
                                statevo.getIcon(), statevo.getDescription(), statevo.getColor(),
                                statevo.getButtonIcon(), statevo.isFromAutomatic(),
                                getSelectedCollectable() != null
                                        ? StateDelegate.getInstance().getStatemodel(uc)
                                                .isStateReachableInDefaultPath(stateCurrent.getId(), statevo)
                                        : StateDelegate.getInstance().getStatemodel(uc)
                                                .isStateReachableInDefaultPathByNumeral(stateCurrent.getNumeral(),
                                                        statevo)));

                // Sort and finally enter the items into the combo box:
                Collections.sort(lstComboEntries);
                for (StateWrapper state : lstComboEntries)
                    if (state != null)
                        cmbbxCurrentState.addItem(state);
                cmbbxCurrentState.setSelectedItem(stateCurrent);

                final ActionListener al = new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent ev) {
                        UIUtils.runShortCommandLater(getTab(), new CommonRunnable() {
                            @Override
                            public void run() {
                                final StateWrapper state = (StateWrapper) cmbbxCurrentState.getSelectedItem();
                                if (state != stateCurrent && state != null && state.getId() != null) {
                                    cmdChangeState(state);
                                }
                            }
                        });
                    }
                };
                cmbbxCurrentState.addActionListener(al);
            }
        }

        cmbbxCurrentState.setEnabled(bEnableButtons && cmbbxCurrentState.getItemCount() != 0);
    }

    /**
     * shows/hides the buttons for switching to states default path
     * @param bVisible
     * @param bEnableButtons
     */
    private void setStatesDefaultPathVisible(boolean bVisible, boolean bEnableButtons) {
        // remove all previous listeners:
        cmpStateStandardView.removeActionListeners();

        // initialize:
        cmpStateStandardView.removeAllItems();
        cmpStateStandardView.setSelectedItem(null);

        if (!bVisible)
            cmpStateStandardView.setVisible(false);
        else {
            cmpStateStandardView.setVisible(true);
            cmpStateStandardView.setEnabled(bEnableButtons);

            // Create a temporary list for sorting the entries before entering into combo box
            final List<StateWrapper> lstDefaultPathEntries = new ArrayList<StateWrapper>();

            final StateWrapper stateCurrent = getStateWrapperFromSelectedGenericObject();

            UsageCriteria uc = null;
            List<StateVO> lstDefaultPathStates = new ArrayList<StateVO>();

            try {
                uc = getSelectedCollectable() == null ? getUsageCriteriaFromView(false)
                        : getUsageCriteria(getSelectedCollectable());
                lstDefaultPathStates.addAll(StateDelegate.getInstance().getStatemodel(uc).getDefaultStatePath());
            } catch (Exception e) {
                // ignore
            }

            // Copy all subsequent states to the sorting list:
            for (StateVO statevo : lstDefaultPathStates)
                if (statevo == null)
                    // we don't want to throw an exception here, so we just log the error:
                    LOG.error("Die Liste der Folgestati enth\u00e4lt ein null-Objekt.");
                else if (!lstDefaultPathEntries.contains(new StateWrapper(statevo.getId(), statevo.getNumeral(),
                        statevo.getStatename(), statevo.getIcon(), statevo.getDescription(), null, null)))
                    lstDefaultPathEntries.add(new StateWrapper(statevo.getId(), statevo.getNumeral(),
                            getSpringLocaleDelegate()
                                    .getResource(/*StateDelegate.getInstance().getResourceSIdForName(statevo.getId()*/
                                            StateDelegate.getInstance().getStatemodelClosure(getModuleId())
                                                    .getResourceSIdForLabel(statevo.getId()),
                                            statevo.getStatename()),
                            statevo.getIcon(), statevo.getDescription(), statevo.getColor(),
                            statevo.getButtonIcon(), statevo.isFromAutomatic(),
                            getSelectedCollectable() != null
                                    ? StateDelegate.getInstance().getStatemodel(uc)
                                            .isStateReachableInDefaultPath(stateCurrent.getId(), statevo)
                                    : StateDelegate.getInstance().getStatemodel(uc)
                                            .isStateReachableInDefaultPathByNumeral(stateCurrent.getNumeral(),
                                                    statevo)));

            cmpStateStandardView.setSelectedItem(
                    getSelectedCollectable() == null || getSelectedCollectable().getId() == null ? null
                            : stateCurrent);
            cmpStateStandardView.addItems(lstDefaultPathEntries);

            for (Iterator<StateWrapper> iterator = lstDefaultPathEntries.iterator(); iterator.hasNext();) {
                final StateWrapper item = iterator.next();
                final ActionListener al = new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent ev) {
                        UIUtils.runShortCommand(getTab(), new CommonRunnable() {
                            @Override
                            public void run() {
                                if (item != stateCurrent && item != null && item.getId() != null) {
                                    cmdChangeStates(stateCurrent, item, item.getStatesBefore());
                                }
                            }
                        });
                    }
                };
                cmpStateStandardView.addActionListener(al, item);

                if (LangUtils.equals(stateCurrent.getNumeral(), item.getNumeral())) {
                    final List<StateWrapper> lstSubsequentEntries = new ArrayList<StateWrapper>();
                    List<StateVO> lstSubsequentStates = StateDelegate.getInstance().getStatemodel(uc)
                            .getSubsequentStates(item.getId(), false);

                    // Copy all subsequent states to the sorting list:
                    for (StateVO statevo : lstSubsequentStates)
                        if (statevo == null)
                            // we don't want to throw an exception here, so we just log the error:
                            LOG.error("Die Liste der Folgestati enth\u00e4lt ein null-Objekt.");
                        else if (!lstSubsequentEntries.contains(
                                new StateWrapper(statevo.getId(), statevo.getNumeral(), statevo.getStatename(),
                                        statevo.getIcon(), statevo.getDescription(), null, null)))
                            lstSubsequentEntries.add(new StateWrapper(statevo.getId(), statevo.getNumeral(),
                                    getSpringLocaleDelegate()
                                            .getResource(/*StateDelegate.getInstance().getResourceSIdForName(statevo.getId()*/
                                                    StateDelegate.getInstance().getStatemodelClosure(getModuleId())
                                                            .getResourceSIdForLabel(statevo.getId()),
                                                    statevo.getStatename()),
                                    statevo.getIcon(), statevo.getDescription(), statevo.getColor(),
                                    statevo.getButtonIcon()));

                    Map<StateWrapper, Action> mpSubsequentStatesAction = new HashMap<StateWrapper, Action>();
                    for (Iterator<StateWrapper> iterator2 = lstSubsequentEntries.iterator(); iterator2.hasNext();) {
                        final StateWrapper subsequentState = iterator2.next();
                        if (!subsequentState.getNumeral().equals(item.getNumeral())) {
                            Action act = new AbstractAction() {

                                @Override
                                public void actionPerformed(ActionEvent e) {
                                    cmdChangeState(subsequentState);
                                }
                            };
                            mpSubsequentStatesAction.put(subsequentState, act);
                        }
                    }
                    cmpStateStandardView.addSubsequentStatesActionListener(item, mpSubsequentStatesAction);
                }
            }
        }

        cmpStateStandardView.setEnabled(getSelectedCollectable() == null ? false
                : (bEnableButtons && cmpStateStandardView.getItemCount() != 0));
    }

    public List<StateVO> getPossibleSubsequentStates() {
        UsageCriteria uc = getUsageCriteria(getSelectedCollectable());
        DynamicAttributeVO av = getSelectedCollectable().getGenericObjectCVO()
                .getAttribute(NuclosEOField.STATE.getMetaData().getId().intValue());
        return StateDelegate.getInstance().getStatemodel(uc).getSubsequentStates(av.getValueId(), false);
    }

    private void showCustomActions(int iDetailsMode) {
        final boolean bWasDetailsChangedIgnored = isDetailsChangedIgnored();
        setDetailsChangedIgnored(true);

        try {
            final boolean bSingle = CollectState.isDetailsModeViewOrEdit(iDetailsMode);
            final boolean bMulti = CollectState.isDetailsModeMultiViewOrEdit(iDetailsMode);
            final boolean bViewOrEdit = bSingle || bMulti;
            final boolean bView = bViewOrEdit && !CollectState.isDetailsModeChangesPending(iDetailsMode);

            this.getDetailsPanel().removeToolBarComponents(toolbarCustomActionsDetails);
            //toolbarCustomActionsDetails.removeAll();
            toolbarCustomActionsDetails.clear();
            if (toolbarCustomActionsDetailsIndex == -1) {
                return; //
            }

            if (!bViewOrEdit) {
                toolbarCustomActionsDetails.add(lbCurrentState);
            } else {
                // button: "print details":
                if (isHistoricalView())
                    toolbarCustomActionsDetails.add(lbCurrentState);
                else {
                    if (bSingle) {
                        /** @todo print historical order */
                        toolbarCustomActionsDetails.add(btnPrintDetails);
                        btnPrintDetails.setEnabled(bView && hasFormsAssigned(getSelectedCollectable()));
                    }

                    toolbarCustomActionsDetails.add(lbCurrentState);

                    // buttons/actions for "generate leased object":
                    if (!isSelectedCollectableMarkedAsDeleted()) {
                        addGeneratorActions(bView, toolbarCustomActionsDetails);
                    }
                    UIUtils.ensureMinimumSize(getTab());
                }
            }

            //if (SecurityCache.getInstance().isActionAllowed(Actions.ACTION_USE_INVALID_MASTERDATA)) {
            //toolbarCustomActionsDetails.add(Box.createHorizontalStrut(5));
            //toolbarCustomActionsDetails.add(chkbxUseInvalidMasterData);
            //toolbarCustomActionsDetails.add(Box.createHorizontalStrut(5));
            //}

            //toolbarCustomActionsDetails.revalidate();

            /*if (cmpStateStandardView.getItemCount() != 0) {
               toolbarCustomActionsDetails.add(Box.createHorizontalStrut(2000));
               toolbarCustomActionsDetails.add(new BlackLabel(cmpStateStandardView, SpringLocaleDelegate.getMessage("GenericObjectCollectController.107","Standardpfad")));
            }*/
            this.getDetailsPanel().addToolBarComponents(toolbarCustomActionsDetails,
                    toolbarCustomActionsDetailsIndex);
        } finally {
            setDetailsChangedIgnored(bWasDetailsChangedIgnored);
        }
    }

    protected final ExplorerController getExplorerController() {
        return getMainController().getExplorerController();
    }

    /** @todo consider to move this one to [nucleus.]common */

    private static class NoSuchElementException extends NuclosBusinessException {
    }

    /**
     * @return the common field "value" of the given Collectables. This value is built using the given <code>transformer</code>
     *         on each <code>Collectable</code>'s field with the given name. The transformer will usually be <code>GetValue</code> or <code>GetValueId</code>.
     * @throws NoSuchElementException if there is no common field "value" (or if the given <code>Collection</code> is empty)
     * @postcondition result != null
     */
    private static Object getCollectablesCommonField(Collection<? extends Collectable> collclct, String sFieldName,
            Transformer<CollectableField, Object> transformer) throws NoSuchElementException {
        if (collclct.isEmpty())
            throw new NoSuchElementException();
        final Iterator<? extends Collectable> iter = collclct.iterator();
        final Object result = transformer.transform(iter.next().getField(sFieldName));
        while (iter.hasNext())
            if (!LangUtils.equals(result, transformer.transform(iter.next().getField(sFieldName))))
                throw new NoSuchElementException();
        return result;
    }

    private Integer getSelectedGenericObjectsCommonFieldIdByFieldName(String sFieldName)
            throws NoSuchElementException {
        return (Integer) getCollectablesCommonField(getSelectedCollectables(), sFieldName,
                new CollectableField.GetValueId());
    }

    /**
     * @return the common state numeral of the selected leased objects.
     * @throws NoSuchElementException if there is no common state numeral (or if there is no selected object at all)
     * @postcondition result != null
     */
    private Integer getSelectedGenericObjectsCommonStateNumeral() throws NoSuchElementException {
        final Integer result = (Integer) getCollectablesCommonField(getSelectedCollectables(),
                NuclosEOField.STATENUMBER.getMetaData().getField(), new CollectableField.GetValue());
        assert result != null;
        return result;
    }

    /**
     * @return do the selected leased objects share a common state?
     */
    private boolean doTheSelectedGenericObjectsShareACommonState() {
        boolean result;
        try {
            getSelectedGenericObjectsCommonStateNumeral();
            result = true;
        } catch (NoSuchElementException ex) {
            result = false;
        }
        return result;
    }

    /**
     * @param stateNew
     * @return Did the user press ok?
     * @precondition !this.isHistoricalView()
     * @precondition this.getCollectState().isDetailsMode()
     * NUCLEUSINT-1159 needed for accessing the statechange for status button
     */
    public void cmdChangeState(final StateWrapper stateNew) {
        if (isHistoricalView())
            throw new IllegalStateException(
                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.90",
                            "Statuswechsel ist in historischer Ansicht nicht m\u00f6glich."));
        if (!getCollectState().isDetailsMode() && !getCollectState().isResultMode())
            throw new IllegalStateException(
                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.91",
                            "Statuswechsel ist nur in Detail- order Ergebnisansicht m\u00f6glich."));

        //      final boolean bMultiEdit = getCollectState().isDetailsModeMultiViewOrEdit();
        //
        //      String sQuestion = bMultiEdit
        //      ? getSpringLocaleDelegate().getMessage("GenericObjectCollectController.79","Soll der Wechsel in den Status \"{0}\" f\u00fcr die ausgew\u00e4hlten Objekte wirklich durchgef\u00fchrt werden?\nDie vorgenommenen \u00c4nderungen an dem Objekt werden gespeichert.", stateNew.getStatusText())
        //         : getSpringLocaleDelegate().getMessage("GenericObjectCollectController.80","Soll der Wechsel in den Status \"{0}\" wirklich durchgef\u00fchrt werden?", stateNew.getStatusText());
        //
        //      Object[] argsOptionPane = new Object[] {sQuestion};
        //      if (stateNew.getDescription() != null && !stateNew.getDescription().isEmpty()) {
        //         JTextArea ta = new JTextArea();
        //         ta.setEnabled(true);
        //         ta.setEditable(false);
        //         ta.setText(stateNew.getDescription());
        //         ta.setCaretPosition(0);
        //         argsOptionPane = new Object[] {
        //               sQuestion,
        //               "\n",
        //               ta
        //         };
        //      }
        //
        //      final int btn = JOptionPane.showConfirmDialog(getTab(), argsOptionPane,
        //            getSpringLocaleDelegate().getMessage("GenericObjectCollectController.85","Statuswechsel durchf\u00fchren"),
        //         JOptionPane.OK_CANCEL_OPTION);
        //
        //      // repaint directly:
        //      //getFrame().repaint();
        //
        //      final boolean result = (btn == JOptionPane.OK_OPTION);
        //
        //      stopEditingInDetails();
        //
        final StateWrapper stateCurrent = getStateWrapperFromSelectedGenericObject();
        //
        //      if (result)   {
        //         changeState(stateNew);
        //      }

        OverlayOptionPane.showConfirmDialog(
                getTab(), new ChangeStatePanel(stateCurrent, stateNew), getSpringLocaleDelegate()
                        .getMessage("GenericObjectCollectController.85", "Statuswechsel durchf\u00fchren") + "?",
                JOptionPane.YES_NO_OPTION, new OvOpAdapter() {
                    public void done(int result) {
                        final boolean ok = (result == JOptionPane.YES_OPTION);

                        stopEditingInDetails();

                        if (ok) {
                            changeState(stateNew);
                        } else {
                            if (stateCurrent != null) {
                                cmbbxCurrentState.setSelectedItem(stateCurrent);
                                cmpStateStandardView.setSelectedItem(stateCurrent);
                            }
                        }
                    }
                });
    }

    /**
     * @param stateNew
     * @return Did the user press ok?
     * @precondition !this.isHistoricalView()
     * @precondition this.getCollectState().isDetailsMode()
     * NUCLEUSINT-1159 needed for accessing the statechange for status button
     */
    private void cmdChangeStates(final StateWrapper stateSource, final StateWrapper stateFinal,
            final List<Integer> statesNew) {
        if (isHistoricalView())
            throw new IllegalStateException(
                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.90",
                            "Statuswechsel ist in historischer Ansicht nicht m\u00f6glich."));

        final boolean bMultiEdit;
        if (getCollectState().isDetailsMode()) {
            bMultiEdit = getCollectState().isDetailsModeMultiViewOrEdit();
        } else if (getCollectState().isResultMode()) {
            bMultiEdit = getSelectedCollectables().size() > 1;
        } else {
            throw new IllegalStateException(
                    getSpringLocaleDelegate().getMessage("GenericObjectCollectController.91",
                            "Statuswechsel ist nur in Detail- order Ergebnisansicht m\u00f6glich."));
        }

        //      String sQuestion;
        //      if (bMultiEdit) {
        //         if (getCollectState().isDetailsMode()) {
        //            sQuestion = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.79","Soll der Wechsel in den Status \"{0}\" f\u00fcr die ausgew\u00e4hlten Objekte wirklich durchgef\u00fchrt werden?", stateFinal.getStatusText()) 
        //                  + getSpringLocaleDelegate().getMessage("GenericObjectCollectController.79b","\nDie vorgenommenen \u00c4nderungen an dem Objekt werden gespeichert.", stateFinal.getStatusText());
        //         } else {
        //            sQuestion = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.79","Soll der Wechsel in den Status \"{0}\" f\u00fcr die ausgew\u00e4hlten Objekte wirklich durchgef\u00fchrt werden?", stateFinal.getStatusText());
        //         }
        //      } else {
        //         sQuestion = getSpringLocaleDelegate().getMessage("GenericObjectCollectController.80","Soll der Wechsel in den Status \"{0}\" wirklich durchgef\u00fchrt werden?", stateFinal.getStatusText());
        //      }
        //
        //      Object[] argsOptionPane = new Object[] {sQuestion};
        //      if (stateFinal.getDescription() != null && !stateFinal.getDescription().isEmpty()) {
        //         JTextArea ta = new JTextArea();
        //         ta.setEnabled(true);
        //         ta.setEditable(false);
        //         ta.setText(stateFinal.getDescription());
        //         ta.setCaretPosition(0);
        //         argsOptionPane = new Object[] {
        //               sQuestion,
        //               "\n",
        //               ta
        //         };
        //      }

        OverlayOptionPane.showConfirmDialog(
                getTab(), new ChangeStatePanel(stateSource, stateFinal), getSpringLocaleDelegate()
                        .getMessage("GenericObjectCollectController.85", "Statuswechsel durchf\u00fchren") + "?",
                JOptionPane.YES_NO_OPTION, new OvOpAdapter() {
                    public void done(int result) {
                        final boolean ok = (result == JOptionPane.YES_OPTION);

                        stopEditingInDetails();

                        if (ok) {
                            changeStates(stateFinal, statesNew);
                        } else {
                            if (stateSource != null) {
                                cmpStateStandardView.setSelectedItem(stateSource);
                            }
                        }
                    }
                });

    }

    /**
     * @param stateNew
     * @precondition !this.isHistoricalView()
     * @precondition this.getCollectState().isDetailsMode()
     * NUCLEUSINT-1159 needed for accessing the statechange for status button
     */
    protected void changeState(final StateWrapper stateNew) {
        final boolean bMultiEdit = getCollectState().isDetailsModeMultiViewOrEdit();

        stopEditingInDetails();

        final StateWrapper stateCurrent = getStateWrapperFromSelectedGenericObject();

        class ChangeStateWorker1 implements CommonRunnable {

            private ChangeStateWorker1() {
            }

            @Override
            public void run() throws CommonBusinessException {
                if (getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_EDIT
                        || getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_NEW_CHANGED) {
                    if (bMultiEdit) {
                        getSubFormsLoader().setAfterLoadingRunnable(new CommonRunnable() {
                            @Override
                            public void run() throws CommonBusinessException {
                                GenericObjectCollectController.this.changeStateForMultipleObjects(stateNew);
                            }
                        });
                        try {
                            save();
                        } catch (CommonBusinessException ex) {
                            getSubFormsLoader().setAfterLoadingRunnable(null);
                            final String sErrorMsg = getSpringLocaleDelegate().getMessage(
                                    "GenericObjectCollectController.34",
                                    "Der Statuswechsel konnte nicht vollzogen werden.");
                            handleSaveException(ex, sErrorMsg);

                            // redisplay the old status
                            cmbbxCurrentState.setSelectedItem(stateCurrent);
                            cmpStateStandardView.setSelectedItem(stateCurrent);
                        }
                    } else {
                        GenericObjectCollectController.this.changeStateForSingleObjectAndSave(stateNew);
                    }

                } else if (bMultiEdit)
                    GenericObjectCollectController.this.changeStateForMultipleObjects(stateNew);
                else
                    GenericObjectCollectController.this.changeStateForSingleObjectAndSave(stateNew);
            }
        }

        UIUtils.runShortCommand(getTab(), new ChangeStateWorker1());
    }

    /**
     * @param statesNew
     * @param stateFinal
     * @precondition !this.isHistoricalView()
     * @precondition this.getCollectState().isDetailsMode()
     * NUCLEUSINT-1159 needed for accessing the statechange for status button
     */
    private void changeStates(final StateWrapper stateFinal, final List<Integer> statesNew) {
        final boolean bMultiEdit;
        if (getCollectState().isDetailsMode()) {
            bMultiEdit = getCollectState().isDetailsModeMultiViewOrEdit();
        } else if (getCollectState().isResultMode()) {
            bMultiEdit = getSelectedCollectables().size() > 1;
        } else {
            throw new IllegalArgumentException("Illegal collect state");
        }

        stopEditingInDetails();

        final StateWrapper stateCurrent = getStateWrapperFromSelectedGenericObject();

        class ChangeStateWorker2 implements CommonRunnable {

            private ChangeStateWorker2() {
            }

            @Override
            public void run() throws CommonBusinessException {
                if (getCollectState().isResultMode()) {
                    if (bMultiEdit)
                        GenericObjectCollectController.this.changeStatesForMultipleObjects(stateFinal, statesNew);
                    else
                        GenericObjectCollectController.this.changeStatesForSingleObjectAndSave(statesNew);
                } else if (getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_EDIT
                        || getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_NEW_CHANGED) {
                    if (bMultiEdit) {
                        getSubFormsLoader().setAfterLoadingRunnable(new CommonRunnable() {
                            @Override
                            public void run() throws CommonBusinessException {
                                GenericObjectCollectController.this.changeStatesForMultipleObjects(stateFinal,
                                        statesNew);
                            }
                        });
                        try {
                            save();
                        } catch (CommonBusinessException ex) {
                            getSubFormsLoader().setAfterLoadingRunnable(null);
                            final String sErrorMsg = getSpringLocaleDelegate().getMessage(
                                    "GenericObjectCollectController.34",
                                    "Der Statuswechsel konnte nicht vollzogen werden.");
                            handleSaveException(ex, sErrorMsg);

                            // redisplay the old status
                            cmbbxCurrentState.setSelectedItem(stateCurrent);
                            cmpStateStandardView.setSelectedItem(stateCurrent);
                        }
                    } else {
                        GenericObjectCollectController.this.changeStatesForSingleObjectAndSave(statesNew);
                    }

                } else if (bMultiEdit)
                    GenericObjectCollectController.this.changeStatesForMultipleObjects(stateFinal, statesNew);
                else
                    GenericObjectCollectController.this.changeStatesForSingleObjectAndSave(statesNew);
            }
        }

        UIUtils.runShortCommand(getTab(), new ChangeStateWorker2());
    }

    private void changeStateForSingleObjectAndSave(final StateWrapper stateNew) {
        class ChangeStateWorker3 extends CommonClientWorkerAdapter<CollectableGenericObjectWithDependants> {

            Integer iGenericObjectId;
            Integer iModuleId;
            StateWrapper stateCurrent;
            CollectableGenericObjectWithDependants clct;

            boolean errorOccurred = false;

            private ChangeStateWorker3(CollectController<CollectableGenericObjectWithDependants> clt) {
                super(clt);
            }

            @Override
            public void init() throws CommonBusinessException {
                super.init();
                iGenericObjectId = GenericObjectCollectController.this.getSelectedGenericObjectId();
                iModuleId = getSelectedCollectableModuleId();
                assert iGenericObjectId != null;

                stateCurrent = getStateWrapperFromSelectedGenericObject();
                clct = GenericObjectCollectController.this.getCompleteSelectedCollectable();
            }

            @Override
            public void work() throws CommonBusinessException {
                try {
                    isProcessingStateChange.set(true);
                    if (GenericObjectCollectController.this.changesArePending()) {
                        // NUCLOSINT-1114:
                        // Value must be 'true' to save the changed SubForm data to DB. (Thomas Pasch)
                        // tsc: the implementation with dbUpdate-parameter skipped a lot of data collection logic, which was the reason for the NUCLOSINT-1114.
                        //      Final servercall for database update is now skipped by setting a ThreadLocal variable.
                        //      TODO Best solution would be to refactor and call all data collection logic in prepareCollectableForSaving(), but this would take some time.
                        final CollectableGenericObjectWithDependants updated = GenericObjectCollectController.this
                                .updateCurrentCollectable();
                        invoke(new CommonRunnable() {
                            @Override
                            public void run() throws CommonBusinessException {
                                StateDelegate.getInstance().changeStateAndModify(iModuleId,
                                        updated.getGenericObjectWithDependantsCVO(), stateNew.getId(),
                                        getCustomUsage());
                            }
                        });
                    } else {
                        invoke(new CommonRunnable() {
                            @Override
                            public void run() throws CommonBusinessException {
                                StateDelegate.getInstance().changeState(iModuleId, iGenericObjectId,
                                        stateNew.getId(), getCustomUsage());
                            }
                        });
                    }
                    broadcastCollectableEvent(clct, MessageType.STATECHANGE_DONE);

                    // We have to reload the current leased object, as some fields might have changed:
                    // . nuclosState because of the status change
                    // . other fields because of business rules
                    if (!errorOccurred)
                        GenericObjectCollectController.this.refreshCurrentCollectable(false);
                } finally {
                    isProcessingStateChange.set(false);
                }
            }

            @Override
            public void paint() throws CommonBusinessException {
                super.paint();
            }

            @Override
            public void handleError(Exception ex) {
                errorOccurred = true;
                if (!handleCommonValidationException(ex)) {
                    if (GenericObjectCollectController.this.handlePointerException(ex)) {
                        final PointerException pex = PointerException.extractPointerExceptionIfAny(ex);
                        if (pex != null) {
                            GenericObjectCollectController.this
                                    .setCollectableComponentModelsInDetailsMandatoryAdded(
                                            pex.getPointerCollection().getFields());
                        }
                    } else if (!(ex instanceof UserCancelledException)) {
                        if (!handleSpecialException(ex)) {
                            final String sErrorMsg = getSpringLocaleDelegate().getMessage(
                                    "GenericObjectCollectController.34",
                                    "Der Statuswechsel konnte nicht vollzogen werden.");
                            Errors.getInstance().showExceptionDialog(getTab(), sErrorMsg, ex);
                        }
                    }
                }

                // redisplay the old status
                cmbbxCurrentState.setSelectedItem(stateCurrent);
                cmpStateStandardView.setSelectedItem(stateCurrent);
            }
        }

        CommonMultiThreader.getInstance().execute(new ChangeStateWorker3(GenericObjectCollectController.this));
    }

    private void changeStatesForSingleObjectAndSave(final List<Integer> statesNew) {
        class ChangeStatesWorker4 extends CommonClientWorkerAdapter<CollectableGenericObjectWithDependants> {

            Integer iGenericObjectId;
            Integer iModuleId;
            StateWrapper stateCurrent;
            CollectableGenericObjectWithDependants clct;

            boolean errorOccurred = false;

            private ChangeStatesWorker4(CollectController<CollectableGenericObjectWithDependants> clt) {
                super(clt);
            }

            @Override
            public void init() throws CommonBusinessException {
                super.init();
                iGenericObjectId = GenericObjectCollectController.this.getSelectedGenericObjectId();
                iModuleId = getSelectedCollectableModuleId();
                assert iGenericObjectId != null;

                stateCurrent = getStateWrapperFromSelectedGenericObject();
                clct = GenericObjectCollectController.this.getCompleteSelectedCollectable();
            }

            @Override
            public void work() throws CommonBusinessException {
                try {
                    isProcessingStateChange.set(true);
                    for (final Integer stateNew : statesNew) {
                        if (GenericObjectCollectController.this.changesArePending()) {
                            // NUCLOSINT-1114:
                            // Value must be 'true' to save the changed SubForm data to DB. (Thomas Pasch)
                            // tsc: the implementation with dbUpdate-parameter skipped a lot of data collection logic, which was the reason for the NUCLOSINT-1114.
                            //      Final servercall for database update is now skipped by setting a ThreadLocal variable.
                            //      TODO Best solution would be to refactor and call all data collection logic in prepareCollectableForSaving(), but this would take some time.
                            final CollectableGenericObjectWithDependants updated = GenericObjectCollectController.this
                                    .updateCurrentCollectable();
                            invoke(new CommonRunnable() {
                                @Override
                                public void run() throws CommonBusinessException {
                                    StateDelegate.getInstance().changeStateAndModify(iModuleId,
                                            updated.getGenericObjectWithDependantsCVO(), stateNew,
                                            getCustomUsage());
                                }
                            });
                        } else {
                            invoke(new CommonRunnable() {
                                @Override
                                public void run() throws CommonBusinessException {
                                    StateDelegate.getInstance().changeState(iModuleId, iGenericObjectId, stateNew,
                                            getCustomUsage());
                                }
                            });
                        }
                    }
                    broadcastCollectableEvent(clct, MessageType.STATECHANGE_DONE);

                    // We have to reload the current leased object, as some fields might have changed:
                    // . nuclosState because of the status change
                    // . other fields because of business rules
                    if (!errorOccurred) {
                        if (getCollectState().isResultMode()) {
                            getResultController().getSearchResultStrategy().refreshResult();
                            setCollectState(CollectState.OUTERSTATE_RESULT, CollectState.RESULTMODE_NOSELECTION);
                        } else {
                            GenericObjectCollectController.this.refreshCurrentCollectable(false);
                        }
                    }
                } finally {
                    isProcessingStateChange.set(false);
                }
            }

            @Override
            public void paint() throws CommonBusinessException {
                super.paint();
            }

            @Override
            public void handleError(Exception ex) {
                errorOccurred = true;
                if (!handleCommonValidationException(ex)) {
                    if (GenericObjectCollectController.this.handlePointerException(ex)) {
                        final PointerException pex = PointerException.extractPointerExceptionIfAny(ex);
                        if (pex != null) {
                            GenericObjectCollectController.this
                                    .setCollectableComponentModelsInDetailsMandatoryAdded(
                                            pex.getPointerCollection().getFields());
                        }
                    } else if (!(ex instanceof UserCancelledException)) {
                        if (!handleSpecialException(ex)) {
                            final String sErrorMsg = getSpringLocaleDelegate().getMessage(
                                    "GenericObjectCollectController.34",
                                    "Der Statuswechsel konnte nicht vollzogen werden.");
                            Errors.getInstance().showExceptionDialog(getTab(), sErrorMsg, ex);
                        }
                    }
                }
                // redisplay the old status
                cmbbxCurrentState.setSelectedItem(stateCurrent);
                cmpStateStandardView.setSelectedItem(stateCurrent);
            }
        }

        CommonMultiThreader.getInstance().execute(new ChangeStatesWorker4(GenericObjectCollectController.this));
    }

    @Override
    public void enableToolbarButtonsForDetailsMode(final int iDetailsMode) {
        super.enableToolbarButtonsForDetailsMode(iDetailsMode);
        if (iDetailsMode == CollectState.DETAILSMODE_VIEW) {
            setInitialComponentFocusInDetailsTab(); // @see NUCLOS-1027
        }
    }

    private void changeStateForMultipleObjects(final StateWrapper stateNew) throws CommonBusinessException {
        new ChangeStateForSelectedCollectablesController(this, stateNew,
                new LinkedList<Integer>(Collections.singleton(stateNew.getId())))
                        .run(getMultiActionProgressPanel(getSelectedCollectables().size()));
    }

    private void changeStatesForMultipleObjects(final StateWrapper stateFinal, final List<Integer> statesNew)
            throws CommonBusinessException {
        new ChangeStateForSelectedCollectablesController(this, stateFinal, statesNew)
                .run(getMultiActionProgressPanel(getSelectedCollectables().size()));
    }

    protected boolean showObjectGenerationWarningIfNewObjectIsNotSaveable() {
        return true;
    }

    @Override
    public void save() throws CommonBusinessException {
        super.save();
        if (bGenerated && iGenericObjectIdSources != null && oGeneratorAction != null) {
            if (getSelectedGenericObjectId() != null && oGeneratorAction.isCreateRelationBetweenObjects()) { // could be null if save is not possible (e.g. mandatory fields)
                final EntityMetaDataVO sourceMeta = MetaDataClientProvider.getInstance()
                        .getEntity(IdUtils.toLongId(oGeneratorAction.getSourceModuleId()));
                if (sourceMeta.isStateModel()) {
                    for (Long iGenericObjectIdSource : iGenericObjectIdSources) {
                        lodelegate.relate(IdUtils.unsafeToId(iGenericObjectIdSource),
                                GenericObjectTreeNode.SystemRelationType.PREDECESSOR_OF.getValue(),
                                getSelectedGenericObjectId(), getModuleId(), null, null, null);
                    }
                }
                bGenerated = false;
                setGenerationSource(null, null);
            }
        }
    }

    /**
     * resets the temporary stored data which was hold while switching the layout
     */
    private void resetTransferedDetailsData() {
        transferredDetailsData.clear();
    }

    /**
     * prints the current leased object
     * leased object will be refreshed, when printed documents are attached to it
     */
    private void cmdPrintCurrentGenericObject() {
        try {
            final ReportController reportController = new ReportController(getTab());
            final List<? extends CollectableGenericObject> lstclctlo = getSelectedCollectables();
            reportController.exportForm(lstclctlo, getGreatestCommonUsageCriteriaFromCollectables(lstclctlo),
                    getDocumentSubformEntityName(), getDocumentSubformColumns());
        } catch (Exception ex) {
            Errors.getInstance().showExceptionDialog(getTab(), ex);
        }
    }

    /**
     * get the subform entity name which is used to attache documents to the generic object
     * @return entity name
     */
    protected String getDocumentSubformEntityName() {
        final String sEntity = this.getEntityName() + "document";

        if (MasterDataDelegate.getInstance().hasEntity(sEntity))
            return sEntity;

        return NuclosEntity.GENERALSEARCHDOCUMENT.getEntityName();
    }

    /**
     *
     * @return the 4 column names of the document subform (comment, createdDate, createdUser, filename).
     * used for exporting a form.
     */
    protected String[] getDocumentSubformColumns() {
        return new String[] { "comment", "createdDate", "createdUser", "filename" };
    }

    /**
     * get if the given generic object has assigned forms
     * @param clct
     */
    private boolean hasFormsAssigned(CollectableGenericObjectWithDependants clct) {
        final ReportController reportController = new ReportController(getTab());
        return reportController.hasFormsAssigned(getUsageCriteria(clct));
    }

    /**
     * @param bSearchPanel Use SearchPanel? (false: use Details panel)
     * @return the value ids of the quintuple fields from the view (Search or Details panel)
     * @throws CollectableFieldFormatException
     */
    private UsageCriteria getUsageCriteriaFromView(boolean bSearchPanel) throws CollectableFieldFormatException {
        final Integer iModuleId = bSearchPanel ? getModuleId() : getSelectedCollectableModuleId();
        return new UsageCriteria(iModuleId, getUsageCriteriaProcessIdFromView(bSearchPanel),
                getUsageCriteriaStatusIdFromView(bSearchPanel), getCustomUsage());
    }

    /**
     * @param bSearchPanel Use SearchPanel? (false: use Details panel)
     * @param sSystemAttributeKey key of the quintuple field name in system parameters
     * @return the value id of the given quintuple field
     * @throws CollectableFieldFormatException
     */
    private Integer getUsageCriteriaProcessIdFromView(boolean bSearchPanel) throws CollectableFieldFormatException {
        // 1. makeConsistent:
        for (CollectableComponent clctcomp : getEditView(bSearchPanel)
                .getCollectableComponentsFor(NuclosEOField.PROCESS.getMetaData().getField()))
            clctcomp.makeConsistent();

        // 2. read model:
        final CollectableComponentModel clctcompmodel = getEditView(bSearchPanel).getModel()
                .getCollectableComponentModelFor(NuclosEOField.PROCESS.getMetaData().getField());
        return (clctcompmodel == null) ? null : (Integer) clctcompmodel.getField().getValueId();
    }

    /**
     * @param bSearchPanel Use SearchPanel? (false: use Details panel)
     * @param sSystemAttributeKey key of the quintuple field name in system parameters
     * @return the value id of the given quintuple field
     * @throws CollectableFieldFormatException
     */
    private Integer getUsageCriteriaStatusIdFromView(boolean bSearchPanel) throws CollectableFieldFormatException {
        if (bSearchPanel) {
            // 1. makeConsistent:
            for (CollectableComponent clctcomp : getEditView(bSearchPanel)
                    .getCollectableComponentsFor(NuclosEOField.STATE.getMetaData().getField()))
                clctcomp.makeConsistent();

            // 2. read model:
            final CollectableComponentModel clctcompmodel = getEditView(bSearchPanel).getModel()
                    .getCollectableComponentModelFor(NuclosEOField.STATE.getMetaData().getField());
            return (clctcompmodel == null) ? null : (Integer) clctcompmodel.getField().getValueId();
        } else {
            Collectable clct = getSelectedCollectable();
            if (clct == null || getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_NEW) { // it is a new collectable.
                final Integer iModuleId = bSearchPanel ? getModuleId() : getSelectedCollectableModuleId();
                UsageCriteria uc = new UsageCriteria(iModuleId, getUsageCriteriaProcessIdFromView(bSearchPanel),
                        null, getCustomUsage());
                return getInitialStateId(uc);
            }

            return getSystemAttributeId(getSelectedCollectable(), NuclosEOField.STATE.getMetaData().getField());
        }
    }

    /**
     * @param clct
     * @return the UsageCriteria contained in the given Collectable.
     */
    protected UsageCriteria getUsageCriteria(Collectable clct) {
        return new UsageCriteria(((CollectableGenericObject) clct).getGenericObjectCVO().getModuleId(),
                getSystemAttributeId(((CollectableGenericObject) clct),
                        NuclosEOField.PROCESS.getMetaData().getField()),
                getSystemAttributeId(((CollectableGenericObject) clct),
                        NuclosEOField.STATE.getMetaData().getField()),
                getCustomUsage());
    }

    private static Integer getSystemAttributeId(CollectableGenericObject clct, String sFieldName) {
        return clct.getCollectableEntity().getFieldNames().contains(sFieldName)
                ? (Integer) clct.getField(sFieldName).getValueId()
                : null;
    }

    /**
     * @param collclct
     * @return the greatest common quintuple contained in the given Collection
     * @precondition CollectionUtils.isNonEmpty(collclct)
     */
    protected UsageCriteria getGreatestCommonUsageCriteriaFromCollectables(
            Collection<? extends CollectableGenericObject> collclct) {
        class GetUsageCriteria implements Transformer<CollectableGenericObject, UsageCriteria> {
            @Override
            public UsageCriteria transform(CollectableGenericObject clct) {
                return getUsageCriteria(clct);
            }
        }
        return UsageCriteria
                .getGreatestCommonUsageCriteria(CollectionUtils.transform(collclct, new GetUsageCriteria()));
    }

    private static boolean isUsageCriteriaField(String sFieldName) {
        boolean result = false;
        for (String sUsageCriteriaFieldName : getUsageCriteriaFieldNames())
            if (sUsageCriteriaFieldName.equals(sFieldName)) {
                result = true;
                break;
            }
        return result;
    }

    private CollectableComponentModelListener getCollectableComponentModelListenerForUsageCriteriaFields(
            boolean bSearchPanel) {
        return bSearchPanel ? ccmlistenerUsageCriteriaFieldsForSearch : ccmlistenerUsageCriteriaFieldsForDetails;
    }

    private static int getPanelIndex(boolean bSearchPanel) {
        return bSearchPanel ? 0 : 1;
    }

    /**
     * @param bSearchPanel
     * @return Have the quintuple field listeners for the given panel been added already?
     */
    private boolean getUsageCriteriaFieldListenersAdded(boolean bSearchPanel) {
        return abUsageCriteriaFieldListenersAdded[getPanelIndex(bSearchPanel)];
    }

    private void setUsageCriteriaFieldListenersAdded(boolean bSearchPanel, boolean bValue) {
        abUsageCriteriaFieldListenersAdded[getPanelIndex(bSearchPanel)] = bValue;
    }

    /**
     * adds the quintuple field listeners to the given panel.
     * @param bSearchPanel
     * @precondition !this.getUsageCriteriaFieldListenersAdded(bSearchPanel)
     * @postcondition this.getUsageCriteriaFieldListenersAdded(bSearchPanel)
     */
    private void addUsageCriteriaFieldListeners(boolean bSearchPanel) {
        if (getUsageCriteriaFieldListenersAdded(bSearchPanel))
            throw new IllegalStateException();
        this.addUsageCriteriaFieldListeners(getEditView(bSearchPanel).getModel(),
                getCollectableComponentModelListenerForUsageCriteriaFields(bSearchPanel));

        setUsageCriteriaFieldListenersAdded(bSearchPanel, true);

        assert getUsageCriteriaFieldListenersAdded(bSearchPanel);
    }

    /**
     * removes the quintuple field listeners to the given panel, if they have been added before.
     * @param bSearchPanel
     * @postcondition !this.getUsageCriteriaFieldListenersAdded(bSearchPanel)
     */
    private void removeUsageCriteriaFieldListeners(boolean bSearchPanel) {
        if (getUsageCriteriaFieldListenersAdded(bSearchPanel)) {
            this.removeUsageCriteriaFieldListeners(getEditView(bSearchPanel).getModel(),
                    getCollectableComponentModelListenerForUsageCriteriaFields(bSearchPanel));

            setUsageCriteriaFieldListenersAdded(bSearchPanel, false);
        }
        assert !getUsageCriteriaFieldListenersAdded(bSearchPanel);
    }

    private void addUsageCriteriaFieldListeners(CollectableComponentModelProvider clctcompmodelprovider,
            CollectableComponentModelListener clctcomplistener) {
        for (String sUsageCriteriaFieldName : getUsageCriteriaFieldNames()) {
            final CollectableComponentModel clctcompmodel = clctcompmodelprovider
                    .getCollectableComponentModelFor(sUsageCriteriaFieldName);
            if (clctcompmodel != null) {
                LOG.debug("add listener for field " + clctcompmodel.getFieldName());
                // clctcompmodel.addCollectableComponentModelListener(clctcomplistener);
                ListenerUtil.registerCollectableComponentModelListener(clctcompmodel, null, clctcomplistener);
            }
        }
    }

    private void removeUsageCriteriaFieldListeners(CollectableComponentModelProvider clctcompmodelprovider,
            CollectableComponentModelListener clctcomplistener) {
        for (String sUsageCriteriaFieldName : getUsageCriteriaFieldNames()) {
            final CollectableComponentModel clctcompmodel = clctcompmodelprovider
                    .getCollectableComponentModelFor(sUsageCriteriaFieldName);
            if (clctcompmodel != null) {
                LOG.debug("remove listener for field " + clctcompmodel.getFieldName());
                clctcompmodel.removeCollectableComponentModelListener(clctcomplistener);
            }
        }
    }

    protected void setSearchStatesAccordingToUsageCriteria(UsageCriteria usagecriteria) {
        getSearchStateBox().setProperty("usagecriteria", usagecriteria);
    }

    /**
     * @param cond
     * @param bClearSearchFields
     * @throws CommonBusinessException
     *
     * @deprecated Move to SearchController hierarchy and make protected again.
     */
    @Override
    public void setSearchFieldsAccordingToSearchCondition(CollectableSearchCondition cond,
            boolean bClearSearchFields) throws CommonBusinessException {
        final boolean bUsageCriteriaFieldListenersWereAdded = getUsageCriteriaFieldListenersAdded(true);
        this.removeUsageCriteriaFieldListeners(true);

        final boolean bUsageCriteriaChanged;
        if (bReloadingLayout)
            bUsageCriteriaChanged = true;
        else {
            // 1. If we are not reloading the layout already, do that first:
            final UsageCriteria usagecriteriaOld = getUsageCriteriaFromView(true);
            final UsageCriteria usagecriteria = getUsageCriteriaFromSearchCondition(cond);
            bUsageCriteriaChanged = !usagecriteria.equals(usagecriteriaOld);

            if (bUsageCriteriaChanged) {
                // Note that the usagecriteria field listeners are removed here:
                this.reloadLayout(usagecriteria, getCollectState(), false, false);
                setSearchStatesAccordingToUsageCriteria(usagecriteria);
            }
        }

        assert !getUsageCriteriaFieldListenersAdded(true);

        try {
            clearSearchCondition();
            // 2. fill in fields, ignoring changes in the quintuple fields as the right layout is loaded already:
            super.setSearchFieldsAccordingToSearchCondition(cond, bClearSearchFields && !bUsageCriteriaChanged);
        } finally {
            if (bUsageCriteriaFieldListenersWereAdded)
                this.addUsageCriteriaFieldListeners(true);
        }
    }

    @Override
    protected void _setSearchFieldsAccordingToSubCondition(CollectableSubCondition cond)
            throws CommonBusinessException {
        final String sEntityNameSub = cond.getSubEntityName();
        final SearchConditionSubFormController subformctl = mpsubformctlSearch.get(sEntityNameSub);
        if (subformctl == null)
            throw new NuclosFatalException(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.40",
                    "Ein Unterformular f\u00fcr die Entit\u00e4t {0} ist in der Suchbedingung, aber nicht im aktuellen Layout enthalten.",
                    sEntityNameSub));
        subformctl.setCollectableSearchCondition(cond.getSubCondition());
    }

    @Override
    protected void _clearSearchFields() {
        final boolean bUsageCriteriaFieldListenersWereAdded = getUsageCriteriaFieldListenersAdded(true);
        this.removeUsageCriteriaFieldListeners(true);

        CollectableComponentModel clctcompmodelUsageCriteriaFieldChanged = null;
        // remember if field have to change
        for (String sUsageCriteriaFieldName : getUsageCriteriaFieldNames()) {
            final CollectableComponentModel clctcompmodel = getEditView(true).getModel()
                    .getCollectableComponentModelFor(sUsageCriteriaFieldName);
            if (clctcompmodel != null) {
                if (!clctcompmodel.getField().isNull()) {
                    clctcompmodelUsageCriteriaFieldChanged = clctcompmodel;
                    break;
                }
            }
        }
        CollectableField oldUsageCriteriaField = clctcompmodelUsageCriteriaFieldChanged == null ? null
                : clctcompmodelUsageCriteriaFieldChanged.getField();

        try {
            super._clearSearchFields();
        } finally {
            if (bUsageCriteriaFieldListenersWereAdded)
                this.addUsageCriteriaFieldListeners(true);
        }

        for (SearchConditionSubFormController subformctl : getSubFormControllersInSearch())
            subformctl.clear();

        getSearchStateBox().getJComboBox().setSelectedIndex(-1);

        // invoke.
        if (clctcompmodelUsageCriteriaFieldChanged != null) {
            getCollectableComponentModelListenerForUsageCriteriaFields(true).collectableFieldChangedInModel(
                    new CollectableComponentModelEvent(clctcompmodelUsageCriteriaFieldChanged,
                            oldUsageCriteriaField, clctcompmodelUsageCriteriaFieldChanged.getField()));
        }
    }

    /**
     * @param cond
     * @return the quintuple contained in the atomic fields of the given condition.
     */
    private UsageCriteria getUsageCriteriaFromSearchCondition(CollectableSearchCondition cond) {
        return getUsageCriteriaFromFieldsMap(SearchConditionUtils.getAtomicFieldsMap(cond));
    }

    private UsageCriteria getUsageCriteriaFromFieldsMap(Map<String, CollectableField> mpFields) {
        return new UsageCriteria(getModuleId(), getProcessIdFromUsageCriteriaField(mpFields),
                getStatusIdFromUsageCriteriaField(mpFields), getCustomUsage());
    }

    private Integer getProcessIdFromUsageCriteriaField(Map<String, CollectableField> mpFields) {
        final CollectableField clctfField = mpFields.get(NuclosEOField.PROCESS.getMetaData().getField());
        if (clctfField != null && !clctfField.isIdField())
            return null;
        return (clctfField == null) ? null : (Integer) clctfField.getValueId();
    }

    private Integer getStatusIdFromUsageCriteriaField(Map<String, CollectableField> mpFields) {
        final CollectableField clctfField = mpFields.get(NuclosEOField.STATE.getMetaData().getField());
        if (clctfField != null && !clctfField.isIdField())
            return null;
        return (clctfField == null) ? null : (Integer) clctfField.getValueId();
    }

    /**
     * invokes dialog for export of result table list.
     * If no lines are selected, just exports the result list.
     * If one or more lines are selected, asks whether to export result list or to print appropriate reports for selection
     * @precondition getCollectStateModel().getOuterState() == CollectState.OUTERSTATE_RESULT
     */
    private void cmdPrint() {
        assert getCollectStateModel().getOuterState() == CollectState.OUTERSTATE_RESULT;

        final List<CollectableGenericObjectWithDependants> lstclctlo = getSelectedCollectables();
        final UsageCriteria usagecriteria = (lstclctlo.isEmpty() ? null
                : getGreatestCommonUsageCriteriaFromCollectables(lstclctlo));
        final String sDocumentEntityName = this.getEntityName() + "document";
        final String[] documentFieldNames = this.getDocumentSubformColumns();

        UIUtils.runCommand(getTab(), new CommonRunnable() {
            @Override
            public void run() throws CommonBusinessException {
                final ISearchStrategy<CollectableGenericObjectWithDependants> ss = getSearchStrategy();
                final boolean bIncludeSubModules = ss.getIncludeSubModulesForSearch();
                new ReportController(getTab()).export(getCollectableEntity(), ss.getInternalSearchExpression(),
                        getSelectedFields(), lstclctlo, usagecriteria, bIncludeSubModules, sDocumentEntityName,
                        documentFieldNames, getCustomUsage());
            }
        });
    }

    private static class JTabbedPaneChangeListener implements ChangeListener {

        private final Preferences preferences;

        private JTabbedPaneChangeListener(Preferences preferences) {
            this.preferences = preferences;
        }

        @Override
        public void stateChanged(ChangeEvent e) {
            JTabbedPane pane = (JTabbedPane) e.getSource();
            int idx = pane.getSelectedIndex();
            if (idx != -1) {
                String sTitle = pane.getTitleAt(idx);
                preferences.put(GenericObjectCollectController.TABSELECTED, sTitle);
            }
        }
    }

    private void addTabbedPaneListener(LayoutRoot root) {
        List<JTabbedPane> lstTabs = new ArrayList<JTabbedPane>();
        searchTabbedPanes(root.getRootComponent(), lstTabs);
        for (JTabbedPane tabPane : lstTabs) {
            tabPane.addChangeListener(
                    new JTabbedPaneChangeListener(GenericObjectCollectController.this.getPreferences()));
        }
    }

    private void cmdShowResultsInExplorer() {
        UIUtils.runCommand(getTab(), new CommonRunnable() {
            @Override
            public void run() throws CommonBusinessException {
                final ISearchStrategy<CollectableGenericObjectWithDependants> ss = getSearchStrategy();
                String sFilterName = getEntityLabel() + " " + Integer.toString(++iFilter);
                if (getSearchFilterComboBox().getSelectedIndex() != 0) {
                    sFilterName = sFilterName + ": " + getSearchFilterComboBox().getSelectedItem().toString();
                }
                getExplorerController().showInOwnTab(new EntitySearchResultTreeNode(
                        getSpringLocaleDelegate().getMessage("GenericObjectCollectController.93",
                                "Suchergebnis ({0})", sFilterName),
                        null, ss.getInternalSearchExpression(), sFilterName, getMainController().getUserName(),
                        getEntityName()));
            }
        });
    }

    @Override
    public void makeConsistent(boolean bSearchTab) throws CollectableFieldFormatException {
        super.makeConsistent(bSearchTab);

        if (!stopEditing(bSearchTab))
            /** @todo we need to give a better error message here. */
            throw new CollectableFieldFormatException(getSpringLocaleDelegate()
                    .getMessage("GenericObjectCollectController.95", "Ung\u00fcltige Eingabe in Unterformular."));
    }

    /**
     * transfers the values/states of the given old collectable component models to the given new collectable component models.
     */
    private void transferCollectableComponentModelsInDetailsPanel(
            Map<String, DetailsComponentModel> mpclctcompmodelOld,
            Map<String, DetailsComponentModel> mpclctcompmodelNew) {

        transferredDetailsData.putAll(mpclctcompmodelOld);
        for (DetailsComponentModel clctcompmodelNew : mpclctcompmodelNew.values()) {
            final String sFieldName = clctcompmodelNew.getFieldName();

            DetailsComponentModel clctcompmodelOld = transferredDetailsData.get(sFieldName);
            if (clctcompmodelOld != null)
                // 1. Transfer all fields from the old models to the new models
                clctcompmodelNew.assign(clctcompmodelOld);
            else {
                // 2. Set all additional fields (those contained in the new models, but not in the old models) to their default values:
                final CollectableEntityField clctefNew = clctcompmodelNew.getEntityField();
                final CollectableField clctfDefault = clctefNew.getDefault();
                if (!clctfDefault.isNull()) {
                    final boolean bFieldExistsInOldPanel = mpclctcompmodelOld
                            .containsKey(clctcompmodelNew.getFieldName());
                    if (bFieldExistsInOldPanel)
                        LOG.debug(
                                "Skipping field " + clctefNew.getName() + " as it is contained in the old panel.");
                    else {
                        LOG.debug(
                                "Setting field " + clctefNew.getName() + " to default value " + clctfDefault + ".");
                        clctcompmodelNew.setField(clctfDefault);
                    }
                }
            }
        }
    }

    private void transferSubFormData(
            Map<String, DetailsSubFormController<CollectableEntityObject>> mpOldSubFormControllers,
            Map<String, DetailsSubFormController<CollectableEntityObject>> mpNewSubFormControllers) {
        for (String sEntityName : mpOldSubFormControllers.keySet())
            if (!mpNewSubFormControllers.containsKey(sEntityName))
                LOG.warn("Unterformular f\u00c3\u00bcr Entit\u00c3\u00a4t " + sEntityName
                        + " ist in der neuen Maske nicht enthalten.");
            else
                DetailsSubFormController.copyModel(mpOldSubFormControllers.get(sEntityName),
                        mpNewSubFormControllers.get(sEntityName));
    }

    private void reloadLayoutForDetailsTab(final boolean bAddUsageCriteriaFieldListeners)
            throws CommonBusinessException {
        LOG.debug("GenericObjectCollectController.reloadLayoutForDetailsTab");
        this.reloadLayout(getCollectStateModel().getCollectState(), bAddUsageCriteriaFieldListeners);
    }

    private void reloadLayoutForSearchTab() throws CommonBusinessException {
        LOG.debug("BEGIN reloadLayoutForSearchTab");
        this.reloadLayout(getCollectStateModel().getCollectState(), getUsageCriteriaFieldListenersAdded(true));
        setSearchStatesAccordingToUsageCriteria(getUsageCriteriaFromView(true));
        LOG.debug("FINISHED reloadLayoutForSearchTab");
    }

    private void reloadLayout(CollectState collectstate, boolean bAddUsageCriteriaFieldListeners)
            throws CommonBusinessException {
        final boolean bSearchPanel = collectstate.isSearchMode();
        final UsageCriteria usagecriteria = getUsageCriteriaFromView(bSearchPanel);

        this.reloadLayout(usagecriteria, collectstate, true, bAddUsageCriteriaFieldListeners);
    }

    private Integer iCurrentLayoutId = null;

    private void reloadLayout(final UsageCriteria usagecriteria, final CollectState collectstate,
            final boolean bTransferContents, final boolean bAddUsageCriteriaFieldListeners)
            throws CommonBusinessException {
        /** @todo don't do this unless the layout really changed! */
        if (bReloadingLayout)
            throw new IllegalStateException("reloadLayout must not be called recursively!");

        final boolean bSearchPanel = collectstate.isSearchMode();
        try {
            bReloadingLayout = true;

            final boolean bSearchMode = collectstate.isSearchMode();
            /** @todo maybe factor this out in a protected method and override in GeneralSearchCollectController */
            final org.nuclos.common.collect.collectable.CollectableEntity clcte = bSearchMode
                    ? getCollectableEntity()
                    : CollectableGenericObjectEntity.getByModuleId(usagecriteria.getModuleId());
            final Integer iLayout = GenericObjectLayoutCache.getInstance().getLayoutId(clcte, usagecriteria,
                    bSearchMode);
            if (LangUtils.equals(iCurrentLayoutId, iLayout)) {
                getLayoutMLButtonsActionListener().clearInputMapForParentPanel(getCollectPanel());

                setupShortcutsForTabs(getTab());

                if (collectstate.isDetailsMode()) {
                    getLayoutMLButtonsActionListener().setInputMapForParentPanel(getCollectPanel(),
                            getCollectPanel().getDetailsPanel().getEditComponent());
                }

                return;
            }

            iCurrentLayoutId = iLayout;

            final LayoutRoot layoutroot = getLayoutFromCache(usagecriteria, collectstate);

            addTabbedPaneListener(layoutroot);

            this.removeUsageCriteriaFieldListeners(bSearchPanel);

            try {
                if (bSearchPanel)
                    transferSearchPanel(layoutroot, bTransferContents);
                else {
                    final JComponent compEditOld = getDetailsPanel().getEditComponent();
                    if (bTransferContents)
                        transferDetailsPanel(layoutroot, compEditOld);
                    else
                        transferDetailsPanel(layoutroot, bTransferContents ? compEditOld : null);
                }

                final Collection<SubForm> collsubform = new HashSet<SubForm>();
                for (SubFormController subformctl : getSubFormControllersInDetails())
                    collsubform.add(subformctl.getSubForm());

                respectRights(getDetailsPanel().getEditView().getCollectableComponents(), collsubform,
                        usagecriteria, collectstate);
                respectRights(getDetailsPanel().getEditView().getCollectableLabels(), collsubform, usagecriteria,
                        collectstate);

                // ensure the (possibly new) edit panel is shown completely:
                UIUtils.ensureMinimumSize(getTab());

                // always revalidate:
                getTab().revalidate();
            } finally {
                if (bAddUsageCriteriaFieldListeners)
                    GenericObjectCollectController.this.addUsageCriteriaFieldListeners(bSearchPanel);
            }
        } finally {
            bReloadingLayout = false;
            if (!bSearchPanel) {
                if (getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_NEW
                        || getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_NEW_CHANGED
                        || getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_NEW_SEARCHVALUE) {

                    resetCollectableComponentModelsInDetailsMandatory();
                    resetCollectableComponentModelsInDetailsMandatoryAdded();
                    super.highlightMandatory();
                    highlightMandatoryByState(getInitialStateId(usagecriteria));
                }
            }

            getLayoutMLButtonsActionListener().fireComponentEnabledStateUpdate(
                    getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_EDIT);
        }
    }

    /**
     * transfers the contents of the Details panel after reloading the layout.
     * @param layoutroot
     * @param compEditOld the old Edit panel for the Details tab (or null)
     * @param compEditNew the new Edit panel for the Details tab, as in getEditPanel(..., false)
     */
    private void transferDetailsPanel(final LayoutRoot layoutroot, JComponent compEditOld) {
        // transfer field contents from the old components to the new ones:
        final boolean bChangeListenersWereAdded = changeListenersForDetailsAdded();

        if (bChangeListenersWereAdded)
            removeChangeListenersForDetails();

        try {
            final Map<String, DetailsComponentModel> mpclctcompNew = getMapOfDetailsComponentModels(layoutroot);

            // 0. set multi edit mode for CollectableComponentModels:
            final boolean bMultiEditMode = CollectState
                    .isDetailsModeMultiViewOrEdit(getCollectStateModel().getDetailsMode());
            if (bMultiEditMode)
                for (DetailsComponentModel clctcompmodel : mpclctcompNew.values())
                    clctcompmodel.setMultiEditable(true);

            // 1. transfer data in collectable components:
            if (compEditOld != null) {
                final Map<String, DetailsComponentModel> mpclctcompOld = CollectionUtils.typecheck(
                        layoutrootDetails.getMapOfCollectableComponentModels(), DetailsComponentModel.class);
                transferCollectableComponentModelsInDetailsPanel(mpclctcompOld, mpclctcompNew);
            }

            final JComponent compEditNew = getDetailsPanel().newEditComponent(layoutroot.getRootComponent());
            JPanel pnl = new JPanel(new BorderLayout());
            pnl.add(cmpStateStandardView, BorderLayout.NORTH);
            pnl.add(compEditNew, BorderLayout.CENTER);
            getDetailsPanel().setEditView(DefaultEditView.newDetailsEditView(pnl, layoutroot,
                    layoutroot.getInitialFocusEntityAndFieldName()));

            // layoutrootDetails is used for the ordered field names:
            layoutrootDetails = layoutroot;

            final Collection<DetailsSubFormController<CollectableEntityObject>> oldDetailsControllers = getSubFormControllersInDetails();

            // 2. transfer data in subforms:
            Map<String, SubForm> mpSubForm = layoutroot.getMapOfSubForms();
            final Map<String, DetailsSubFormController<CollectableEntityObject>> mpNewSubFormControllers = newDetailsSubFormControllers(
                    mpSubForm);

            if (compEditOld != null)
                transferSubFormData(getMapOfSubFormControllersInDetails(), mpNewSubFormControllers);
            setMapOfSubFormControllersInDetails(mpNewSubFormControllers);

            // Note that the old subform controllers must be closed before creating the new ones, so that the
            // column order and widths are preserved (first stored and then read):
            closeSubFormControllers(oldDetailsControllers);

            // 3. setup subform controllers
            setupSubFormController(mpSubForm, mpNewSubFormControllers);

            // 4. transfer custom data:
            if (compEditOld != null)
                transferCustomDataInDetailsPanel(layoutroot, compEditOld, compEditNew);
        } finally {
            if (bChangeListenersWereAdded)
                addChangeListenersForDetails();
        }
    }

    /**
     * closes all subform controllers in the Search panel.
     */
    private void closeSubFormControllersInSearch() {
        closeSubFormControllers(getSubFormControllersInSearch());
    }

    /**
     * @param collsubformctl
     * @precondition collsubformctl != null
     */
    private static void closeSubFormControllers(Collection<? extends SubFormController> collsubformctl) {
        for (SubFormController subformctl : collsubformctl) {
            subformctl.close();
        }
        collsubformctl.clear();
    }

    /**
     * Successors may transfer custom data from the old edit panel to the new edit panel after reloading the layout
     * of the Details panel.
     * The default implementation does nothing (no custom data).
     * @param layoutroot
     * @param compEditOld the old Edit panel for the Details tab
     * @param compEditNew the new Edit panel for the Details tab, as in getEditPanel(..., false)
     * @deprecated seems to be unused.
     */
    @Deprecated
    protected void transferCustomDataInDetailsPanel(LayoutRoot layoutroot, JComponent compEditOld,
            JComponent compEditNew) {
        // Default: no custom data
    }

    /**
     * transfers the (contents of the) Search panel after reloading the layout.
     * @param layoutroot
     * @param bTransferContents transfer contents also?
     * @throws CommonBusinessException
     * @precondition this.isSearchPanelVisible()
     */
    private void transferSearchPanel(final LayoutRoot layoutroot, boolean bTransferContents)
            throws CommonBusinessException {
        if (!isSearchPanelAvailable())
            throw new IllegalStateException("!this.isSearchPanelAvailable()");

        removeChangeListenersForSearch();

        try {
            final CollectableSearchCondition searchcond = bTransferContents
                    ? getCollectableSearchConditionFromSearchPanel(true)
                    : null;

            // Note that the old subform controllers must be closed before creating the new ones, so that the
            // column order and widths are preserved (first stored and then read):
            closeSubFormControllersInSearch();

            getSearchPanel().setEditView(newSearchEditView(layoutroot));

            mpsubformctlSearch = newSearchConditionSubFormControllers(layoutroot.getMapOfSubForms());

            if (bTransferContents)
                // try to transfer search condition:
                try {
                    assert isSearchPanelAvailable();
                    setCollectableSearchConditionInSearchPanel(searchcond);

                    if (LOG.isEnabledFor(Level.WARN)) {
                        final CollectableSearchCondition searchcondNew = getCollectableSearchConditionFromSearchPanel(
                                false);
                        if (!LangUtils.equals(searchcondNew, searchcond))
                            LOG.warn("Die Suchbedingung wurde nicht korrekt \u00fcbertragen. Alte Bedingung: "
                                    + searchcond + " - Neue Bedingung: " + searchcondNew);
                        /** @todo Is warning sufficient here? */
                    }
                } catch (CommonBusinessException ex) {
                    throw new NuclosBusinessException("Die Suchbedingung konnte nicht \u00fcbertragen werden.", ex);
                }
            getSearchPanel().getEditView().setComponentsEnabled(true);
        } finally {
            addChangeListenersForSearch();
            cmdDisplayCurrentSearchConditionInSearchPanelStatusBar();
        }
    }

    protected final LayoutRoot getLayoutFromCache(UsageCriteria usagecriteria, CollectState collectstate) {
        final boolean bSearchMode = collectstate.isSearchMode();

        resetLayoutMLButtonsActionListener();

        /** @todo maybe factor this out in a protected method and override in GeneralSearchCollectController */
        final org.nuclos.common.collect.collectable.CollectableEntity clcte = bSearchMode ? getCollectableEntity()
                : CollectableGenericObjectEntity.getByModuleId(usagecriteria.getModuleId());
        final LayoutRoot result = GenericObjectLayoutCache.getInstance().getLayout(clcte, usagecriteria,
                bSearchMode, getLayoutMLButtonsActionListener(), valueListProviderCache);

        if (bSearchMode) {
            for (CollectableComponent comp : result.getCollectableComponents()) {
                comp.getControlComponent().addFocusListener(collectableComponentSearchFocusListener);
            }
        }

        result.getRootComponent().setFocusCycleRoot(true);
        result.getRootComponent().setFocusTraversalPolicyProvider(true);
        result.getRootComponent().setFocusTraversalPolicy(new NuclosFocusTraversalPolicy(result));

        customizeLayout(result, usagecriteria, collectstate);

        return result;
    }

    /**
     * customizes the given layout, respecting the user rights.
     * @param layoutroot
     */
    protected void customizeLayout(LayoutRoot layoutroot, UsageCriteria usagecriteria, CollectState collectstate) {
        // respect rights in view mode is handled in the GenericObjectCollectStateListener.detailsModeEntered()
        if (collectstate.isSearchMode()) {
            /* uncommented. @see NUCLOSINT-1558
            respectRights(layoutroot.getCollectableComponents(), layoutroot.getMapOfSubForms().values(), usagecriteria, collectstate);
            //NUCLEUSINT-442
            respectRights(layoutroot.getCollectableLabels(), layoutroot.getMapOfSubForms().values(), usagecriteria, collectstate);
                
            // why didnt we use this workaround in other than collectstate.isSearchMode()????
            fixUsageCriteriaSearchFields(layoutroot);
            */
        }
        // iterate through the component tree and clear the keymaps of all JSplitPanes
        UIUtils.clearJComponentKeymap(layoutroot.getRootComponent(), JSplitPane.class);
        getLayoutMLButtonsActionListener().clearInputMapForParentPanel(getCollectPanel());
        getLayoutMLButtonsActionListener().clearInputMapForParentPanel(layoutroot.getRootComponent());

        setupShortcutsForTabs(getTab());

        if (collectstate.isDetailsMode()) {
            getLayoutMLButtonsActionListener().setInputMapForParentPanel(getCollectPanel(),
                    layoutroot.getRootComponent());
        }
    }

    /**
     * workaround: ComboBoxes for quintuple fields must be pure dropdowns otherwise quintuple field listeners don't work.
     * @todo This is a bug in CollectableComboBox - it fires two "changed" events instead of one
     */
    private void fixUsageCriteriaSearchFields(CollectableComponentsProvider clctcompprovider) {
        for (String sUsageCriteriaFieldName : getUsageCriteriaFieldNames())
            for (CollectableComponent clctcomp : clctcompprovider
                    .getCollectableComponentsFor(sUsageCriteriaFieldName))
                if (clctcomp instanceof CollectableComboBox)
                    clctcomp.setInsertable(false);
    }

    /**
     * adjusts the visibility and "enability" ;) of the fields according to the user rights.
     * @param collclctcomp
     * @param usagecriteria
     * @param collectstate
     */
    protected void respectRights(Collection<CollectableComponent> collclctcomp, Collection<SubForm> collsubform,
            UsageCriteria usagecriteria, CollectState collectstate) {

        synchronized (lockCurrRecReadable) {
            blnCurrentRecordReadable = null;
        }
        synchronized (lockCurrRecWritable) {
            blnCurrentRecordWritable = null;
        }

        final Collection<Integer> collStateIds;
        if (collectstate.isSearchMode() || collectstate.isDetailsModeNew())
            collStateIds = Collections.singletonList(getInitialStateId(usagecriteria));
        else if (collectstate.isDetailsModeViewOrEdit())
            collStateIds = Collections.singletonList(getSelectedGenericObjectStateId());
        else if (collectstate.isDetailsModeMultiViewOrEdit())
            collStateIds = getStateIds(getSelectedCollectables());
        else
            /** @todo this occurs when the user changes a usagecriteria field in the search panel and pushes the Search button
             * "too quickly". Must be deferred to Elisa 1.1 as changing the reloading behaviour
             * (runCommand instead of runCommandLater) is too dangerous now. 7.9.2004 */
            // above comment is an old comment from elisa. we do not have problems here anymore.
            // except switching workspaces with open resultcontroller and a search criteria with a usagecriteria field.
            // switching workspace back will produces that collectstate. but we can ignore it here. all panels are loaded correctly.
            return;//throw new IllegalStateException("collectstate: " + collectstate);

        for (CollectableComponent clctcomp : collclctcomp) {
            final Permission permission = getLeastCommonPermission(clctcomp, collStateIds);

            if (!permission.includesReading())
                clctcomp.setVisible(false);
            if (permission.includesWriting())
                clctcomp.setReadOnly(false);
            else
                clctcomp.setReadOnly(true);
        } // for

        // adjust subforms:
        if (collsubform != null && !collsubform.isEmpty())
            for (SubForm subform : collsubform) {
                Permission permission = getLeastCommonSubformPermission(subform, collStateIds);

                if (!permission.includesReading())
                    if (subform != null && subform.getParent() != null)
                        subform.getParent().remove(subform);

                boolean isEditable = SecurityCache.getInstance().isWriteAllowedForModule(getEntity(),
                        getSelectedGenericObjectId()) && permission.includesWriting()
                        && MetaDataClientProvider.getInstance().getEntity(subform.getEntityName()).isEditable();

                if (isEditable) {
                    subform.setReadOnly(!MetaDataClientProvider.getInstance().getEntity(getEntity()).isEditable());
                } else {
                    subform.setReadOnly(!collectstate.isSearchMode());
                }
            }
    }

    @Override
    protected boolean isNewAllowed() {
        return super.isNewAllowed() && SecurityCache.getInstance().isNewAllowedForModule(getEntity());
    }

    /**
     * @param clctcomp
     * @param collStateIds
     * @return the least common permission for the
     * @postcondition result != null
     */
    private Permission getLeastCommonPermission(CollectableComponent clctcomp, Collection<Integer> collStateIds) {
        if (CollectionUtils.isNullOrEmpty(collStateIds))
            throw new IllegalArgumentException("collStateIds");
        Permission result = Permission.READWRITE;
        for (Iterator<Integer> iter = collStateIds.iterator(); iter.hasNext() && (result != Permission.NONE);) {
            final Integer iStateId = iter.next();
            result = LangUtils.min(result, getPermission(clctcomp, iStateId));
        }
        return result;
    }

    /**
     * @param clctcomp
     * @param stateId
     * @return the permission (read/write) for the given component in the state with the given id.
     * @postcondition result != null
     */
    private Permission getPermission(CollectableComponent component, Integer stateId) {
        // @todo This is not as clean. isCurrentRecordReadable() is called for search/new also.
        final Permission permissionRecord = Permission.getPermissionReadingOverrides(isCurrentRecordReadable(),
                isCurrentRecordWritable());

        logSecurity.debug("getPermission: Erlaubnis f?r den Datensatz: " + permissionRecord);

        final CollectableGenericObjectEntityField clctloef = (CollectableGenericObjectEntityField) component
                .getEntityField();
        final AttributeCVO attrcvo = clctloef.getAttributeCVO();

        final Permission result = LangUtils.min(permissionRecord, attrcvo.getPermission(stateId));

        logSecurity.debug("getPermission: Erlaubnis f?r das Attribut " + attrcvo.getName() + ": " + result);

        return result;
    }

    /**
     * @param subform
     * @param collStateIds
     * @return the least common permission for the subform
     * @postcondition result != null
     */
    protected final Permission getLeastCommonSubformPermission(SubForm subform, Collection<Integer> collStateIds) {
        if (CollectionUtils.isNullOrEmpty(collStateIds))
            throw new IllegalArgumentException("collStateIds");

        Permission result = Permission.READWRITE;

        for (Iterator<Integer> iter = collStateIds.iterator(); iter.hasNext() && (result != Permission.NONE);) {
            final Integer iStateId = iter.next();
            result = LangUtils.min(result, getSubformPermission(subform, iStateId));
        }
        return result;
    }

    /**
     * @param subform
     * @param iStateId
     * @return the permission (read/write) for the given subform in the state
     *          with the given id
     * @postcondition result != null
     */
    private Permission getSubformPermission(SubForm subform, Integer iStateId) {
        Map<Integer, Permission> mpPermissions = SecurityCache.getInstance()
                .getSubFormPermission(subform.getEntityName());

        if (mpPermissions.containsKey(iStateId)) {
            Permission permission = Permission.READWRITE;
            if (isHistoricalView())
                permission = Permission.READONLY;
            return LangUtils.min(permission, mpPermissions.get(iStateId));
        } else
            return Permission.NONE;

    }

    private static Collection<Integer> getStateIds(Collection<? extends CollectableGenericObject> collclct) {
        return CollectionUtils.transform(collclct, new Transformer<CollectableGenericObject, Integer>() {
            @Override
            public Integer transform(CollectableGenericObject clct) {
                return GenericObjectDelegate.getInstance().getStateIdByGenericObject(clct.getId());
                //return getSystemAttributeId(clct, ParameterProvider.KEY_SYSTEMATTRIBUTE_STATUS);
            }
        });
    }

    /**
     * @param usagecriteria
     * @return
     * @precondition usagecriteria.getModuleId() != null
     */
    private Integer getInitialStateId(UsageCriteria usagecriteria) {
        return StateDelegate.getInstance().getStatemodel(usagecriteria).getInitialStateId();
    }

    /**
     * @todo move to CollectController after renaming stopEditing() to stopEditingInDetails()
     * @param bSearchTab
     * @return Has the editing been stopped?
     */
    protected boolean stopEditing(boolean bSearchTab) {
        return bSearchTab ? stopEditingInSearch() : stopEditingInDetails();
    }

    @Override
    protected boolean stopEditingInDetails() {
        boolean result = super.stopEditingInDetails();
        if (result)
            for (SubFormController subformctl : getSubFormControllersInDetails())
                result = result && subformctl.stopEditing();
        return result;
    }

    /**
     * TODO: Make this protected again.
     */
    @Override
    public boolean stopEditingInSearch() {
        boolean result = super.stopEditingInSearch();
        if (result)
            for (SubFormController subformctl : getSubFormControllersInSearch())
                result = result && subformctl.stopEditing();
        return result;
    }

    @Override
    protected String getTitle(int iTab, int iMode) {
        final SpringLocaleDelegate localeDelegate = getSpringLocaleDelegate();
        final String[] asTabs = { localeDelegate.getMessage("GenericObjectCollectController.92", "Suche"),
                localeDelegate.getMessage("GenericObjectCollectController.42", "Ergebnis"),
                localeDelegate.getMessage("GenericObjectCollectController.35", "Details") };
        final String[] asDetailsMode = {
                localeDelegate.getMessage("GenericObjectCollectController.94", "Undefiniert"),
                localeDelegate.getMessage("GenericObjectCollectController.36", "Details"),
                localeDelegate.getMessage("GenericObjectCollectController.14", "Bearbeiten"),
                localeDelegate.getMessage("GenericObjectCollectController.55", "Neueingabe"),
                localeDelegate.getMessage("GenericObjectCollectController.57", "Neueingabe (Ge\u00e4ndert)"),
                localeDelegate.getMessage("GenericObjectCollectController.63", "Sammelbearbeitung"),
                localeDelegate.getMessage("GenericObjectCollectController.64", "Sammelbearbeitung (Ge\u00e4ndert)"),
                localeDelegate.getMessage("GenericObjectCollectController.56",
                        "Neueingabe (\u00dcbernahme Suchwerte)") };

        String sPrefix;
        String sSuffix = "";
        final String sMode;

        switch (iTab) {
        case CollectState.OUTERSTATE_DETAILS:
            sPrefix = ""; //getEntityLabel();
            sMode = asDetailsMode[iMode];
            if (CollectState.isDetailsModeViewOrEdit(iMode)) {
                sPrefix += getTreeViewIdentifier(getSelectedCollectable());
            } else if (CollectState.isDetailsModeMultiViewOrEdit(iMode))
                sSuffix = localeDelegate.getMessage("GenericObjectCollectController.97", " von {0} Objekten",
                        getSelectedCollectables().size());
            break;
        default:
            sPrefix = getEntityLabel();
            if (getProcess() != null) {
                sPrefix += " (" + getProcess().getValue() + ")";
            }
            sMode = asTabs[iTab];
        }

        final StringBuilder sb = new StringBuilder(sPrefix + (sPrefix.length() > 0 ? " - " : "") + sMode + sSuffix);

        if (isSelectedCollectableMarkedAsDeleted())
            sb.append(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.2",
                    " (Objekt ist als gel\u00f6scht markiert)"));
        else if (isHistoricalView())
            sb.append(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.1",
                    " (Historischer Zustand vom {0})", dateHistorical));

        return sb.toString();
    }

    @Override
    protected String getLabelForStartTab() {
        String result = null;

        boolean buildTreeView = false;
        switch (getCollectState().getOuterState()) {
        case CollectState.OUTERSTATE_DETAILS:
            buildTreeView = this.getCollectState().isDetailsModeViewOrEdit();
            break;
        case CollectState.OUTERSTATE_RESULT:
            buildTreeView = this.getSelectedCollectables().size() == 1;
            break;
        }

        if (buildTreeView) {
            result = getTreeViewIdentifier(getSelectedCollectable());
        }

        if (result == null) {
            return super.getLabelForStartTab();
        } else {
            return result.trim();
        }
    }

    private String getTreeViewIdentifier(CollectableGenericObjectWithDependants clct) {
        MetaDataProvider metaprovider = SpringApplicationContextHolder.getBean(MetaDataProvider.class);

        String tmp = getSpringLocaleDelegate().getTreeViewLabel(clct, getEntity(), metaprovider);

        int idx = -1;
        while ((idx = tmp.indexOf("[$" + CollectableFieldFormat.class.getName() + ",")) != -1) {
            tmp = tmp.substring(0, idx) + tmp.substring(tmp.indexOf("$]") + 2);
        }
        return tmp;
    }

    private boolean isSelectedCollectableMarkedAsDeleted() {
        boolean result = false;
        if (getCollectState().getInnerState() == CollectState.DETAILSMODE_VIEW)
            if (!multipleCollectablesSelected()) {
                final CollectableGenericObject clct = getSelectedCollectable();
                if (clct != null && clct.getGenericObjectCVO().isDeleted())
                    result = true;
            }
        return result;
    }

    private static class RestorePreferences implements Serializable {
        private static final long serialVersionUID = 6637996725938917463L;

        String searchFilterName;
        String resultTemplateName;

        Integer processId;
    }

    private static String toXML(RestorePreferences rp) {
        final XStreamSupport xs = XStreamSupport.getInstance();
        final XStream xstream = xs.getXStream();
        try {
            return xstream.toXML(rp);
        } finally {
            xs.returnXStream(xstream);
        }
    }

    private static RestorePreferences fromXML(String xml) {
        final XStreamSupport xs = XStreamSupport.getInstance();
        final XStream xstream = xs.getXStream();
        try {
            return (RestorePreferences) xstream.fromXML(xml);
        } finally {
            xs.returnXStream(xstream);
        }
    }

    @Override
    protected void storeInstanceStateToPreferences(Map<String, String> inheritControllerPreferences) {
        RestorePreferences rp = new RestorePreferences();

        SearchFilter filter = getSelectedSearchFilter();
        rp.searchFilterName = (filter == null || filter.isDefaultFilter()) ? null : filter.getName();

        SearchResultTemplate template = searchResultTemplatesController.getSelectedSearchResultTemplate();
        rp.resultTemplateName = (template == null || template.isDefaultTemplate()) ? null : template.getName();

        if (getProcess() != null) {
            rp.processId = (Integer) getProcess().getValueId();
        }

        inheritControllerPreferences.put(GenericObjectCollectController.class.getName(), toXML(rp));
        super.storeInstanceStateToPreferences(inheritControllerPreferences);
    }

    @Override
    protected void restoreInstanceStateFromPreferences(Map<String, String> inheritControllerPreferences) {
        String prefXml = inheritControllerPreferences.get(GenericObjectCollectController.class.getName());
        if (prefXml != null) {
            RestorePreferences rp = fromXML(prefXml);

            // Restore the settings for the chosen search result template in this module window
            if (rp.resultTemplateName == null)
                searchResultTemplatesController.selectDefaultTemplate();
            else
                // find search result template by name:
                searchResultTemplatesController.setSelectedSearchResultTemplate(rp.resultTemplateName);

            // Restore the settings for the chosen search filter in this module window (may override the global settings)
            if (rp.searchFilterName == null)
                selectDefaultFilter();
            else
                // find filter by name:
                for (int i = 1; i < getSearchFilterComboBox().getItemCount(); ++i)
                    if (((SearchFilter) getSearchFilterComboBox().getItemAt(i)).getName()
                            .equals(rp.searchFilterName)) {
                        getSearchFilterComboBox().setSelectedIndex(i);
                        break;
                    }

            if (rp.processId != null) {
                try {
                    process = new CollectableValueIdField(rp.processId.intValue(), MasterDataCache.getInstance()
                            .get(NuclosEntity.PROCESS.getEntityName(), rp.processId.intValue()).getField("name"));
                } catch (CommonFinderException e) {
                    LOG.warn("Could not restore process setting because process could not be found.", e);
                }
            }
        }
        super.restoreInstanceStateFromPreferences(inheritControllerPreferences);
    }

    @Override
    @Deprecated
    protected int restoreStateFromPreferences(Preferences prefs) throws CommonBusinessException {
        // Restore the settings for the chosen search result template in this module window
        restoreSelectedSearchResultTemplateFromPreferences(prefs);
        return super.restoreStateFromPreferences(prefs);
    }

    /**
     * @param prefs
     * @throws CommonBusinessException
     * @precondition this.isSearchPanelVisible()
     */
    @Override
    @Deprecated
    protected void restoreSearchCriteriaFromPreferences(Preferences prefs) throws CommonBusinessException {
        if (!isSearchPanelAvailable())
            throw new IllegalStateException("!isSearchPanelVisible()");

        // Restore the settings for the chosen search filter in this module window (may override the global settings)
        restoreSelectedSearchFilterFromPreferences(prefs);

        super.restoreSearchCriteriaFromPreferences(prefs);
    }

    @Deprecated
    private void restoreSelectedSearchFilterFromPreferences(Preferences prefs) {
        // restore search filter:
        final String sFilterName = prefs.get(PREFS_KEY_FILTERNAME, null);
        if (sFilterName == null)
            selectDefaultFilter();
        else
            // find filter by name:
            for (int i = 1; i < getSearchFilterComboBox().getItemCount(); ++i)
                if (((SearchFilter) getSearchFilterComboBox().getItemAt(i)).getName().equals(sFilterName)) {
                    getSearchFilterComboBox().setSelectedIndex(i);
                    break;
                }
    }

    @Deprecated
    private void restoreSelectedSearchResultTemplateFromPreferences(Preferences prefs) {
        // restore search result template:
        final String sTemplateName = prefs.get(PREFS_KEY_SEARCHRESULTTEMPLATENAME, null);
        if (sTemplateName == null)
            searchResultTemplatesController.selectDefaultTemplate();
        else
            // find search result template by name:
            searchResultTemplatesController.setSelectedSearchResultTemplate(sTemplateName);
    }

    /**
     * inner class ChangeStateForSelectedCollectablesController
     *
     * @deprecated Move to ResultController hierarchy.
     */
    private static class ChangeStateForSelectedCollectablesController
            extends MultiCollectablesActionController<CollectableGenericObjectWithDependants, Object> {

        private static class ChangeStateAction extends UpdateAction<CollectableGenericObjectWithDependants> {
            private final GenericObjectCollectController ctl;
            private final StateWrapper stateFinal;
            private final List<Integer> statesNew;

            ChangeStateAction(GenericObjectCollectController ctl, StateWrapper stateFinal, List<Integer> statesNew)
                    throws CommonBusinessException {
                super(ctl);
                this.ctl = ctl;
                this.stateFinal = stateFinal;
                this.statesNew = statesNew;
            }

            @Override
            public Object perform(CollectableGenericObjectWithDependants clctlo) throws CommonBusinessException {
                super.perform(clctlo);

                final GenericObjectVO govo = clctlo.getGenericObjectCVO();
                for (final Integer stateNew : statesNew) {
                    ctl.invoke(new CommonRunnable() {
                        @Override
                        public void run() throws CommonBusinessException {
                            StateDelegate.getInstance().changeState(govo.getModuleId(), govo.getId(), stateNew,
                                    ctl.getCustomUsage());
                        }
                    });
                }
                return null;
            }

            @Override
            public String getText(CollectableGenericObjectWithDependants clctlo) {
                return SpringLocaleDelegate.getInstance().getMessage("GenericObjectCollectController.86",
                        "Statuswechsel f\u00fcr Datensatz {0}...",
                        MultiCollectablesActionController.getCollectableLabel(ctl.getEntityName(), clctlo));
            }

            @Override
            public String getSuccessfulMessage(CollectableGenericObjectWithDependants clctlo, Object oResult) {
                return SpringLocaleDelegate.getInstance().getMessage("GenericObjectCollectController.87",
                        "Statuswechsel f\u00fcr Datensatz {0} war erfolgreich.",
                        MultiCollectablesActionController.getCollectableLabel(ctl.getEntityName(), clctlo));
            }

            @Override
            public String getConfirmStopMessage() {
                return SpringLocaleDelegate.getInstance().getMessage("GenericObjectCollectController.101",
                        "Wollen Sie den Statuswechsel f\u00fcr die ausgew\u00e4hlten Datens\u00e4tze an dieser Stelle beenden?\n(Die bisher ge\u00e4nderten Datens\u00e4tze bleiben in jedem Fall ge\u00e4ndert.)");
            }

            @Override
            public String getExceptionMessage(CollectableGenericObjectWithDependants clctlo, Exception ex) {
                String message = NuclosBusinessRuleException.extractOriginFromNuclosBusinessRuleExceptionIfAny(ex);
                if (message == null) {
                    message = ex.getMessage();
                }
                return SpringLocaleDelegate.getInstance().getMessage("GenericObjectCollectController.89",
                        "Statuswechsel ist fehlgeschlagen f\u00fcr Datensatz {0}. {1}",
                        MultiCollectablesActionController.getCollectableLabel(ctl.getEntityName(), clctlo),
                        message);
            }

            @Override
            public void executeFinalAction() throws CommonBusinessException {
                // In order to stay in the multi view mode (if possible), one solution would be the following algorithm.
                // But as that seems a bit oversized here (and potentially inefficient for large search results),
                // we just refresh the search and jump back to the result mode.
                // <algorithm>
                // Refresh the current search result. If the previously selected objects are still in the search result,
                // go back to multi view mode (or stay in multi view mode, virtually).
                // Otherwise display the new search result.
                // </algorithm>

                // store field widths before performing search:
                /** @todo this should be done by the search itself! */
                //            ctl.getResultController().writeSelectedFieldsAndWidthsToPreferences();
                // refresh search result in order to reflect changes made by state transitions:
                ctl.getResultController().getSearchResultStrategy().refreshResult();
                ctl.setCollectState(CollectState.OUTERSTATE_RESULT, CollectState.RESULTMODE_NOSELECTION);
            }
        }

        ChangeStateForSelectedCollectablesController(GenericObjectCollectController ctl, StateWrapper stateNew,
                final List<Integer> statesNew) throws CommonBusinessException {
            super(ctl,
                    SpringLocaleDelegate.getInstance().getMessage("GenericObjectCollectController.88",
                            "Statuswechsel in Status \"{0}\"", stateNew.getStatusText()),
                    new ChangeStateAction(ctl, stateNew, statesNew), ctl.getCompleteSelectedCollectables());
        }

    } // class ChangeStateForSelectedCollectablesController

    /**
     * inner class GenericObjectCollectStateListener
     */
    private class GenericObjectCollectStateListener extends CollectStateAdapter {
        @Override
        public void searchModeEntered(CollectStateEvent ev) throws CommonBusinessException {
            iCurrentLayoutId = null;
            if (!bInitialSearchLayout) {
                setupEditPanelForSearchTab(getInitialLayoutMLDefinitionForSearchPanel());
                setProcessSearchCondition();
                bInitialSearchLayout = true; // do not load layout for search anymore.
            }
            setInitialComponentFocusInSearchTab();
            bGenerated = false;
            setGenerationSource(null, null);
        }

        @Override
        public void searchModeLeft(CollectStateEvent ev) throws CommonBusinessException {
            iCurrentLayoutId = null;
        }

        @Override
        public void resultModeEntered(CollectStateEvent ev) throws NuclosBusinessException {
            bGenerated = false;
            setGenerationSource(null, null);

            if (ev.getOldCollectState().getOuterState() != CollectState.OUTERSTATE_RESULT)
                setupChangeListenerForResultTableVerticalScrollBar();

            final int iResultMode = ev.getNewCollectState().getInnerState();
            final boolean bOneRowSelected = (iResultMode == CollectState.RESULTMODE_SINGLESELECTION);
            final boolean bMoreThanOneRowsSelected = (iResultMode == CollectState.RESULTMODE_MULTISELECTION);
            final boolean bRowsSelected = bOneRowSelected || bMoreThanOneRowsSelected;

            btnDeletePhysicallyInResult.setEnabled(bRowsSelected && isDeleteSelectedCollectableAllowed()
                    && hasCurrentUserDeletionRights(getSelectedCollectable(), true));

            if (LOG.isDebugEnabled())
                if (CollectState.isResultModeSelected(ev.getNewCollectState().getInnerState())) {
                    final UsageCriteria usagecriteria = getGreatestCommonUsageCriteriaFromCollectables(
                            getSelectedCollectables());
                    LOG.debug("Greatest common usagecriteria: " + usagecriteria);
                }
        }

        @Override
        public void resultModeLeft(CollectStateEvent ev) throws NuclosBusinessException {
            if (ev.getNewCollectState().getOuterState() != CollectState.OUTERSTATE_RESULT)
                removePreviousChangeListenersForResultTableVerticalScrollBar();
        }

        @Override
        public void detailsModeEntered(CollectStateEvent ev) throws CommonBusinessException {
            final int iDetailsMode = ev.getNewCollectState().getInnerState();

            final boolean bViewingExistingRecord = (iDetailsMode == CollectState.DETAILSMODE_VIEW);

            final GenericObjectCollectController ctl = GenericObjectCollectController.this;

            ctl.btnDeletePhysicallyInDetails
                    .setEnabled(bViewingExistingRecord && isPhysicallyDeleteAllowed(getSelectedCollectable()));
            ctl.btnMakeTreeRoot.setEnabled(bViewingExistingRecord);
            ctl.btnShowStateHistory.setEnabled(bViewingExistingRecord);
            ctl.btnShowLogBook.setEnabled(bViewingExistingRecord);
            ctl.clctdatechooserHistorical.setEnabled(bViewingExistingRecord);
            ctl.clctdatechooserHistorical.getDateChooser().getJTextField().setEditable(false);
            ctl.chkbxUseInvalidMasterData.setEnabled(bViewingExistingRecord);
            ctl.btnExecuteRule.setEnabled(bViewingExistingRecord);

            // current state, subsequent states and custom actions:
            switch (iDetailsMode) {
            case CollectState.DETAILSMODE_NEW:
                iCurrentLayoutId = null;
                // deselect potentially previously selected entry
                getResultTable().clearSelection();
                getLayoutMLButtonsActionListener().setComponentsEnabled(false);
                cmpStateStandardView.setSelectedItem(null);
                break;
            case CollectState.DETAILSMODE_NEW_SEARCHVALUE:
                iCurrentLayoutId = null;
                // deselect potentially previously selected entry
                getResultTable().clearSelection();
                break;
            case CollectState.DETAILSMODE_VIEW:
                // show subsequent state buttons only in current (non-historical) view and only if the current record is writable:
                setSubsequentStatesVisible(true, ctl.isCurrentRecordWritable());
                setStatesDefaultPathVisible(true, ctl.isCurrentRecordWritable());
                getLayoutMLButtonsActionListener().fireComponentEnabledStateUpdate(false);
                break;
            case CollectState.DETAILSMODE_EDIT:
                // show subsequent state buttons only in current (non-historical) view and only if the current record is writable:
                setSubsequentStatesVisible(true, ctl.isCurrentRecordWritable());
                setStatesDefaultPathVisible(true, ctl.isCurrentRecordWritable());
                getLayoutMLButtonsActionListener().fireComponentEnabledStateUpdate(true);
                break;
            case CollectState.DETAILSMODE_MULTIVIEW:
            case CollectState.DETAILSMODE_MULTIEDIT:
                iCurrentLayoutId = null;
                // show the buttons for subsequent states only if all objects are in the same state:
                if (doTheSelectedGenericObjectsShareACommonState()) {
                    setSubsequentStatesVisible(true, iDetailsMode == CollectState.DETAILSMODE_MULTIVIEW);
                    setStatesDefaultPathVisible(true, iDetailsMode == CollectState.DETAILSMODE_MULTIVIEW);
                } else {
                    setSubsequentStatesVisible(false, false);
                    setStatesDefaultPathVisible(false, false);
                }
                getLayoutMLButtonsActionListener().setComponentsEnabled(false);
                break;

            default:
                // hide the buttons for subsequent states:
                setSubsequentStatesVisible(false, false);
                if (iDetailsMode != CollectState.DETAILSMODE_NEW_CHANGED)
                    setStatesDefaultPathVisible(false, false);
                else {
                    setStatesDefaultPathVisible(true, false);
                    cmpStateStandardView.setSelectedItem(null);
                }
            } // switch

            if (iDetailsMode != CollectState.DETAILSMODE_NEW_CHANGED
                    && iDetailsMode != CollectState.DETAILSMODE_VIEW) {
                bGenerated = false;
                setGenerationSource(null, null);
            }

            setDeleteButtonToggleInDetails();
            // show custom actions only in view mode:
            showCustomActions(iDetailsMode);

            final Collection<SubForm> collsubform = new HashSet<SubForm>();
            for (SubFormController subformctl : getSubFormControllersInDetails())
                collsubform.add(subformctl.getSubForm());

            // dynamic reloading of layouts:
            if (CollectState.isDetailsModeChangesPending(iDetailsMode)) {
                // check if a change in a usagecriteria field caused the state change:
                final Object oSource = getSourceOfLastDetailsChange();
                if (oSource != null && oSource instanceof CollectableComponentModel) {
                    final CollectableComponentModel clctcompmodelSource = (CollectableComponentModel) oSource;
                    if (isUsageCriteriaField(clctcompmodelSource.getFieldName()))
                        // don't add usagecriteria field listeners here. They are added later (see below).
                        reloadLayoutForDetailsTab(false);
                }

                // add listeners for usagecriteria fields:
                /** @todo don't do this asynchronously - it's not safe!!! */
                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        if (!getUsageCriteriaFieldListenersAdded(false))
                            // add usagecriteria field listeners here:
                            addUsageCriteriaFieldListeners(false);
                    }
                });
            }
            // reset the source of last details change in order to prevent a memory leak:
            resetSourceOfLastDetailsChange();

            if (ev.hasOuterStateChanged())
                setInitialComponentFocusInDetailsTab();
        }

        @Override
        public void detailsModeLeft(CollectStateEvent ev) {
            final int iDetailsMode = ev.getOldCollectState().getInnerState();
            final boolean bDetailsChanged = (iDetailsMode == CollectState.DETAILSMODE_EDIT)
                    || (iDetailsMode == CollectState.DETAILSMODE_NEW_CHANGED);

            if (bDetailsChanged) {
                // remove listeners for quintuple fields:
                LOG.debug("removeUsageCriteriaFieldListeners");
                removeUsageCriteriaFieldListeners(false);
            }
            getLayoutMLButtonsActionListener().clearInputMapForParentPanel(getCollectPanel());
            setupShortcutsForTabs(getTab()); // reset all actions here.
        }
    } // inner class GenericObjectCollectStateListener

    private final ListSelectionListener deleteToggleResultListener = new ListSelectionListener() {
        @Override
        public void valueChanged(ListSelectionEvent e) {
            if (MetaDataClientProvider.getInstance().getEntity(getEntityName()).isEditable())
                setDeleteButtonToggleInResult();
        }
    };

    /**
     * set delete button in result mode
     * @param btn
     */
    private void setDeleteButtonToggleInResult() {
        List<CollectableGenericObjectWithDependants> collist = getSelectedCollectables();
        int del = 0;
        int ndel = 0;
        Iterator<CollectableGenericObjectWithDependants> iter = collist.iterator();
        while (iter.hasNext()) {
            CollectableGenericObjectWithDependants col = iter.next();
            if (col.getGenericObjectCVO().isDeleted())
                del++;
            else if (!col.getGenericObjectCVO().isDeleted())
                ndel++;
        }

        final int fdel = del;
        final int fndel = ndel;
        UIUtils.invokeOnDispatchThread(new Runnable() {
            @Override
            public void run() {
                if (fdel == 0 && fndel != 0) {
                    GenericObjectCollectController.this.getResultPanel().btnDelete
                            .setEnabled(actDeleteSelectedCollectables.isEnabled());
                    GenericObjectCollectController.this.getResultPanel().btnDelete.setSelected(false);
                    GenericObjectCollectController.this.getResultPanel().btnDelete
                            .setAction(actDeleteSelectedCollectables);
                } else if (fdel != 0 && fndel == 0) {
                    GenericObjectCollectController.this.getResultPanel().btnDelete
                            .setEnabled(actRestoreSelectedCollectables.isEnabled());
                    GenericObjectCollectController.this.getResultPanel().btnDelete
                            .setSelected(actRestoreSelectedCollectables.isEnabled());
                    GenericObjectCollectController.this.getResultPanel().btnDelete
                            .setAction(actRestoreSelectedCollectables);
                } else if ((fdel == 0 && fndel == 0) || (fdel != 0 && fndel != 0)) {
                    GenericObjectCollectController.this.getResultPanel().btnDelete
                            .setSelected(actDeleteSelectedCollectables.isEnabled());
                    GenericObjectCollectController.this.getResultPanel().btnDelete.setEnabled(false);
                    GenericObjectCollectController.this.getResultPanel().btnDelete
                            .setAction(actDeleteSelectedCollectables);
                }
            }
        });
    }

    /**
     * set toggle delete button in details mode
     */
    private void setDeleteButtonToggleInDetails() {
        CollectableGenericObjectWithDependants gowd = getSelectedCollectable();
        if (gowd == null) {
            getDetailsPanel().btnDelete.setAction(actDeleteCurrentCollectableInDetails);
            getDetailsPanel().btnDelete.setSelected(false);
        } else if (gowd.getGenericObjectCVO().isDeleted()) {
            getDetailsPanel().btnDelete.setAction(actRestoreCurrentCollectableInDetails);
            getDetailsPanel().btnDelete.setSelected(true);
        } else {
            getDetailsPanel().btnDelete.setAction(actDeleteCurrentCollectableInDetails);
            getDetailsPanel().btnDelete.setSelected(false);
        }
    }

    @Override
    protected void setDeleteActionEnabled(boolean enabled) {
        actDeleteCurrentCollectableInDetails.setEnabled(enabled);
        actRestoreCurrentCollectableInDetails.setEnabled(enabled);
    }

    protected static class GenericObjectDetailsPanel extends DetailsPanel {

        public GenericObjectDetailsPanel(Long entityId) {
            super(entityId);
        }

        /**
         * @param compRoot the edit component according to the LayoutML
         * @return the edit component to be used in the Details panel. Default is <code>compRoot</code> itself.
         *         Successors may build their own component/panel out of compRoot.
         * @todo pull down to SearchOrDetailsPanel and/or change signature into EditView newEditView()
         */
        public JComponent newEditComponent(JComponent compRoot) {
            return compRoot;
        }

        @Override
        protected AbstractButton getDeleteButton() {
            return new JToggleButton();
        }

    } // inner class GenericObjectDetailsPanel

    protected static class GenericObjectResultPanel
            extends NuclosResultPanel<CollectableGenericObjectWithDependants> {

        public GenericObjectResultPanel(Long entityId) {
            super(entityId);
        }

        @Override
        protected AbstractButton getDeleteButton() {
            return new JToggleButton();
        }

    }

    protected class GenericObjectCollectPanel extends CollectPanel<CollectableGenericObjectWithDependants> {

        private final CollectableComboBox searchStateBox;

        protected GenericObjectCollectPanel(Long entityId, CollectableComboBox searchStateBox, boolean bSearch,
                boolean bDetailsInOverlay) {
            super(entityId, bSearch, bDetailsInOverlay);
            this.searchStateBox = searchStateBox;
        }

        @Override
        public SearchPanel newSearchPanel(Long entityId) {
            final Collection<CollectableComponent> additionalSearchComponents = new ArrayList<CollectableComponent>();
            if (searchStateBox != null) {
                additionalSearchComponents.add(searchStateBox);
            }
            return new GenericObjectSearchPanel(entityId, additionalSearchComponents);
        }

        @Override
        public ResultPanel<CollectableGenericObjectWithDependants> newResultPanel(Long entityId) {
            return new GenericObjectResultPanel(entityId);
        }

        @Override
        public DetailsPanel newDetailsPanel(Long entityId) {
            return LayoutComponentUtils.setPreferences(getEntityPreferences(),
                    new GenericObjectDetailsPanel(entityId));
        }
    }

    /**
     * @return <code>MultiActionProgressPanel</code> for the MultiObjectsActionController.
     *
     * TODO: Make protected again.
     */
    @Override
    public MultiActionProgressPanel getMultiActionProgressPanel(int iCount) {
        MultiActionProgressPanel multiActionProgressPanel = new MultiActionProgressPanel(iCount);
        multiActionProgressPanel.setResultHandler(new MultiActionProgressResultHandler(this) {
            @Override
            public void handleMultiSelection(Collection<MultiActionProgressLine> selection) {
                Collection<Integer> ids = CollectionUtils.transform(selection,
                        new Transformer<MultiActionProgressLine, Integer>() {
                            @Override
                            public Integer transform(MultiActionProgressLine i) {
                                if (i.getSourceObject() instanceof Collectable) {
                                    return IdUtils.unsafeToId(((Collectable) i).getId());
                                } else if (i.getSourceObject() instanceof Integer) {
                                    return (Integer) i.getSourceObject();
                                }
                                return null;
                            }
                        });
                ((GenericObjectCollectController) controller).openGenericObjectController(ids);
            }
        });
        return multiActionProgressPanel;
    }

    /**
     * open the GenericObjectCollectController for an entity. if collGenericObjectIds only contains one generic object id the controller is opened
     * in details mode if there are more than one id a search expression is created and the search result is shown
     *
     * @param collGenericObjectIds
     */
    private void openGenericObjectController(final Collection<Integer> collGenericObjectIds) {
        UIUtils.runCommand(getTab(), new Runnable() {
            @Override
            public void run() {
                try {
                    GenericObjectClientUtils.showDetails(collGenericObjectIds);
                } catch (CommonBusinessException ex) {
                    Errors.getInstance().showExceptionDialog(getTab(), ex);
                }
            }
        });
    }

    /**
     * creates a SearchResultTemplate accordng to selected columns in search result
     * @throws CommonBusinessException
     */
    protected SearchResultTemplate getCurrentSearchResultFormatFromResultPanel() throws CommonBusinessException {
        final SearchResultTemplate result = new SearchResultTemplate(getModuleId());
        // set selected columns:
        //List<String> sFieldsNames = CollectableUtils.getFieldNamesFromCollectableEntityFields(this.getSelectedFields());
        //result.setVisibleColumns(sFieldsNames);
        //final List<String> lstQualifiedEntityFieldNames = CollectionUtils.transform(getSelectedFields(),
        //   new CollectableEntityField.GetQualifiedEntityFieldName());
        result.setVisibleColumns(getSelectedFields());
        // TODO set sorting column names
        List<CollectableSorting> lstSortingColumnNames = Collections.emptyList();
        result.setSortingOrder(lstSortingColumnNames);
        Map<String, Integer> currentFieldWiths = getResultPanel().getCurrentFieldWithsMap();
        result.setListColumnsWidths(currentFieldWiths);
        //result.setListColumnsFixed(CollectableUtils.getFieldNamesFromCollectableEntityFields(((NuclosResultPanel) this.getResultPanel()).getFixedColumns()));
        final List<String> fixedColumnsNames = CollectableUtils
                .getFieldNamesFromCollectableEntityFields(getResultPanel().getFixedColumns());
        final List<CollectableEntityField> filteredFixedColumns = CollectionUtils.select(getSelectedFields(),
                PredicateUtils.transformedInputPredicate(new CollectableEntityFieldWithEntity.GetName(),
                        PredicateUtils.valuesCollection(fixedColumnsNames)));

        //final List<String> lstQualifiedEntityFieldNamesFixed = CollectionUtils.transform(filteredFixedColumns,
        //   new CollectableEntityField.GetQualifiedEntityFieldName());
        result.setFixedColumns(filteredFixedColumns);
        return result;
    }

    /**
     * @param currclct
     * @return the current collectable filled with the values which are set in the search panel,
     *         but first reload layout if process field is available and filled in search panel
     * @throws CommonBusinessException
     */
    @Override
    protected CollectableGenericObjectWithDependants newCollectableWithSearchValues(
            CollectableGenericObjectWithDependants currclct) throws CommonBusinessException {
        final Collection<SearchComponentModel> collscm = getSearchCollectableComponentModels();

        // iterate over each component in search panel to set process field in details panel
        for (SearchComponentModel scm : collscm)
            if (scm.getFieldName().equals(NuclosEOField.PROCESS.getMetaData().getField())) {
                // set 'process' field in details panel if any found
                for (CollectableComponent clctcomp : getDetailCollectableComponentsFor(scm.getFieldName())) {
                    if (scm.getField().getValue() != null) {
                        currclct.setField(scm.getFieldName(), scm.getField());
                        for (CollectableField clctField : MasterDataDelegate.getInstance()
                                .getProcessByUsage(getModuleId(), false))
                            if (((String) clctField.getValue()).equals(scm.getField().getValue())) {
                                // reload layout according to the 'process' field
                                reloadLayout(new UsageCriteria(getModuleId(), (Integer) clctField.getValueId(),
                                        null, getCustomUsage()), getCollectState(), true, true);
                                detailsChanged(clctcomp);
                                break;
                            }
                    }
                    break;
                }
                break;
            }
        return super.newCollectableWithSearchValues(currclct);
    }

    /**
     * complete the current collectable with the subform values which are set in the search panel
     */
    @Override
    protected void newCollectableWithDependantSearchValues() throws NuclosBusinessException {
        Collection<SearchConditionSubFormController> collscsfc = getSubFormControllersInSearch();
        Collection<DetailsSubFormController<CollectableEntityObject>> colldsfc = getSubFormControllersInDetails();
        // iterate over each search subform
        for (SearchConditionSubFormController scsfc : collscsfc)
            // handel only subforms of the first hierarchie
            if (scsfc.getSubForm().getParentSubForm() == null)
                // iterate over each detail subform
                for (DetailsSubFormController<CollectableEntityObject> dsfc : colldsfc) {
                    if (dsfc.getEntityAndForeignKeyFieldName().getEntityName()
                            .equals(scsfc.getEntityAndForeignKeyFieldName().getEntityName()))
                        if (dsfc.getSubForm().isEnabled()) {
                            SubForm.SubFormTableModel searchTableModel = scsfc.getSearchConditionTableModel();
                            CollectableTableModel<CollectableEntityObject> detailsTableModel = dsfc
                                    .getCollectableTableModel();
                            Collection<CollectableMasterData> newCollectables = new ArrayList<CollectableMasterData>();
                            // iterate over each row found in the search subform
                            for (int iSearchRow = 0; iSearchRow < searchTableModel.getRowCount(); iSearchRow++) {
                                CollectableMasterData clctmd = dsfc.insertNewRow();
                                newCollectables.add(clctmd);
                                // iterate over each column found in the search subform
                                for (int iSearchColumn = 0; iSearchColumn < searchTableModel
                                        .getColumnCount(); iSearchColumn++)
                                    // iterate over each coresponding column found in the detail subform
                                    for (int columnDetail = 0; columnDetail < detailsTableModel
                                            .getColumnCount(); columnDetail++)
                                        if (searchTableModel.getColumnName(iSearchColumn)
                                                .equals(detailsTableModel.getColumnName(columnDetail))) {
                                            TableCellEditor tce = dsfc.getSubForm().getJTable()
                                                    .getCellEditor(iSearchRow, columnDetail);

                                            if (tce instanceof CollectableComponentTableCellEditor) {
                                                boolean bSetAllowed = true;

                                                if (!isSetAllowedForClctComponent(
                                                        ((CollectableComponentTableCellEditor) tce)
                                                                .getCollectableComponent()))
                                                    bSetAllowed = false;

                                                if (bSetAllowed) {
                                                    Object oClctSearchCondition = searchTableModel
                                                            .getValueAt(iSearchRow, iSearchColumn);
                                                    if (oClctSearchCondition != null) {
                                                        String sFieldName = ((AtomicCollectableSearchCondition) oClctSearchCondition)
                                                                .getFieldName();
                                                        Object oSearchValue = null;
                                                        Object oSearchValueId = null;
                                                        if (oClctSearchCondition instanceof CollectableComparison) {
                                                            CollectableField clctField = ((CollectableComparison) oClctSearchCondition)
                                                                    .getComparand();
                                                            if (clctField instanceof CollectableValueIdField)
                                                                oSearchValueId = ((CollectableValueIdField) clctField)
                                                                        .getValueId();

                                                            oSearchValue = clctField.getValue();
                                                        } else if (oClctSearchCondition instanceof CollectableLikeCondition)
                                                            oSearchValue = ((CollectableLikeCondition) oClctSearchCondition)
                                                                    .getLikeComparand();

                                                        if (oSearchValue != null) {
                                                            if (oSearchValueId != null) {
                                                                clctmd.setField(sFieldName,
                                                                        new CollectableValueIdField(oSearchValueId,
                                                                                oSearchValue));
                                                                if (clctmd.getMasterDataCVO() != null)
                                                                    clctmd.getMasterDataCVO().setField(
                                                                            sFieldName + "Id", oSearchValueId);
                                                            } else {
                                                                clctmd.setField(sFieldName,
                                                                        new CollectableValueField(oSearchValue));
                                                                if (clctmd.getMasterDataCVO() != null)
                                                                    clctmd.getMasterDataCVO().setField(sFieldName,
                                                                            oSearchValue);
                                                            }
                                                            newCollectables.remove(clctmd);
                                                            detailsChanged(dsfc.getSubForm());
                                                        }
                                                    }
                                                }
                                            }
                                        }
                            }
                            for (CollectableMasterData clctmd : newCollectables) {
                                dsfc.getCollectableTableModel().remove(clctmd);
                            }
                        }
                }
    }

    /**
     * @return whether the current collectable is generated
     */
    public boolean isCollectableGenerated() {
        return bGenerated;
    }

    /**
     * @return the source object's ids if current collectable is generated
     */
    public Collection<Long> getSourceObjectId() {
        return iGenericObjectIdSources;
    }

    /**
     * @deprecated Move to SearchController and make protected again.
     */
    @Override
    public Collection<CollectableEntityField> getAdditionalSearchFields() {
        Collection<CollectableEntityField> additionalFields = new HashSet<CollectableEntityField>();
        if (super.getAdditionalSearchFields() != null)
            additionalFields.addAll(super.getAdditionalSearchFields());
        return additionalFields;

    }

    private static class GenericObjectEditView extends DefaultEditView {

        protected GenericObjectEditView(JComponent compRoot, CollectableComponentsProvider clctcompprovider,
                EditModel model, boolean bForSearch, EntityAndFieldName initialFocusField) {
            super(compRoot, clctcompprovider, model, initialFocusField);
        }

    } // inner class GenericObjectEditView

    @Override
    public GenericObjectEditView newSearchEditView(LayoutRoot layoutroot) {
        final JComponent compEdit = getSearchPanel().newEditComponent(layoutroot.getRootComponent());
        final GenericObjectCollectableComponentsProvider prov = new GenericObjectCollectableComponentsProvider(
                layoutroot, getSearchStateBox());
        final boolean forSearch = true;
        return new GenericObjectEditView(compEdit, prov, newGenericObjectEditModel(prov, forSearch), forSearch,
                layoutroot.getInitialFocusEntityAndFieldName());
    }

    private EditModel newGenericObjectEditModel(CollectableComponentsProvider clctcompprovider,
            boolean bForSearch) {
        final Collection<CollectableComponent> clctcomp = clctcompprovider.getCollectableComponents();
        return bForSearch ? new GenericObjectSearchEditModel(clctcomp) : new DefaultDetailsEditModel(clctcomp);
    }

    public class GenericObjectSearchEditModel extends DefaultSearchEditModel {

        public GenericObjectSearchEditModel(Collection<CollectableComponent> collclctcomp) {
            super(collclctcomp);
        }

        @Override
        public SearchComponentModel getCollectableComponentModelFor(String sFieldName) {
            if (NuclosEOField.STATE.getMetaData().getField().equals(sFieldName)
                    || NuclosEOField.STATENUMBER.getMetaData().getField().equals(sFieldName)
                    || NuclosEOField.STATEICON.getMetaData().getField().equals(sFieldName))
                return getSearchStateBox().getSearchModel();
            return super.getCollectableComponentModelFor(sFieldName);
        }

        @Override
        public Collection<SearchComponentModel> getCollectableComponentModels() {
            Collection<SearchComponentModel> result = new HashSet<SearchComponentModel>();
            result.addAll(super.getCollectableComponentModels());
            result.add(getSearchStateBox().getSearchModel());
            return result;
        }

        @Override
        public Collection<String> getFieldNames() {
            Collection<String> result = new HashSet<String>();
            result.addAll(super.getFieldNames());
            result.add(NuclosEOField.STATE.getMetaData().getField());
            result.add(NuclosEOField.STATENUMBER.getMetaData().getField());
            result.add(NuclosEOField.STATEICON.getMetaData().getField());
            return result;
        }

    } //inner class GenericObjectSearchEditModel

    private static class GenericObjectCollectableComponentsProvider implements CollectableComponentsProvider {

        private final CollectableComponentsProvider mainProvider;

        private final CollectableComboBox searchStateBox;

        public GenericObjectCollectableComponentsProvider(CollectableComponentsProvider provider,
                CollectableComboBox searchStateBox) {
            this.mainProvider = provider;
            this.searchStateBox = searchStateBox;
        }

        @Override
        public Collection<CollectableComponent> getCollectableComponents() {
            Collection<CollectableComponent> mainComponents = mainProvider.getCollectableComponents();
            mainComponents.add(searchStateBox);
            return mainComponents;
        }

        @Override
        public Collection<CollectableComponent> getCollectableComponentsFor(String sFieldName) {
            if (NuclosEOField.STATE.getMetaData().getField().equals(sFieldName)
                    || NuclosEOField.STATENUMBER.getMetaData().getField().equals(sFieldName)
                    || NuclosEOField.STATEICON.getMetaData().getField().equals(sFieldName)) {
                Collection<CollectableComponent> result = new ArrayList<CollectableComponent>();
                result.addAll(mainProvider.getCollectableComponentsFor(sFieldName));
                result.add(searchStateBox);
                return result;
            }
            return mainProvider.getCollectableComponentsFor(sFieldName);
        }

        @Override
        public Collection<CollectableComponent> getCollectableLabels() {
            return mainProvider.getCollectableLabels();
        }

    } // inner class GenericObjectCollectableComponentsProvider

    public void runViewSingleCollectable(CollectableGenericObjectWithDependants clct, boolean bShow,
            CommonRunnable pAfterLoadingRunnable) {
        this.subFormsLoader.setAfterLoadingRunnable(pAfterLoadingRunnable);
        super.runViewSingleCollectable(clct, bShow);
    }

    protected void highlightMandatoryByState(Integer stateId) {
        if (stateId != null) {
            for (StateVO statevo : StateDelegate.getInstance().getStatemodelClosure(getModuleId()).getAllStates()) {
                if (stateId.equals(statevo.getId())) {
                    final Set<String> mandatoryfields = CollectionUtils.transformIntoSet(
                            statevo.getMandatoryFields(), new Transformer<MandatoryFieldVO, String>() {
                                @Override
                                public String transform(MandatoryFieldVO i) {
                                    EntityFieldMetaDataVO efMeta = MetaDataClientProvider.getInstance()
                                            .getEntityField(getEntity(), i.getFieldId().longValue());
                                    return efMeta != null ? efMeta.getField() : null;
                                }
                            });
                    setCollectableComponentModelsInDetailsMandatory(mandatoryfields);
                }
            }
        }
    }

    @Override
    protected void highlightMandatory() {
        super.highlightMandatory();
        //getUsageCriteria(getSelectedCollectable());

        if (getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_VIEW
                || getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_EDIT) {
            highlightMandatoryByState(getSelectedGenericObjectStateId());

        } else if (getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_MULTIEDIT
                || getCollectStateModel().getDetailsMode() == CollectState.DETAILSMODE_MULTIVIEW) {
            final Collection<Integer> collStateIds = getStateIds(getSelectedCollectables());
            if (collStateIds.size() == 1) {
                highlightMandatoryByState(collStateIds.iterator().next());
            }
        }
    }

    @Override
    public Map<String, DetailsSubFormController<CollectableEntityObject>> getDetailsSubforms() {
        return this.mpsubformctlDetails;
    }

    protected void setGenerationSource(Collection<Long> ids, GeneratorActionVO action) {
        iGenericObjectIdSources = ids;
        oGeneratorAction = action;
        bGenerated = true;
    }

    @Override
    public List<GeneratorActionVO> getGeneratorActions() {
        try {
            if (getCollectState().isResultMode()) {
                return getGeneratorActions(getResultController().getSelectedCollectablesFromTableModel());
            } else if (CollectState.isDetailsModeViewOrEdit(getCollectStateModel().getDetailsMode())) {
                final UsageCriteria usagecriteria = getUsageCriteriaFromView(false);
                final Integer iStateNumeral = getSelectedGenericObjectStateNumeral();
                return GeneratorActions.getActions(getModuleId(), iStateNumeral, usagecriteria.getProcessId());
            } else {
                final Integer iProcessId = getSelectedGenericObjectsCommonFieldIdByFieldName(
                        NuclosEOField.PROCESS.getMetaData().getField());
                final Integer iStateNumeral = getSelectedGenericObjectsCommonStateNumeral();
                return GeneratorActions.getActions(getModuleId(), iStateNumeral, iProcessId);
            }
        } catch (NoSuchElementException ex) {
            LOG.info("Keinen aktuellen Zustand gefunden f\u00fcr GenericObject mit Id "
                    + getSelectedGenericObjectId() + ".");
            return Collections.emptyList();
        } catch (CollectableFieldFormatException ex) {
            throw new NuclosFatalException(getSpringLocaleDelegate().getMessage("GenericObjectCollectController.61",
                    "Prozess-Id ist ung\u00fcltig."), ex);
        }
    }

    @Override
    public List<GeneratorActionVO> getGeneratorActions(
            Collection<CollectableGenericObjectWithDependants> selectedCollectablesFromResult) {
        List<GeneratorActionVO> result = null;

        for (CollectableGenericObjectWithDependants go : selectedCollectablesFromResult) {
            DynamicAttributeVO dynState = go.getGenericObjectCVO()
                    .getAttribute(NuclosEOField.STATE.getMetaData().getId().intValue());
            DynamicAttributeVO dynProcess = go.getGenericObjectCVO()
                    .getAttribute(NuclosEOField.PROCESS.getMetaData().getId().intValue());

            Integer iStateNumeral = null;
            Integer iProcessId = null;
            if (dynState != null) {
                StateVO statevo = StateDelegate.getInstance().getState(getModuleId(), dynState.getValueId());
                if (statevo != null) {
                    iStateNumeral = statevo.getNumeral();
                }
            }
            if (dynProcess != null) {
                iProcessId = dynProcess.getValueId();
            }

            List<GeneratorActionVO> goActions = GeneratorActions.getActions(getModuleId(), iStateNumeral,
                    iProcessId);
            if (result == null) {
                result = goActions;
            } else {
                result = new ArrayList<GeneratorActionVO>(CollectionUtils.intersection(result, goActions));
                //                  , new BinaryPredicate<GeneratorActionVO, GeneratorActionVO>() {
                //               @Override
                //               public boolean evaluate(GeneratorActionVO t1, GeneratorActionVO t2) {
                //                  return LangUtils.equals(t1.getId(), t2.getId());
                //            }}));
            }
        }

        return result == null ? new ArrayList<GeneratorActionVO>() : GeneratorActions.sort(result);
    }

    @Override
    public void cmdGenerateObject(GeneratorActionVO generatoractionvo) {
        Map<Long, UsageCriteria> sources = new HashMap<Long, UsageCriteria>();
        for (CollectableGenericObjectWithDependants clct : getSelectedCollectables()) {
            sources.put(IdUtils.toLongId(clct.getId()), getUsageCriteria(clct));
        }
        GenerationController controller = new GenerationController(sources, generatoractionvo, this, getTab());
        controller.generateGenericObject();
    }

    @Override
    public Collection<RuleVO> getUserRules() {
        try {
            UsageCriteria uc = getUsageCriteriaFromView(false);
            final Collection<RuleVO> collRules = RuleDelegate.getInstance()
                    .findRulesByUsageAndEvent(RuleEventUsageVO.USER_EVENT, uc);
            // remove inactive rules
            CollectionUtils.removeAll(collRules, new Predicate<RuleVO>() {
                @Override
                public boolean evaluate(RuleVO rulevo) {
                    return !rulevo.isActive();
                }
            });

            // add EventSupports if existing
            collRules.addAll(EventSupportDelegate.getInstance()
                    .findEventSupportsByUsageAndEvent("org.nuclos.api.eventsupport.CustomSupport", uc));

            return collRules;
        } catch (Exception e) {
            return Collections.EMPTY_LIST;
        }
    }

    public CollectableField getProcess() {
        return process;
    }

    public void setProcess(CollectableField process) {
        this.process = process;
        setProcessSearchCondition();
    }

    @Override
    protected CollectableGenericObjectWithDependants newCollectableWithDefaultValues() {
        final CollectableGenericObjectWithDependants result = super.newCollectableWithDefaultValues();
        if (getProcess() != null) {
            result.setField(NuclosEOField.PROCESS.getName(),
                    new CollectableValueIdField(getProcess().getValueId(), getProcess().getValue()));
        }
        return result;
    }

    @Override
    protected void clearSearchFields() {
        super.clearSearchFields();
        setProcessSearchCondition();
    }

    private void setProcessSearchCondition() {
        if (getProcess() != null && isSearchPanelAvailable()) {
            CollectableComponentModel m = getSearchEditView().getModel()
                    .getCollectableComponentModelFor(NuclosEOField.PROCESS.getName());
            if (m != null) {
                m.setField(new CollectableValueIdField(getProcess().getValueId(), getProcess().getValue()));
            }
        }
    }

    protected List<StateVO> getSubsequentStates(
            Collection<CollectableGenericObjectWithDependants> selectedCollectablesFromResult) {
        List<StateVO> result = null;

        for (CollectableGenericObjectWithDependants go : selectedCollectablesFromResult) {
            UsageCriteria uc = getUsageCriteria(go);
            DynamicAttributeVO av = go.getGenericObjectCVO()
                    .getAttribute(NuclosEOField.STATE.getMetaData().getId().intValue());
            List<StateVO> lstSubsequentStates = StateDelegate.getInstance().getStatemodel(uc)
                    .getSubsequentStates(av.getValueId(), false);
            if (result == null) {
                result = lstSubsequentStates;
            } else {
                result = new ArrayList<StateVO>(CollectionUtils.intersection(result, lstSubsequentStates,
                        new BinaryPredicate<StateVO, StateVO>() {
                            @Override
                            public boolean evaluate(StateVO t1, StateVO t2) {
                                return LangUtils.equals(t1.getId(), t2.getId());
                            }
                        }));
            }
        }

        if (result == null) {
            return new ArrayList<StateVO>();
        } else {
            return CollectionUtils.sorted(result, new Comparator<StateVO>() {
                @Override
                public int compare(StateVO o1, StateVO o2) {
                    return LangUtils.compare(o1.getNumeral(), o2.getNumeral());
                }
            });
        }
    }

    protected void setupResultContextMenuStates() {
        this.getResultPanel().popupmenuRow.addPopupMenuListener(new PopupMenuListener() {

            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                UIUtils.runCommand(getTab(), new Runnable() {

                    @Override
                    public void run() {
                        try {
                            final List<StateVO> lstSubsequenteStates = getSubsequentStates(
                                    getSelectedCollectables());
                            JMenu mi = getResultPanel().miStates;
                            mi.setVisible(lstSubsequenteStates.size() != 0);

                            // try to find single soruce state...
                            StateVO statevoSource = getSingleSourceStateIfAny(getSelectedCollectables());

                            for (final StateVO statevoTarget : lstSubsequenteStates) {
                                final JMenuItem miStateChange = new JMenuItem(
                                        new StateChangeAction(statevoSource, statevoTarget));
                                miStateChange.setIcon(null);
                                mi.add(miStateChange);
                            }
                        } catch (Exception e1) {
                            getResultPanel().miStates.setVisible(false);
                            LOG.warn("popupMenuWillBecomeVisible failed: " + e1 + ", setting it invisible");
                        }
                    }
                });
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                clearStatesMenu();
            }

            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {
                clearStatesMenu();
            }

            private void clearStatesMenu() {
                getResultPanel().miStates.removeAll();
            }
        });
    }

    protected class StateChangeAction extends AbstractAction {

        private final StateVO statevoSource;
        private final StateVO statevoTarget;

        public StateChangeAction(StateVO statevoSource, StateVO statevoTarget) {
            super(StringUtils.looksEmpty(statevoTarget.getButtonLabel()) ? statevoTarget.getStatename()
                    : SpringLocaleDelegate.getInstance().getResource(statevoTarget.getButtonLabel(),
                            statevoTarget.getStatename()),
                    statevoTarget.getButtonIcon() == null ? null
                            : ResourceCache.getInstance().getIconResource(statevoTarget.getButtonIcon().getId()));
            this.statevoSource = statevoSource;
            this.statevoTarget = statevoTarget;
            putValue("Color", statevoTarget.getColor());
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            StateWrapper swSource = null;
            if (statevoSource != null) {
                swSource = new StateWrapper(statevoSource.getId(), statevoSource.getNumeral(),
                        getSpringLocaleDelegate()
                                .getResource(/*StateDelegate.getInstance().getResourceSIdForName(statevo.getId()*/
                                        StateDelegate.getInstance().getStatemodelClosure(getModuleId())
                                                .getResourceSIdForLabel(statevoSource.getId()),
                                        statevoSource.getStatename()),
                        statevoSource.getIcon(), statevoSource.getDescription(), statevoSource.getColor(),
                        statevoSource.getButtonIcon(), statevoSource.isFromAutomatic(),
                        Collections.singletonList(statevoSource.getId()));
            }

            List<Integer> newStates = new ArrayList<Integer>(1);
            newStates.add(statevoTarget.getId());
            StateWrapper swTarget = new StateWrapper(statevoTarget.getId(), statevoTarget.getNumeral(),
                    getSpringLocaleDelegate()
                            .getResource(/*StateDelegate.getInstance().getResourceSIdForName(statevo.getId()*/
                                    StateDelegate.getInstance().getStatemodelClosure(getModuleId())
                                            .getResourceSIdForLabel(statevoTarget.getId()),
                                    statevoTarget.getStatename()),
                    statevoTarget.getIcon(), statevoTarget.getDescription(), statevoTarget.getColor(),
                    statevoTarget.getButtonIcon(), statevoTarget.isFromAutomatic(), newStates);
            cmdChangeStates(swSource, swTarget, swTarget.getStatesBefore());
        }

    }

    @Override
    protected List<ResultActionCollection> getResultActionsMultiThreaded(
            Collection<CollectableGenericObjectWithDependants> selectedCollectablesFromResult) {
        List<ResultActionCollection> result = super.getResultActionsMultiThreaded(selectedCollectablesFromResult);
        if (result == null) {
            result = new ArrayList<ResultActionCollection>();
        }
        ResultActionCollection rac = new ResultActionCollection(
                SpringLocaleDelegate.getInstance().getMessage("ResultPanel.15", "Statuswechsel"));

        // try to find single source state...
        StateVO statevoSource = getSingleSourceStateIfAny(selectedCollectablesFromResult);

        for (StateVO statevoTarget : getSubsequentStates(selectedCollectablesFromResult)) {

            rac.addAction(new StateChangeAction(statevoSource, statevoTarget));
        }
        result.add(0, rac);
        return result;
    }

    private StateVO getSingleSourceStateIfAny(
            Collection<CollectableGenericObjectWithDependants> selectedCollectables) {
        Integer sourceStateId = null;
        for (CollectableGenericObjectWithDependants go : selectedCollectables) {
            Integer currentStateId = (Integer) go.getValueId(NuclosEOField.STATE.getName());
            if (sourceStateId == null) {
                sourceStateId = currentStateId;
            } else {
                if (!sourceStateId.equals(currentStateId)) {
                    sourceStateId = null;
                    break;
                }
            }
        }
        return sourceStateId == null ? null : StateDelegate.getInstance().getState(getModuleId(), sourceStateId);
    }

} // class GenericObjectCollectController