org.carrot2.workbench.core.ui.SearchInputView.java Source code

Java tutorial

Introduction

Here is the source code for org.carrot2.workbench.core.ui.SearchInputView.java

Source

/*
 * Carrot2 project.
 *
 * Copyright (C) 2002-2016, Dawid Weiss, Stanisaw Osiski.
 * All rights reserved.
 *
 * Refer to the full license file "carrot2.LICENSE"
 * in the root folder of the repository checkout or at:
 * http://www.carrot2.org/carrot2.LICENSE
 */

package org.carrot2.workbench.core.ui;

import java.io.IOException;
import java.util.*;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.carrot2.core.*;
import org.carrot2.core.attribute.Processing;
import org.carrot2.util.attribute.*;
import org.carrot2.util.attribute.BindableDescriptor.GroupingMethod;
import org.carrot2.workbench.core.WorkbenchCorePlugin;
import org.carrot2.workbench.core.helpers.*;
import org.carrot2.workbench.core.preferences.PreferenceConstants;
import org.carrot2.workbench.core.ui.actions.ActiveSearchEditorActionDelegate;
import org.carrot2.workbench.core.ui.actions.GroupingMethodAction;
import org.carrot2.workbench.core.ui.widgets.CScrolledComposite;
import org.carrot2.workbench.editors.AttributeEvent;
import org.carrot2.workbench.editors.AttributeListenerAdapter;
import org.eclipse.core.commands.operations.OperationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.action.*;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.*;
import org.eclipse.ui.part.ViewPart;

import org.carrot2.shaded.guava.common.base.Predicate;
import org.carrot2.shaded.guava.common.base.Predicates;
import org.carrot2.shaded.guava.common.collect.Lists;
import org.carrot2.shaded.guava.common.collect.Maps;

/**
 * The search view defines a combination of source, algorithm and required input
 * parameters required to open a new editor.
 */
public class SearchInputView extends ViewPart {
    /**
     * Public identifier of this view.
     */
    public static final String ID = "org.carrot2.workbench.core.views.search";

    /**
     * Filter showing only required attributes.
     */
    private final static Predicate<AttributeDescriptor> SHOW_REQUIRED = new AnnotationsPredicate(false,
            Required.class);

    /**
     * Filter showing all attributes.
     */
    private final static Predicate<AttributeDescriptor> SHOW_ALL = Predicates.alwaysTrue();

    /**
     * State persistence.
     */
    private SearchInputViewMemento state;

    private ComboViewer sourceViewer;
    private ComboViewer algorithmViewer;
    private Button processButton;

    /**
     * Scroller composite container.
     */
    private CScrolledComposite scroller;

    /**
     * A composite holding holding an {@link AttributeGroups} (editors for the current
     * combination of input/ source).
     */
    private Composite editorCompositeContainer;

    /**
     * The current editor composite.
     */
    private AttributeGroups attributeGroups;

    /**
     * A joint set of attributes for all sources from and default attribute values for
     * algorithms.
     */
    private final AttributeValueSet attributes = new AttributeValueSet("global");

    /**
     * A map of {@link BindableDescriptor} for each document source ID and
     * algorithm ID from {@link WorkbenchCorePlugin#getComponentSuite()}.
     */
    private Map<String, BindableDescriptor> descriptors = Maps.newHashMap();

    /**
     * All {@link DocumentSourceDescriptor}s related to {@link IDocumentSource}s in
     * {@link #sourceViewer}.
     */
    private HashMap<String, DocumentSourceDescriptor> sources;

    /**
     * All {@link ProcessingComponentDescriptor}s related to {@link IClusteringAlgorithm}s
     * in {@link #algorithmViewer}.
     */
    private HashMap<String, ProcessingComponentDescriptor> algorithms;

    /**
     * Link the GUI with currently selected editor.
     */
    private boolean linkWithEditor = false;

