cz.cas.lib.proarc.webapp.client.presenter.DigitalObjectEditor.java Source code

Java tutorial

Introduction

Here is the source code for cz.cas.lib.proarc.webapp.client.presenter.DigitalObjectEditor.java

Source

/*
 * Copyright (C) 2012 Jan Pokorsky
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package cz.cas.lib.proarc.webapp.client.presenter;

import com.google.gwt.activity.shared.ActivityManager;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceController;
import com.google.web.bindery.event.shared.SimpleEventBus;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.data.RecordList;
import com.smartgwt.client.data.ResultSet;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.SelectionType;
import com.smartgwt.client.types.VerticalAlignment;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.IconButton;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.events.DrawEvent;
import com.smartgwt.client.widgets.events.DrawHandler;
import com.smartgwt.client.widgets.events.VisibilityChangedEvent;
import com.smartgwt.client.widgets.events.VisibilityChangedHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.Layout;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.menu.IconMenuButton;
import com.smartgwt.client.widgets.menu.Menu;
import com.smartgwt.client.widgets.toolbar.ToolStrip;
import com.smartgwt.client.widgets.toolbar.ToolStripSeparator;
import cz.cas.lib.proarc.common.object.model.DatastreamEditorType;
import cz.cas.lib.proarc.webapp.client.ClientMessages;
import cz.cas.lib.proarc.webapp.client.ClientUtils;
import cz.cas.lib.proarc.webapp.client.ClientUtils.SweepTask;
import cz.cas.lib.proarc.webapp.client.Editor;
import cz.cas.lib.proarc.webapp.client.action.AbstractAction;
import cz.cas.lib.proarc.webapp.client.action.Action;
import cz.cas.lib.proarc.webapp.client.action.ActionEvent;
import cz.cas.lib.proarc.webapp.client.action.Actions;
import cz.cas.lib.proarc.webapp.client.action.Actions.ActionSource;
import cz.cas.lib.proarc.webapp.client.action.DigitalObjectEditAction;
import cz.cas.lib.proarc.webapp.client.action.DigitalObjectEditAction.AcceptFilter;
import cz.cas.lib.proarc.webapp.client.action.DigitalObjectNavigateAction;
import cz.cas.lib.proarc.webapp.client.action.DigitalObjectNavigateAction.ChildSelector;
import cz.cas.lib.proarc.webapp.client.action.RefreshAction;
import cz.cas.lib.proarc.webapp.client.action.RefreshAction.Refreshable;
import cz.cas.lib.proarc.webapp.client.action.Selectable;
import cz.cas.lib.proarc.webapp.client.ds.DigitalObjectDataSource.DigitalObject;
import cz.cas.lib.proarc.webapp.client.ds.MetaModelDataSource;
import cz.cas.lib.proarc.webapp.client.ds.MetaModelDataSource.MetaModelRecord;
import cz.cas.lib.proarc.webapp.client.ds.SearchDataSource;
import cz.cas.lib.proarc.webapp.client.event.EditorLoadEvent;
import cz.cas.lib.proarc.webapp.client.event.EditorLoadHandler;
import cz.cas.lib.proarc.webapp.client.event.HasEditorLoadHandlers;
import cz.cas.lib.proarc.webapp.client.presenter.DigitalObjectEditing.DigitalObjectEditorPlace;
import cz.cas.lib.proarc.webapp.client.widget.BatchDatastreamEditor;
import cz.cas.lib.proarc.webapp.client.widget.DatastreamEditor;
import cz.cas.lib.proarc.webapp.client.widget.DigitalObjectAdministrationEditor;
import cz.cas.lib.proarc.webapp.client.widget.DigitalObjectChildrenEditor;
import cz.cas.lib.proarc.webapp.client.widget.DigitalObjectChildrenEditor.ChildActivities;
import cz.cas.lib.proarc.webapp.client.widget.DigitalObjectChildrenEditor.ChildEditorDisplay;
import cz.cas.lib.proarc.webapp.client.widget.DigitalObjectParentEditor;
import cz.cas.lib.proarc.webapp.client.widget.MediaEditor;
import cz.cas.lib.proarc.webapp.client.widget.TextEditor;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.logging.Logger;

/**
 * Edits digital object data streams.
 *
 * @author Jan Pokorsky
 */