    /**
     * Selection listener on {@link #sourceViewer}.
     */
    private ISelectionChangedListener sourceSelectionListener = new ISelectionChangedListener() {
        public void selectionChanged(SelectionChangedEvent event) {
            final DocumentSourceDescriptor impl = (DocumentSourceDescriptor) ((IStructuredSelection) event
                    .getSelection()).getFirstElement();
            setSourceId(impl.getId());
        }
    };

    /*
     * 
     */
    private IAction linkWithEditorAction;

    /**
     * Validation validationStatus.
     */
    private CLabel validationStatus;

    /**
     * If <code>true</code>, validation validationStatus is immediately propagated to
     * the user interface (this happens after the first click on {@link #processButton}.
     */
    private boolean showValidationStatus;

    /*
     * 
     */
    private Image errorStatusImage = WorkbenchCorePlugin.getImageDescriptor("icons/error.gif").createImage();

    /**
     * Link the GUI of {@link SearchInputView} with the currently active
     * {@link SearchEditor}.
     */
    private class LinkWithEditorActionDelegate extends ActiveSearchEditorActionDelegate {
        /* */
        @Override
        protected void run(SearchEditor editor) {
            linkWithEditor = !linkWithEditor;

            super.getAction().setChecked(linkWithEditor);
            if (linkWithEditor) {
                if (isEnabled(getEditor())) {
                    linkWith((SearchEditor) getEditor());
                }
            }
        }

        /**
         * Is this action enabled for the given editor?
         */
        protected boolean isEnabled(IEditorPart activeEditor) {
            return activeEditor != null && activeEditor instanceof SearchEditor;
        }

        /**
         * Detect editor switch.
         */
        @Override
        protected void switchingEditors(IEditorPart previous, IEditorPart activeEditor) {
            super.switchingEditors(previous, activeEditor);

            if (activeEditor != null && linkWithEditor) {
                final SearchEditor editor = (SearchEditor) activeEditor;
                linkWith(editor);
            }
        }

        /**
         * Synchronize the view with the given editor.
         */
        private void linkWith(SearchEditor editor) {
            final SearchInput input = editor.getSearchResult().getInput();

            setAlgorithmId(input.getAlgorithmId());
            attributes.setAttributeValues(input.getAttributeValueSet().getAttributeValues());
            setSourceId(input.getSourceId());
        }
    }

    /**
     * Toggles between {@link SearchInputView#SHOW_REQUIRED} and
     * {@link SearchInputView#SHOW_ALL}.
     */
    private static class ShowOptionalAction extends Action {
        public ShowOptionalAction() {
            super("Show optional attributes", SWT.TOGGLE);

            setImageDescriptor(WorkbenchCorePlugin.getImageDescriptor("icons/optional.gif"));

            /*
             * Subscribe to change events on the global preference property and update
             * initial state.
             */
            final IPreferenceStore preferenceStore = WorkbenchCorePlugin.getDefault().getPreferenceStore();
            preferenceStore.addPropertyChangeListener(new IPropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent event) {
                    if (PreferenceConstants.SHOW_OPTIONAL.equals(event.getProperty())) {
                        updateState();
                    }
                }
            });

            updateState();
        }

        /*
         * Update selection state.
         */
        private void updateState() {
            final boolean state = WorkbenchCorePlugin.getDefault().getPreferenceStore()
                    .getBoolean(PreferenceConstants.SHOW_OPTIONAL);

            setChecked(state);
        }

        @Override
        public void run() {
            final IPreferenceStore preferenceStore = WorkbenchCorePlugin.getDefault().getPreferenceStore();

            final boolean state = preferenceStore.getBoolean(PreferenceConstants.SHOW_OPTIONAL);

            preferenceStore.setValue(PreferenceConstants.SHOW_OPTIONAL, !state);
        }
    }

    /*
     * 
     */
    @Override
    public void init(IViewSite site) throws PartInitException {
        super.init(site);

        /*
         * Create toolbar and menu contributions.
         */
        final IActionBars bars = getViewSite().getActionBars();
        createToolbar(bars.getToolBarManager());
        bars.updateActionBars();
    }

    /*
     * 
     */
    @Override
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);

        this.state = null;
        try {
            if (memento != null) {
                this.state = SimpleXmlMemento.getChild(SearchInputViewMemento.class, memento);
            }
        } catch (IOException e) {
            Utils.logError(e, false);
            this.state = null;
        }
    }

    /*
     * 
     */
    private void createToolbar(IToolBarManager toolBarManager) {
        // Link with editor action.
        final IAction linkWithEditor = new ActionDelegateProxy(new LinkWithEditorActionDelegate(),
                IAction.AS_CHECK_BOX);
        linkWithEditor.setImageDescriptor(WorkbenchCorePlugin.getImageDescriptor("icons/link_e.gif"));
        linkWithEditor.setToolTipText("Link the interface with current editor");
        toolBarManager.add(linkWithEditor);
        this.linkWithEditorAction = linkWithEditor;

        // Optional attributes action toggle.
        final IAction showRequiredOnly = new ShowOptionalAction();
        toolBarManager.add(showRequiredOnly);

        // Grouping method action.
        toolBarManager.add(new GroupingMethodAction(PreferenceConstants.GROUPING_INPUT_VIEW));

        // Add save attributes action.
        toolBarManager.add(new SaveDocumentSourceAttributesAction(this));
    }

    /**
     * Create user interface for the view.
     */
    @Override
    public void createPartControl(Composite parent) {
        /*
         * Create GUI components.
         */
        createComponents(parent);

        /*
         * Hook processing event to the processing button and on traversal (when all
         * attributes are given).
         */
        processButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                final String source = getSourceId();
                if (hasAllRequiredAttributes(source)) {
                    fireProcessing();
                } else {
                    showValidationStatus = true;
                    checkAllRequiredAttributes();
                }
            }
        });

        parent.addTraverseListener(new TraverseListener() {
            public void keyTraversed(TraverseEvent e) {
                if (e.detail == SWT.TRAVERSE_RETURN) {
                    /*
                     * Consume the traversal event, spawn a new editor.
                     */
                    e.doit = false;
                    e.detail = SWT.TRAVERSE_NONE;

                    fireProcessing();
                }
            }
        });

        /*
         * Hook global preference updates.
         */
        final IPreferenceStore preferenceStore = WorkbenchCorePlugin.getDefault().getPreferenceStore();

        preferenceStore
                .addPropertyChangeListener(new PropertyChangeListenerAdapter(PreferenceConstants.SHOW_OPTIONAL) {
                    public void propertyChangeFiltered(PropertyChangeEvent event) {
                        displayEditorSet();
                    }
                });

        preferenceStore.addPropertyChangeListener(
                new PropertyChangeListenerAdapter(PreferenceConstants.GROUPING_INPUT_VIEW) {
                    public void propertyChangeFiltered(PropertyChangeEvent event) {
                        displayEditorSet();
                    }
                });
    }

    /**
     * Display a new composite with editors for the current combination of the source,
     * algorithm and grouping/ filtering flags.
     */
    private void displayEditorSet() {
        final IPreferenceStore preferenceStore = WorkbenchCorePlugin.getDefault().getPreferenceStore();

        final Predicate<AttributeDescriptor> filter;
        if (preferenceStore.getBoolean(PreferenceConstants.SHOW_OPTIONAL)) {
            filter = SHOW_ALL;
        } else {
            filter = SHOW_REQUIRED;
        }

        this.editorCompositeContainer.setRedraw(false);

        /*
         * Dispose of last editor.
         */
        Map<String, Boolean> expansionState = Collections.emptyMap();
        if (this.attributeGroups != null) {
            expansionState = this.attributeGroups.getExpansionStates();
            this.attributeGroups.dispose();
            this.attributeGroups = null;
        }

        /*
         * Create a new editor set.
         */
        final IPreferenceStore prefStore = WorkbenchCorePlugin.getDefault().getPreferenceStore();

        final String sourceID = getSourceId();
        final GroupingMethod groupingMethod = GroupingMethod
                .valueOf(prefStore.getString(PreferenceConstants.GROUPING_INPUT_VIEW));
        final BindableDescriptor sourceDescriptor = this.descriptors.get(sourceID);

        if (StringUtils.isEmpty(getSourceId())) {
            final Label label = new Label(editorCompositeContainer, SWT.CENTER);
            label.setText("No active sources");
            final GridData gd = new GridData();
            gd.horizontalAlignment = org.eclipse.swt.layout.GridData.FILL;
            gd.grabExcessHorizontalSpace = true;
            label.setLayoutData(gd);
        } else {
            this.attributeGroups = new AttributeGroups(editorCompositeContainer, sourceDescriptor, groupingMethod,
                    filter, attributes.getAttributeValues());

            final GridData gd = new GridData();
            gd.horizontalAlignment = org.eclipse.swt.layout.GridData.FILL;
            gd.grabExcessHorizontalSpace = true;
            attributeGroups.setLayoutData(gd);
            attributeGroups.setExpanded(expansionState);

            /*
             * Set default values for those attributes that do not have any assigned.
             */
            Map<String, Object> defaultValues = sourceDescriptor.getDefaultValues();
            for (Map.Entry<String, Object> e : defaultValues.entrySet()) {
                if (attributes.getAttributeValue(e.getKey()) == null) {
                    attributes.setAttributeValue(e.getKey(), e.getValue());
                }
            }

            /*
             * Set initial values of editors.
             */
            attributeGroups.setAttributes(filterAttributesOf(sourceID));

            /*
             * Hook up listeners updating attributes on changes in editors.
             */
            attributeGroups.addAttributeListener(new AttributeListenerAdapter() {
                public void valueChanged(AttributeEvent event) {
                    if (event.key.equals(AttributeList.ENABLE_VALIDATION_OVERLAYS)) {
                        return;
                    }

                    attributes.setAttributeValue(event.key, event.value);
                    checkAllRequiredAttributes();
                }

                public void valueChanging(AttributeEvent event) {
                    /*
                     * On content changing, eagerly substitute the value of the given
                     * attribute with the new value. In the input view, early commit of
                     * attribute values should not trigger any additional consequences, so
                     * we can do it.
                     */
                    valueChanged(event);
                }
            });
        }

        /*
         * Redraw GUI.
         */
        this.editorCompositeContainer.setRedraw(true);
        this.editorCompositeContainer.layout(true);

        checkAllRequiredAttributes();
        scroller.reflow(true);
    }

    /**
     * Creates permanent GUI elements (source, algorithm combos, placeholder for the
     * editors).
     */
    private void createComponents(Composite parent) {
        final WorkbenchCorePlugin core = WorkbenchCorePlugin.getDefault();
        parent.setLayout(new FillLayout());

        this.scroller = new CScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
        scroller.setExpandHorizontal(true);
        scroller.setExpandVertical(false);

        final Composite innerComposite = GUIFactory.createSpacer(scroller);
        final GridLayout gridLayout = (GridLayout) innerComposite.getLayout();
        gridLayout.numColumns = 2;
        gridLayout.makeColumnsEqualWidth = false;

        scroller.setContent(innerComposite);

        // Initialize sources, descriptors and source combo.
        final ProcessingComponentSuite suite = core.getComponentSuite();

        sourceViewer = createComboViewer(innerComposite, "Source", suite.getSources());
        sourceViewer.addSelectionChangedListener(sourceSelectionListener);

        sources = Maps.newHashMap();
        for (DocumentSourceDescriptor e : suite.getSources()) {
            try {
                sources.put(e.getId(), e);
                addFilteredDescriptor(e);
            } catch (Exception x) {
                Utils.logError("Could not initialize source: " + e.getId(), false);
            }
        }

        // Initialize algorithms and algorithm combo.
        algorithmViewer = createComboViewer(innerComposite, "Algorithm", suite.getAlgorithms());

        algorithms = Maps.newHashMap();
        for (ProcessingComponentDescriptor e : suite.getAlgorithms()) {
            algorithms.put(e.getId(), e);
            addFilteredDescriptor(e);
        }

        final Label l = new Label(innerComposite, SWT.SEPARATOR | SWT.HORIZONTAL);
        l.setLayoutData(GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(2, 1)
                .minSize(SWT.DEFAULT, 10).create());

        // Initialize a place holder for the editors.
        this.editorCompositeContainer = new Composite(innerComposite, SWT.NONE);
        this.editorCompositeContainer.setLayoutData(GridDataFactory.fillDefaults().span(2, 1).create());
        this.editorCompositeContainer.setLayout(GridLayoutFactory.fillDefaults().create());

        final Composite processStatus = new Composite(innerComposite, SWT.NONE);
        processStatus.setLayoutData(GridDataFactory.fillDefaults().span(2, 1).grab(true, false).create());
        processStatus.setLayout(GridLayoutFactory.fillDefaults().numColumns(2).spacing(3, 0).create());

        this.validationStatus = new CLabel(processStatus, SWT.LEFT);
        validationStatus
                .setLayoutData(GridDataFactory.fillDefaults().hint(250, SWT.DEFAULT).grab(true, false).create());

        // Initialize process button.
        processButton = new Button(processStatus, SWT.PUSH);
        processButton.setText("Process");

        final GridData processButtonGridData = new GridData();
        processButtonGridData.horizontalAlignment = GridData.END;
        processButtonGridData.verticalAlignment = GridData.END;
        processButtonGridData.horizontalSpan = 1;
        processButton.setLayoutData(processButtonGridData);

        // Restore view state.
        restoreState();

        // Initial editor display for the current input.
        displayEditorSet();

        // Restore initial expansion state for groups.
        if (attributeGroups != null) {
            if (state != null) {
                if (state.sectionsExpansionState != null) {
                    this.attributeGroups.setExpanded(state.sectionsExpansionState);
                }
            } else {
                // Set the default expansion state.
                this.attributeGroups.setExpanded(false);
                this.attributeGroups.setExpanded(AttributeLevel.BASIC.toString(), true);
            }
        }
    }

    /**
     * Adds a {@link BindableDescriptor} to {@link #descriptors}, filtering
     * to only {@link Input} and {@link Processing} attributes.
     */
    private void addFilteredDescriptor(ProcessingComponentDescriptor e) {
        final WorkbenchCorePlugin core = WorkbenchCorePlugin.getDefault();

        descriptors.put(e.getId(), core.getComponentDescriptor(e.getId()).only(Input.class, Processing.class));
    }

    /**
     * Creates a JFace ComboViewer around a collection of extension point implementations.
     */
    private ComboViewer createComboViewer(Composite parent, String comboLabel,
            List<? extends ProcessingComponentDescriptor> components) {
        final Label label = new Label(parent, SWT.CENTER);
        label.setLayoutData(new GridData());
        label.setText(comboLabel);

        final GridData gridData = new GridData();
        gridData.horizontalAlignment = GridData.FILL;
        gridData.grabExcessHorizontalSpace = true;

        final Combo combo = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER);
        combo.setLayoutData(gridData);
        combo.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));

        final ComboViewer viewer = new ComboViewer(combo);
        viewer.setLabelProvider(new LabelProvider() {
            public String getText(Object element) {
                return ((ProcessingComponentDescriptor) element).getLabel();
            }
        });

        viewer.setComparator(new ViewerComparator(new Comparator<String>() {
            public int compare(String s1, String s2) {
                return s1.toLowerCase().compareTo(s2.toLowerCase());
            }
        }));

        viewer.setContentProvider(new ArrayContentProvider());
        viewer.setInput(components);

        return viewer;
    }

    /**
     * Initiate query processing. Open an editor with the current parameter values.
     */
    private void fireProcessing() {
        if (!hasAllRequiredAttributes(getSourceId())) {
            return;
        }

        final IWorkbenchPage page = getViewSite().getWorkbenchWindow().getActivePage();

        /*
         * Clone current attribute values so that they can be freely modified in the
         * editor and in the input view.
         */
        final AttributeValueSet requestAttrs = new AttributeValueSet("request");
        requestAttrs.setAttributeValues(filterAttributesOf(getSourceId()));
        requestAttrs.setAttributeValues(filterAttributesOf(getAlgorithmId()));

        final SearchInput input = new SearchInput(getSourceId(), getAlgorithmId(), requestAttrs);
        try {
            page.openEditor(input, SearchEditor.ID);
        } catch (Exception x) {
            final IStatus status = new OperationStatus(IStatus.ERROR, WorkbenchCorePlugin.PLUGIN_ID, -2,
                    "Editor could not be opened.", x);
            Utils.showError(status);
        }
    }

    /**
     * Check if all required {@link Input} attributes of a given source are properly
     * initialized.
     */
    private boolean hasAllRequiredAttributes(String sourceId) {
        if (StringUtils.isEmpty(sourceId)) {
            return false;
        }

        return getEmptyRequiredAttributes(sourceId).isEmpty();
    }

    /**
     * Returns descriptors of all required attributes that still have no values or have
     * invalid values.
     */
    private Collection<AttributeDescriptor> getEmptyRequiredAttributes(String sourceId) {
        final Collection<AttributeDescriptor> desc = descriptors.get(sourceId).flatten().attributeDescriptors
                .values();

        final ArrayList<AttributeDescriptor> remaining = Lists.newArrayList();
        for (AttributeDescriptor d : desc) {
            final Object value = attributes.getAttributeValue(d.key);

            if (!d.isValid(value)) {
                remaining.add(d);
            }
        }

        return remaining;
    }

    /**
     * Check if all required attributes for the current configuration are available and
     * update the {@link #processButton}.
     */
    private void checkAllRequiredAttributes() {
        if (!showValidationStatus) {
            this.validationStatus.setText("");
            this.validationStatus.setImage(null);
        } else {
            attributeGroups.setAttribute(AttributeList.ENABLE_VALIDATION_OVERLAYS, true);

            final String source = getSourceId();
            if (!StringUtils.isEmpty(source)) {
                final Collection<AttributeDescriptor> remaining = getEmptyRequiredAttributes(source);
                if (remaining.size() > 0) {
                    final String firstBad = remaining.iterator().next().metadata.getLabelOrTitle();
                    validationStatus.setText("Invalid attribute value: " + firstBad);
                    validationStatus.setImage(errorStatusImage);
                } else {
                    this.validationStatus.setText("");
                    this.validationStatus.setImage(null);
                }
            }
        }
    }

    /**
     * Restore state of UI components from saved state.
     */
    private void restoreState() {
        if (state != null) {
            this.attributes.setAttributeValues(state.attributes.getAttributeValues());
            this.linkWithEditor = state.linkWithEditor;
        }
        this.linkWithEditorAction.setChecked(linkWithEditor);

        ProcessingComponentDescriptor source = null;
        ProcessingComponentDescriptor algorithm = null;

        if (state != null) {
            source = sources.get(state.sourceId);
            algorithm = algorithms.get(state.algorithmId);
        }

        if (source == null) {
            // Try to select the default set in preferences.
            String id = WorkbenchCorePlugin.getDefault().getPreferenceStore()
                    .getString(PreferenceConstants.DEFAULT_SOURCE_ID);
            source = sources.get(id);
        }

        if (algorithm == null) {
            // Try to select the default set in preferences.
            String id = WorkbenchCorePlugin.getDefault().getPreferenceStore()
                    .getString(PreferenceConstants.DEFAULT_ALGORITHM_ID);
            algorithm = algorithms.get(id);
        }

        restoreState(sourceViewer, source);
        restoreState(algorithmViewer, algorithm);

        /*
         * Disable GUI if no inputs or algorithms.
         */
        if (sources.isEmpty()) {
            disableComboWithMessage(sourceViewer.getCombo(), "No document sources.");
            processButton.setEnabled(false);
        }

        if (algorithms.isEmpty()) {
            disableComboWithMessage(algorithmViewer.getCombo(), "No clustering algorithms.");
            processButton.setEnabled(false);
        }
    }

    /**
     * Attempt to set selection to the given descriptor, if failed, select the first
     * available element. 
     */
    @SuppressWarnings("unchecked")
    private void restoreState(ComboViewer combo, ProcessingComponentDescriptor descriptor) {
        if (descriptor == null) {
            Collection<ProcessingComponentDescriptor> options = (Collection<ProcessingComponentDescriptor>) combo
                    .getInput();

            if (options.size() > 0) {
                descriptor = options.iterator().next();
            }
        }

        if (descriptor != null) {
            combo.setSelection(new StructuredSelection(descriptor), true);
        }
    }

    /*
     *
     */
    private void disableComboWithMessage(Combo toDisable, String message) {
        toDisable.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
        toDisable.setToolTipText(message);
        toDisable.setEnabled(false);
    }

    /**
     * Return the current source component identifier.
     */
    String getSourceId() {
        return getSelectedId(sourceViewer);
    }

    /**
     * Switch to user interface to the given source.
     */
    private void setSourceId(String sourceID) {
        sourceViewer.removeSelectionChangedListener(sourceSelectionListener);
        sourceViewer.setSelection(new StructuredSelection(sources.get(sourceID)));
        sourceViewer.addSelectionChangedListener(sourceSelectionListener);
        showValidationStatus = false;

        displayEditorSet();
    }

    /*
     * 
     */
    private String getAlgorithmId() {
        return getSelectedId(algorithmViewer);
    }

    /*
     * 
     */
    private void setAlgorithmId(String algorithmID) {
        algorithmViewer.setSelection(new StructuredSelection(algorithms.get(algorithmID)));
    }

    /**
     * 
     */
    private String getSelectedId(ComboViewer combo) {
        if (combo.getSelection().isEmpty()) {
            return null;
        }

        final IStructuredSelection selection = ((IStructuredSelection) combo.getSelection());
        return ((ProcessingComponentDescriptor) selection.getFirstElement()).getId();
    }

    /**
     * Filter only those keys from {@link #attributes} that belong to source
     * <code>sourceID</code>.
     */
    Map<String, Object> filterAttributesOf(String componentId) {
        final Map<String, Object> filtered = Maps.newLinkedHashMap(attributes.getAttributeValues());

        filtered.keySet().retainAll(descriptors.get(componentId).attributeDescriptors.keySet());

        return filtered;
    }

    /**
     * Update an attribute stored in the input view. 
     */
    void setAttribute(String key, Object value) {
        /*
         * Force update of attributes map directly because this method
         * updates algorithm attributes that are not covered by editorComposite.
         */
        this.attributes.setAttributeValue(key, value);
        this.attributeGroups.setAttribute(key, value);
    }

    /*
     * 
     */
    @Override
    public void saveState(IMemento memento) {
        final SearchInputViewMemento state = new SearchInputViewMemento();

        state.sourceId = getSourceId();
        state.algorithmId = getAlgorithmId();
        state.linkWithEditor = linkWithEditor;
        state.attributes = attributes;

        if (attributeGroups != null) {
            state.sectionsExpansionState = attributeGroups.getExpansionStates();
        }

        try {
            SimpleXmlMemento.addChild(memento, state);
        } catch (IOException e) {
            Utils.logError(e, false);
        }
    }

    /**
     * We set the focus to the current {@link #attributeGroups}'s default element if
     * possible. Otherwise, set the focus to the input source combo.
     */
    @Override
    public void setFocus() {
        if (attributeGroups != null) {
            attributeGroups.setFocus();
        } else {
            this.sourceViewer.getCombo().setFocus();
        }
    }

    /*
     * 
     */
    @Override
    public void dispose() {
        if (this.attributeGroups != null)
            attributeGroups.dispose();
        if (this.errorStatusImage != null)
            this.errorStatusImage.dispose();

        super.dispose();
    }

    /**
     * Returns the active page's view instance.
     */
    public static SearchInputView getView() {
        final IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();

        return (SearchInputView) page.findView(SearchInputView.ID);
    }
}