public final class DigitalObjectEditor
        implements Refreshable, Selectable<Record>, HasEditorLoadHandlers, ChildSelector {

    private static final Logger LOG = Logger.getLogger(DigitalObjectEditor.class.getName());
    private final ClientMessages i18n;
    private final VLayout widget;
    private final Label lblHeader;
    private final ToolStrip toolbar;
    private ToolStripSeparator customToolbarSeparator;
    private final VLayout editorContainer;
    private VLayout optionalEditorContainer;
    private EditorDescriptor currentEditor;
    private OptionalEditor optionalEditor;
    private IconButton optionalEditorSwitch;
    /** currently edited object {PID, MODEL_OBJECT}; should be replaced with some interface */
    private Record[] selection;
    private final EnumMap<DatastreamEditorType, EditorDescriptor> editorCache;
    private final ActionSource actionSource;
    private final PlaceController places;
    private boolean importView;
    private boolean embeddedView;
    private boolean optionalView;
    private HandlerManager handlerManager;
    private Label unsupportedEditor;
    /** Holds the last used editor type. */
    private DatastreamEditorType lastEditorType;

    public DigitalObjectEditor(ClientMessages i18n, PlaceController places) {
        this(i18n, places, false);
    }

    public DigitalObjectEditor(ClientMessages i18n, PlaceController places, boolean embedded) {
        this.i18n = i18n;
        this.places = places;
        this.editorCache = new EnumMap<DatastreamEditorType, EditorDescriptor>(DatastreamEditorType.class);
        this.widget = new VLayout();
        this.lblHeader = new Label();
        lblHeader.setAutoHeight();
        lblHeader.setPadding(4);
        lblHeader.setStyleName(Editor.CSS_PANEL_DESCRIPTION_TITLE);
        this.actionSource = new ActionSource(this);
        this.embeddedView = embedded;
        this.toolbar = Actions.createToolStrip();
        this.editorContainer = new VLayout();
        editorContainer.setLayoutMargin(4);
        editorContainer.setWidth100();
        editorContainer.setHeight100();

        widget.addMember(lblHeader);
        widget.addMember(toolbar);

        if (embedded) {
            widget.addMember(editorContainer);
        } else {
            editorContainer.setResizeBarTarget("next");
            HLayout multiView = new HLayout();
            multiView.setWidth100();
            multiView.setHeight100();
            multiView.setLayoutMargin(4);
            multiView.addMember(editorContainer);
            initOptionalEditor(multiView);
            widget.addMember(multiView);
        }
    }

    private void initOptionalEditor(Layout multiView) {
        if (embeddedView) {
            return;
        }
        editorContainer.setShowResizeBar(true);
        VLayout optionalEditorInnerContainer = new VLayout();
        optionalEditorContainer = new VLayout();
        optionalEditor = new OptionalEditor(i18n, optionalEditorInnerContainer);
        optionalEditorInnerContainer.addStyleName("defaultBorder");
        optionalEditorContainer.setLayoutMargin(4);
        optionalEditorContainer.setWidth100();
        optionalEditorContainer.setVisible(false);
        optionalEditorContainer.setMinMemberSize(200);
        optionalEditorContainer.setMembers(optionalEditorInnerContainer);
        multiView.addMember(optionalEditorContainer);
        optionalEditorContainer.addDrawHandler(new DrawHandler() {

            @Override
            public void onDraw(DrawEvent event) {
                //                LOG.warning("onDraw: " + widget.getID() + ", drawn: " + optionalEditorContainer.isDrawn()
                //                        + ", isVisible: " + optionalEditorContainer.isVisible()
                //                        + ", isSelected: " + optionalEditorSwitch.isSelected()
                //                        );
                if (optionalEditor.isEnabled()) {
                    // Ignore events thrown while the editor is enabled as it breaks
                    // the layout hierarchy in case browser history usage.
                    // onDraw is necessary as VisibilityChangedEvent is not fired
                    // before drawing a widget.
                    return;
                }
                switchOptionalEditor(true);
            }
        });
        // switch the editor on/off by clicking the resize bar
        optionalEditorContainer.addVisibilityChangedHandler(new VisibilityChangedHandler() {

            @Override
            public void onVisibilityChanged(VisibilityChangedEvent event) {
                //                LOG.warning("onVisibilityChanged: " + widget.getID() + ", visible: " + event.getIsVisible());
                switchOptionalEditor(event.getIsVisible());
            }
        });
    }

    public Canvas getUI() {
        return widget;
    }

    /**
     * Shows UI for the passed editor type and fetches digital objects.
     * @param type optional type. If {@code null} the last type or a reasonable default is used
     * @param pids digital objects to fetch
     */
    public void edit(DatastreamEditorType type, Record[] pids) {
        if (pids == null || pids.length == 0) {
            // this should occur just in case someone breakes URL in browser.
            notifyMissingPid(type);
            return;
        }

        OpenEditorTask task = new OpenEditorTask(pids);
        edit(type, task, pids.length > 1);
    }

    /**
     * {@link #edit(cz.cas.lib.proarc.common.object.model.DatastreamEditorType, com.smartgwt.client.data.Record[])
     * description}
     */
    public void edit(DatastreamEditorType type, String... pids) {
        if (pids.length == 0) {
            // this should occur just in case someone breakes URL in browser.
            notifyMissingPid(type);
            return;
        }

        OpenEditorTask task = new OpenEditorTask(pids);
        edit(type, task, pids.length > 1);
    }

    private void notifyMissingPid(DatastreamEditorType type) {
        ClientUtils.severe(LOG, "invalid edit parameters: %s, no pid", type);
        SC.warn("Invalid URL!");
        places.goTo(Place.NOWHERE);
    }

    private void edit(DatastreamEditorType type, OpenEditorTask task, boolean multiselection) {
        this.selection = null;
        if (type == null) {
            //            ClientUtils.warning(LOG, "missing type, objects: %s", task.toString());
            // reasonable default
            if (lastEditorType != null) {
                type = lastEditorType;
            } else if (optionalView) {
                type = multiselection ? DatastreamEditorType.PARENT : DatastreamEditorType.MEDIA;
            } else {
                type = multiselection ? DatastreamEditorType.PARENT : DatastreamEditorType.MODS;
            }
        }
        lastEditorType = type;

        EditorDescriptor previousEditor = currentEditor;
        currentEditor = getDatastreamEditor(type);
        updateToolbar(previousEditor, currentEditor);
        task.start();
    }

    public void focus() {
        if (currentEditor != null) {
            currentEditor.getEditor().focus();
        }
    }

    private void setSelection(Record[] records) {
        this.selection = records;
        actionSource.fireEvent();
    }

    private void openEditor() {
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {

            @Override
            public void execute() {
                openEditorImpl();
            }
        });
    }

    private void openEditorImpl() {
        final DatastreamEditor editor = currentEditor.getEditor();
        final Record[] records = getSelection();
        DigitalObject[] dobjs = records == null ? new DigitalObject[0] : DigitalObject.toArray(records);
        if (dobjs.length > 1) {
            setDescription(currentEditor.getTitle(),
                    i18n.DigitalObjectEditor_MultiSelection_Title(String.valueOf(dobjs.length)), null);
            BatchDatastreamEditor beditor = editor.getCapability(BatchDatastreamEditor.class);
            if (beditor != null) {
                beditor.edit(DigitalObject.toArray(records));
                ClientUtils.setMembers(editorContainer, editor.getUI());
            } else {
                openUnsupportedEditor();
            }
        } else {
            MetaModelRecord model = dobjs[0].getModel();
            setDescription(currentEditor.getTitle(), getLabel(records[0]), model);
            if (model.isSupportedDatastream(currentEditor.getType().name())) {
                editor.edit(dobjs[0]);
                ClientUtils.setMembers(editorContainer, editor.getUI());
            } else {
                openUnsupportedEditor();
            }
        }
        openOptionalEditor(dobjs);
        //        editorContainer.show();
    }

    private void openOptionalEditor(DigitalObject... dobj) {
        if (optionalEditor == null) {
            return;
        }
        if (currentEditor != null && currentEditor.getType() == DatastreamEditorType.CHILDREN) {
            DigitalObject[] children = DigitalObject.toArray(getChildSelection());
            optionalEditor.open(children);
        } else {
            optionalEditor.open(dobj);
        }
    }

    private void openUnsupportedEditor() {
        if (currentEditor != null) {
            updateToolbar(currentEditor, null);
            currentEditor = null;
        }
        editorContainer.setMembers(getUnsupportedEditor());
    }

    private Label getUnsupportedEditor() {
        if (unsupportedEditor == null) {
            unsupportedEditor = new Label();
            unsupportedEditor.setContents(i18n.DigitalObjectEditor_UnsupportedEditor_Msg());
            unsupportedEditor.setIcon("[SKIN]/Dialog/warn.png");
            unsupportedEditor.setIconSize(2 * 16);
            unsupportedEditor.setLayoutAlign(Alignment.CENTER);
            unsupportedEditor.setLayoutAlign(VerticalAlignment.CENTER);
            unsupportedEditor.setHeight100();
            unsupportedEditor.setWidth100();
            unsupportedEditor.setAlign(Alignment.CENTER);
        }
        return unsupportedEditor;
    }

    @Override
    public void refresh() {
        if (currentEditor == null) {
            return;
        }
        Refreshable r = currentEditor.getEditor().getCapability(Refreshable.class);
        if (r != null) {
            r.refresh();
        }
    }

    @Override
    public Record[] getSelection() {
        return selection;
    }

    @Override
    public Record[] getChildSelection() {
        Record[] result = null;
        if (currentEditor != null) {
            ChildSelector selector = currentEditor.getEditor().getCapability(ChildSelector.class);
            if (selector != null) {
                result = selector.getChildSelection();
            }
        }
        return result;
    }

    private void updateToolbar(EditorDescriptor oldEditor, EditorDescriptor newEditor) {
        if (customToolbarSeparator == null) {
            initToolbar(toolbar, actionSource);
        }
        if (oldEditor == newEditor) {
            return;
        }
        if (oldEditor != null) {
            toolbar.removeMembers(oldEditor.getToolbarItems());
        }
        Canvas[] customToolbar = newEditor == null ? new Canvas[0] : newEditor.getToolbarItems();
        if (customToolbar.length > 0 && !(customToolbar[0] instanceof ToolStripSeparator)) {
            customToolbarSeparator.setVisible(true);
        } else {
            customToolbarSeparator.setVisible(false);
        }
        int addCustomIdx = toolbar.getMemberNumber(customToolbarSeparator);
        for (Canvas item : customToolbar) {
            toolbar.addMember(item, ++addCustomIdx);
        }
    }

    private ToolStrip initToolbar(ToolStrip t, ActionSource source) {
        RefreshAction refreshAction = new RefreshAction(i18n);
        DigitalObjectEditAction modsEditAction = new DigitalObjectEditAction(
                i18n.ImportBatchItemEditor_TabMods_Title(), i18n.DigitalObjectEditAction_Hint(), null,
                DatastreamEditorType.MODS,
                embeddedView ? new AcceptFilter(true, true) : new AcceptFilter(false, false), places);
        DigitalObjectEditAction ocrEditAction = new DigitalObjectEditAction(
                i18n.ImportBatchItemEditor_TabOcr_Title(), i18n.DigitalObjectEditAction_Hint(), null,
                DatastreamEditorType.OCR, places);
        DigitalObjectEditAction noteEditAction = new DigitalObjectEditAction(
                i18n.ImportBatchItemEditor_TabNote_Title(), i18n.ImportBatchItemEditor_TabNote_Hint(), null,
                DatastreamEditorType.NOTE, places);
        DigitalObjectEditAction parentEditAction = new DigitalObjectEditAction(
                i18n.DigitalObjectEditor_ParentAction_Title(), i18n.DigitalObjectEditAction_Hint(), null,
                DatastreamEditorType.PARENT,
                // support multiple selection just in case DigitalObjectEditor is embeded
                embeddedView ? new AcceptFilter(true, true) : new AcceptFilter(false, false), places);
        DigitalObjectEditAction mediaEditAction = new DigitalObjectEditAction(
                i18n.DigitalObjectEditor_MediaAction_Title(), i18n.DigitalObjectEditor_MediaAction_Hint(), null,
                DatastreamEditorType.MEDIA, places);
        DigitalObjectEditAction childrenEditAction = new DigitalObjectEditAction(
                i18n.DigitalObjectEditor_ChildrenAction_Title(), i18n.DigitalObjectEditor_ChildrenAction_Hint(),
                null, DatastreamEditorType.CHILDREN, places);
        DigitalObjectEditAction atmEditAction = new DigitalObjectEditAction(
                i18n.DigitalObjectEditor_AdministrationAction_Title(),
                i18n.DigitalObjectEditor_AdministrationAction_Hint(), null, DatastreamEditorType.ATM,
                new AcceptFilter(true, true), places);
        if (embeddedView) {
            Action actionMore = Actions.emptyAction(i18n.ActionsMenu_Title(), null, null);
            IconMenuButton actionsMenu = Actions.asIconMenuButton(actionMore, source);
            t.addMember(actionsMenu);
            Menu menu = Actions.createMenu();
            menu.addItem(Actions.asMenuItem(refreshAction, source, false));
            //            if (!optionalView) {
            menu.addItem(Actions.asMenuItem(modsEditAction, source, false));
            //            }
            menu.addItem(Actions.asMenuItem(noteEditAction, source, false));
            if (!importView/* && !optionalView*/) {
                menu.addItem(Actions.asMenuItem(parentEditAction, source, false));
            }
            if (!importView) {
                menu.addItem(Actions.asMenuItem(mediaEditAction, source, false));
            }
            menu.addItem(Actions.asMenuItem(ocrEditAction, source, false));
            menu.addItem(Actions.asMenuItem(atmEditAction, source, false));
            actionsMenu.setMenu(menu);
        } else {
            t.addMember(Actions.asIconButton(refreshAction, source));
            Action actionEditors = Actions.emptyAction(i18n.EditorsAction_Title(), "[SKIN]/actions/edit.png",
                    i18n.EditorsAction_Hint());
            IconMenuButton btnEditors = Actions.asIconMenuButton(actionEditors, source);
            Menu menuEditors = Actions.createMenu();
            menuEditors.addItem(Actions.asMenuItem(modsEditAction, source, false));
            menuEditors.addItem(Actions.asMenuItem(noteEditAction, source, false));
            menuEditors.addItem(Actions.asMenuItem(parentEditAction, source, false));
            menuEditors.addItem(Actions.asMenuItem(mediaEditAction, source, false));
            menuEditors.addItem(Actions.asMenuItem(ocrEditAction, source, false));
            menuEditors.addItem(Actions.asMenuItem(childrenEditAction, source, false));
            menuEditors.addItem(Actions.asMenuItem(atmEditAction, source, false));
            btnEditors.setMenu(menuEditors);
            t.addMember(btnEditors);
            DigitalObjectNavigateAction parentAction = DigitalObjectNavigateAction.parent(i18n, places);
            DigitalObjectNavigateAction childAction = DigitalObjectNavigateAction.child(i18n, places);
            DigitalObjectNavigateAction prevSiblingAction = DigitalObjectNavigateAction.previous(i18n, places);
            DigitalObjectNavigateAction nextSiblingAction = DigitalObjectNavigateAction.next(i18n, places);
            t.addMember(Actions.asIconButton(parentAction, source));
            t.addMember(Actions.asIconButton(childAction, source));
            t.addMember(Actions.asIconButton(prevSiblingAction, source));
            t.addMember(Actions.asIconButton(nextSiblingAction, source));
        }
        customToolbarSeparator = new ToolStripSeparator();
        customToolbarSeparator.setVisible(false);
        t.addMember(customToolbarSeparator);
        if (!embeddedView) {
            t.addFill();
            optionalEditorSwitch = Actions.asIconButton(new SwitchOptionalEditorAction(), source);
            optionalEditorSwitch.setSelected(false);
            optionalEditorSwitch.setActionType(SelectionType.CHECKBOX);
            optionalEditorSwitch.setShowSelectedIcon(true);
            t.addMember(optionalEditorSwitch);
        }
        return t;
    }

    private void switchOptionalEditor(boolean state) {
        if (optionalEditor == null || state == optionalEditor.isEnabled()) {
            return;
        }
        optionalEditorSwitch.setSelected(state);
        optionalEditor.setEnabled(optionalEditorSwitch.getSelected());
        if (state) {
            DigitalObject[] selection = DigitalObject.toArray(getSelection());
            openOptionalEditor(selection);
        }
    }

    /**
     * Use to customize editor for batch import items.
     */
    public void setImportView(boolean importView) {
        this.importView = importView;
    }

    /**
     * Use to customize editor as an embedded view.
     */
    public void setEmbeddedView(boolean embeddedView) {
        this.embeddedView = embeddedView;
    }

    /**
     * Use to customize editor as an optional view.
     */
    public void setOptionalView(boolean optionalView) {
        this.optionalView = optionalView;
    }

    private EditorDescriptor getDatastreamEditor(DatastreamEditorType type) {
        EditorDescriptor desc = editorCache.get(type);
        if (desc != null) {
            return desc;
        }
        DatastreamEditor deditor = null;
        String title = "";
        switch (type) {
        case OCR:
            title = i18n.ImportBatchItemEditor_TabOcr_Title();
            deditor = TextEditor.ocr(i18n);
            break;
        case NOTE:
            title = i18n.ImportBatchItemEditor_TabNote_Title();
            deditor = TextEditor.note(i18n);
            break;
        case MEDIA:
            title = i18n.DigitalObjectEditor_MediaEditor_Title();
            deditor = new MediaEditor(i18n);
            break;
        case MODS:
            title = i18n.ImportBatchItemEditor_TabMods_Title();
            deditor = new ModsMultiEditor(i18n);
            break;
        case PARENT:
            title = i18n.DigitalObjectEditor_ParentEditor_Title();
            deditor = new DigitalObjectParentEditor(i18n);
            break;
        case CHILDREN:
            title = i18n.DigitalObjectEditor_ChildrenEditor_Title();
            deditor = new DigitalObjectChildrenEditor(i18n, places, optionalEditor);
            break;
        case ATM:
            title = i18n.DigitalObjectEditor_AdministrationEditor_Title();
            deditor = new DigitalObjectAdministrationEditor(i18n);
            break;
        }
        title = ClientUtils.format("<b>%s</b>", title);
        desc = new EditorDescriptor(deditor, title, type);
        editorCache.put(type, desc);
        attachDatastreamEditor(deditor);
        return desc;
    }

    /**
     * Forwards editor events.
     */
    private void attachDatastreamEditor(DatastreamEditor deditor) {
        if (deditor instanceof HasEditorLoadHandlers) {
            ((HasEditorLoadHandlers) deditor).addEditorLoadHandler(new EditorLoadHandler() {

                @Override
                public void onEditorLoad(EditorLoadEvent evt) {
                    fireEvent(evt);
                }
            });
        }
    }

    private void setDescription(String editorTitle, String objectLabel, MetaModelRecord mr) {
        String content;
        if (mr != null) {
            // Editor Name - Model - Label
            String model = mr.getDisplayName();
            content = ClientUtils.format("%s - %s: %s", editorTitle, model, objectLabel);
        } else {
            // Editor Name - Label
            content = ClientUtils.format("%s: %s", editorTitle, objectLabel);
        }
        lblHeader.setContents(content);
    }

    private String getLabel(Record r) {
        return r == null ? "[ERROR]" : r.getAttribute(SearchDataSource.FIELD_LABEL);
    }

    @Override
    public HandlerRegistration addEditorLoadHandler(EditorLoadHandler handler) {
        return ensureHandlers().addHandler(EditorLoadEvent.TYPE, handler);
    }

    @Override
    public void fireEvent(GwtEvent<?> event) {
        if (handlerManager != null) {
            handlerManager.fireEvent(event);
        }
    }

    private HandlerManager ensureHandlers() {
        return handlerManager == null ? handlerManager = createHandlerManager() : handlerManager;
    }

    private HandlerManager createHandlerManager() {
        return new HandlerManager(this);
    }

    /** Holds already created editor and its toolbar */
    private static final class EditorDescriptor {

        private final DatastreamEditor editor;
        private final Canvas[] toolbarItems;
        private final String title;
        private final DatastreamEditorType type;

        EditorDescriptor(DatastreamEditor editor, String title, DatastreamEditorType type) {
            this.editor = editor;
            toolbarItems = editor.getToolbarItems();
            this.title = title;
            this.type = type;
        }

        public DatastreamEditor getEditor() {
            return editor;
        }

        public DatastreamEditorType getType() {
            return type;
        }

        public String getTitle() {
            return title;
        }

        public Canvas[] getToolbarItems() {
            return toolbarItems;
        }

    }

    /**
     * Opens editor when object description and model object are fetched.
     */
    private final class OpenEditorTask extends SweepTask implements Callback<ResultSet, Void> {

        private RecordList searchList;
        private ResultSet modelResultSet;
        private final String[] pids;
        private final Record[] digitalObjects;

        /**
         * It will fetch model and other attributes for each PID.
         */
        public OpenEditorTask(String[] pids) {
            this(null, pids);
        }

        /**
         * No fetch. It will use records as digital objects.
         */
        public OpenEditorTask(Record[] digitalObjects) {
            this(digitalObjects, null);
        }

        private OpenEditorTask(Record[] digitalObjects, String[] pids) {
            this.pids = pids;
            this.digitalObjects = digitalObjects;
        }

        public void start() {
            expect();
            if (pids != null) {
                initSearchList(pids);
            }
            initModels();
            release();
        }

        private void initModels() {
            expect();
            MetaModelDataSource.getModels(false, this);
        }

        private void initSearchList(String[] pids) {
            expect();
            SearchDataSource.getInstance().find(pids, new Callback<ResultSet, Void>() {

                @Override
                public void onFailure(Void reason) {
                    searchList = new RecordList();
                    release();
                }

                @Override
                public void onSuccess(ResultSet result) {
                    searchList = result;
                    release();
                }
            });
        }

        /**
         * Collects responses and opens editor.
         */
        @Override
        protected void processing() {
            Record[] records = processRecords();
            if (records == null) {
                return;
            }
            setSelection(records);
            openEditor();
        }

        private Record[] processRecords() {
            Record[] records;
            if (pids != null) {
                records = searchList.toArray();
                String error = checkSearchedRecordsConsistency(records);
                if (error != null) {
                    SC.warn(error);
                    places.goTo(Place.NOWHERE);
                    return null;
                }
            } else {
                records = digitalObjects;
            }
            return records;
        }

        private String checkSearchedRecordsConsistency(Record[] records) {
            String error = null;
            HashSet<String> pidSet = new HashSet<String>(Arrays.asList(pids));
            for (Record record : records) {
                String recordPid = record.getAttribute(SearchDataSource.FIELD_PID);
                if (!pidSet.remove(recordPid)) {
                    error = ClientUtils.format("PID %s not requested!", recordPid);
                    break;
                } else if (SearchDataSource.isDeleted(record)) {
                    error = ClientUtils.format("PID %s is deleted!", recordPid);
                    break;
                }
            }
            if (error == null && !pidSet.isEmpty()) {
                error = ClientUtils.format("PID %s not found!", pidSet.toString());
            }
            return error;
        }

        @Override
        public void onFailure(Void reason) {
            modelResultSet = new ResultSet();
            release();
        }

        @Override
        public void onSuccess(ResultSet result) {
            modelResultSet = result;
            release();
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            if (pids != null) {
                sb.append("pids: ").append(Arrays.toString(pids));
            } else {
                String[] recordPids = ClientUtils.toFieldValues(digitalObjects, SearchDataSource.FIELD_PID);
                sb.append("records: ").append(Arrays.toString(recordPids));
            }
            return super.toString();
        }
    }

    public static final class OptionalEditor {

        private final PlaceController embeddedPlaces;
        private boolean editorEnabled;

        private OptionalEditor(ClientMessages i18n, Layout previewContainer) {
            SimpleEventBus eventBus = new SimpleEventBus();
            embeddedPlaces = new PlaceController(eventBus);
            DigitalObjectEditor embeddedEditor = new DigitalObjectEditor(i18n, embeddedPlaces, true);
            embeddedEditor.setOptionalView(true);
            ActivityManager activityManager = new ActivityManager(new ChildActivities(embeddedEditor), eventBus);
            activityManager.setDisplay(new ChildEditorDisplay(previewContainer));
        }

        public void open(DigitalObject... objects) {
            open(null, objects);
        }

        public void open(DatastreamEditorType editor, DigitalObject... objects) {
            if (!editorEnabled) {
                return;
            }
            DigitalObject openObject = findRecentSelection(objects);
            if (openObject == null) {
                embeddedPlaces.goTo(Place.NOWHERE);
                return;
            }
            //            LOG.log(Level.SEVERE, "# openOptionalEditor: " + objects.length, new IllegalStateException(openObject.toString()));
            embeddedPlaces.goTo(new DigitalObjectEditorPlace(editor, openObject));
        }

        private DigitalObject findRecentSelection(DigitalObject... objects) {
            if (objects == null || objects.length == 0 || objects[0] == null) {
                return null;
            } else if (objects.length == 1) {
                return objects[0];
            }
            for (DigitalObject object : objects) {
                Record record = object.getRecord();
                Boolean isLastSelection = record
                        .getAttributeAsBoolean(DigitalObjectChildrenEditor.LAST_CLICKED_ATTR);
                if (isLastSelection != null && isLastSelection) {
                    return object;
                }
            }
            return null;
        }

        public boolean isEnabled() {
            return editorEnabled;
        }

        public void setEnabled(boolean editorEnabled) {
            this.editorEnabled = editorEnabled;
        }
    }

    private final class SwitchOptionalEditorAction extends AbstractAction {

        public SwitchOptionalEditorAction() {
            super(i18n.DigitalObjectEditor_OptionalEditorAction_Title(), null,
                    i18n.DigitalObjectEditor_OptionalEditorAction_Hint());
        }

        @Override
        public void performAction(ActionEvent event) {
            boolean visible = optionalEditorContainer.isVisible();
            // fires VisibilityChangedEvent
            // see optionalEditorContainer.addVisibilityChangedHandler in initOptionalEditor
            optionalEditorContainer.setVisible(!visible);
        }

    }

}