org.eclipse.scanning.device.ui.model.ModelFieldEditorFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.scanning.device.ui.model.ModelFieldEditorFactory.java

Source

/*-
 *******************************************************************************
 * Copyright (c) 2011, 2016 Diamond Light Source Ltd.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Matthew Gerring - initial API and implementation and/or initial documentation
 *******************************************************************************/
package org.eclipse.scanning.device.ui.model;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.dawnsci.analysis.api.expressions.IExpressionEngine;
import org.eclipse.dawnsci.analysis.api.expressions.IExpressionService;
import org.eclipse.dawnsci.analysis.api.roi.IROI;
import org.eclipse.jface.fieldassist.ContentProposal;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckboxCellEditor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.window.DefaultToolTip;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.richbeans.widgets.cell.CComboCellEditor;
import org.eclipse.richbeans.widgets.cell.CComboWithEntryCellEditor;
import org.eclipse.richbeans.widgets.cell.CComboWithEntryCellEditorData;
import org.eclipse.richbeans.widgets.cell.LongStringCellEditor;
import org.eclipse.richbeans.widgets.cell.NumberCellEditor;
import org.eclipse.richbeans.widgets.decorator.RegexDecorator;
import org.eclipse.richbeans.widgets.file.FileDialogCellEditor;
import org.eclipse.richbeans.widgets.table.TextCellEditorWithContentProposal;
import org.eclipse.scanning.api.IScannable;
import org.eclipse.scanning.api.annotation.ui.DeviceType;
import org.eclipse.scanning.api.annotation.ui.EditType;
import org.eclipse.scanning.api.annotation.ui.FieldDescriptor;
import org.eclipse.scanning.api.annotation.ui.FieldUtils;
import org.eclipse.scanning.api.annotation.ui.FieldValue;
import org.eclipse.scanning.api.annotation.ui.FileType;
import org.eclipse.scanning.api.device.IRunnableDeviceService;
import org.eclipse.scanning.api.device.IScannableDeviceService;
import org.eclipse.scanning.api.event.scan.DeviceInformation;
import org.eclipse.scanning.api.filter.IFilterService;
import org.eclipse.scanning.api.scan.ScanningException;
import org.eclipse.scanning.device.ui.ServiceHolder;
import org.eclipse.scanning.device.ui.util.PageUtil;
import org.eclipse.scanning.device.ui.util.SortNatural;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * Factory for creating editors for FieldValue
 * 
 * @author Matthew Gerring
 *
 */
public class ModelFieldEditorFactory {

    private static final Logger logger = LoggerFactory.getLogger(ModelFieldEditorFactory.class);

    private static ISelectionListener selectionListener;
    private static ToolTip currentHint;
    private IScannableDeviceService cservice;
    private IRunnableDeviceService dservice;

    private ColumnLabelProvider labelProvider;

    public ModelFieldEditorFactory() {
        try {
            cservice = ServiceHolder.getRemote(IScannableDeviceService.class);
            dservice = ServiceHolder.getRemote(IRunnableDeviceService.class);
        } catch (Exception e) {
            logger.error("Cannot get remote services!", e);
        }
    }

    public ModelFieldEditorFactory(ColumnLabelProvider labelProvider) {
        this();
        this.labelProvider = labelProvider;
    }

    public void dispose() {

    }

    /**
     * Create a new editor for a field.
     * @param field
     * 
     * @return null if the field is not editable.
     * @throws ScanningException 
     */
    public CellEditor createEditor(FieldValue field, Composite parent) throws ScanningException {

        Object value;
        try {
            value = field.get();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

        Class<? extends Object> clazz = null;
        if (value != null) {
            clazz = value.getClass();
        } else {
            try {
                clazz = field.getType();
            } catch (NoSuchFieldException | SecurityException e) {
                e.printStackTrace();
            }

        }

        CellEditor ed = null;
        final FieldDescriptor anot = field.getAnnotation();
        if (!isEnabled(field.getModel(), anot))
            return null;

        if (clazz == Boolean.class) {
            ed = new CheckboxCellEditor(parent, SWT.NONE);

        } else if (anot != null && anot.edit() == EditType.COMPOUND) {
            ed = new ModelCellEditor(parent, field, labelProvider);

        } else if (Number.class.isAssignableFrom(clazz) || isNumberArray(clazz)) {
            ed = getNumberEditor(field, clazz, parent);

        } else if (IROI.class.isAssignableFrom(clazz)) {
            throw new IllegalArgumentException("Have not ported RegionCellEditor to scanning yet!");
            // TODO FIXME Need way of editing regions.
            //ed = new RegionCellEditor(parent);

        } else if (Enum.class.isAssignableFrom(clazz)) {
            ed = getChoiceEditor((Class<? extends Enum>) clazz, parent);

        } else if (CComboWithEntryCellEditorData.class.isAssignableFrom(clazz)) {
            ed = getChoiceWithEntryEditor((CComboWithEntryCellEditorData) value, parent);

        } else if (FileDialogCellEditor.isEditorFor(clazz) || (anot != null && anot.file() != FileType.NONE)) {
            FileDialogCellEditor fe = new FileDialogCellEditor(parent);
            fe.setValueClass(clazz);
            ed = fe;
            if (anot != null) {
                fe.setDirectory(anot.file().isDirectory());
                fe.setNewFile(anot.file().isNewFile());
            }
        } else if (String.class.equals(clazz) && anot != null && anot.device() != DeviceType.NONE) {
            ed = getDeviceEditor(anot.device(), parent);

        } else if (String.class.equals(clazz) && anot != null && anot.dataset() != null
                && !anot.dataset().isEmpty()) {
            ed = getDatasetEditor(field, parent);

        } else if (String.class.equals(clazz) && anot != null && anot.edit() == EditType.LONG) {
            ed = getLongTextEditor(parent, anot);

        } else if (String.class.equals(clazz)) {
            ed = getSimpleTextEditor(parent, anot);
        }

        // Show the tooltip, if there is one
        if (ed != null) {
            if (anot != null) {
                String hint = anot.hint();
                if (hint != null && !"".equals(hint)) {
                    showHint(hint, parent);
                }
            }
        }

        return ed;

    }

    private CellEditor getLongTextEditor(Composite parent, FieldDescriptor anot) {
        return new LongStringCellEditor(parent, labelProvider);
    }

    private CellEditor getSimpleTextEditor(Composite parent, FieldDescriptor anot) {
        TextCellEditor ed = new TextCellEditor(parent) {
            @Override
            protected void doSetValue(Object value) {
                String string = value != null ? value.toString() : "";
                super.doSetValue(string);
            }
        };
        if (anot != null && anot.regex().length() > 0) {
            Text text = (Text) ed.getControl();
            RegexDecorator deco = new RegexDecorator(text, anot.regex());
            deco.setAllowInvalidValues(false);
        }
        return ed;
    }

    public CellEditor getDeviceEditor(DeviceType deviceType, Composite parent) throws ScanningException {

        final List<String> items;
        if (deviceType == DeviceType.SCANNABLE) {
            items = IFilterService.DEFAULT.filter("org.eclipse.scanning.scannableFilter",
                    cservice.getScannableNames());
        } else if (deviceType == DeviceType.RUNNABLE) {
            Collection<DeviceInformation<?>> infos = dservice.getDeviceInformation();
            List<String> names = new ArrayList<String>(infos.size());
            infos.forEach(info -> {
                if (info.getDeviceRole().isDetector())
                    names.add(info.getName());
            });
            items = IFilterService.DEFAULT.filter("org.eclipse.scanning.detectorFilter", names);
        } else {
            throw new ScanningException("Unrecognised device " + deviceType);
        }

        if (items != null) {
            final List<String> sorted = new ArrayList<>(items);
            Collections.sort(sorted, new SortNatural<>(false));
            final String[] finalItems = sorted.toArray(new String[sorted.size()]);

            return new CComboCellEditor(parent, finalItems) {
                private Object lastValue;

                protected void doSetValue(Object value) {
                    if (value instanceof Integer)
                        value = finalItems[((Integer) value).intValue()];
                    lastValue = value;
                    super.doSetValue(value);
                }

                protected Object doGetValue() {
                    try {
                        Integer ordinal = (Integer) super.doGetValue();
                        return finalItems[ordinal];
                    } catch (IndexOutOfBoundsException ne) {
                        return lastValue;
                    }
                }
            };
        } else {
            return new TextCellEditor(parent) {
                @Override
                protected void doSetValue(Object value) {
                    String string = value != null ? value.toString() : "";
                    super.doSetValue(string);
                }
            };
        }

    }

    public static boolean isEnabled(Object model, FieldDescriptor anot) {

        if (anot == null)
            return true;
        if (!anot.editable())
            return false;

        String enableIf = anot.enableif();
        if (enableIf != null && !"".equals(enableIf)) {

            try {
                final IExpressionService service = ServiceHolder.getExpressionService();
                final IExpressionEngine engine = service.getExpressionEngine();
                engine.createExpression(enableIf);

                final Map<String, Object> values = new HashMap<>();
                final Collection<FieldValue> fields = FieldUtils.getModelFields(model);
                for (FieldValue field : fields) {
                    Object value = field.get();
                    if (value instanceof Enum)
                        value = ((Enum) value).name();
                    values.put(field.getName(), value);
                }
                engine.setLoadedVariables(values);
                return (Boolean) engine.evaluate();

            } catch (Exception ne) {
                logger.error("Cannot evaluate expression " + enableIf, ne);
            }
        }

        return true;
    }

    private static void showHint(final String hint, final Composite parent) {

        if (parent.isDisposed())
            return;
        if (parent != null)
            parent.getDisplay().asyncExec(new Runnable() {
                public void run() {

                    currentHint = new DefaultToolTip(parent, ToolTip.NO_RECREATE, true);
                    ((DefaultToolTip) currentHint).setText(hint);
                    currentHint.setHideOnMouseDown(true);
                    currentHint.show(new Point(0, parent.getSize().y));

                    if (selectionListener == null) {
                        if (PageUtil.getPage() != null) {
                            selectionListener = new ISelectionListener() {
                                @Override
                                public void selectionChanged(IWorkbenchPart part, ISelection selection) {
                                    if (currentHint != null)
                                        currentHint.hide();
                                }
                            };

                            PageUtil.getPage().addSelectionListener(selectionListener);
                        }

                    }
                }
            });
    }

    private static boolean isNumberArray(Class<? extends Object> clazz) {

        if (clazz == null)
            return false;
        if (!clazz.isArray())
            return false;

        return double[].class.isAssignableFrom(clazz) || float[].class.isAssignableFrom(clazz)
                || int[].class.isAssignableFrom(clazz) || long[].class.isAssignableFrom(clazz);
    }

    private static CellEditor getChoiceEditor(final Class<? extends Enum> clazz, Composite parent) {

        final Enum[] values = clazz.getEnumConstants();
        final String[] items = Arrays.toString(values).replaceAll("^.|.$", "").split(", ");

        CComboCellEditor cellEd = new CComboCellEditor(parent, items) {
            protected void doSetValue(Object value) {
                if (value instanceof Enum)
                    value = ((Enum) value).ordinal();
                super.doSetValue(value);
            }

            protected Object doGetValue() {
                Integer ordinal = (Integer) super.doGetValue();
                return values[ordinal];
            }
        };

        return cellEd;
    }

    private static CellEditor getChoiceWithEntryEditor(final CComboWithEntryCellEditorData data, Composite parent) {

        final String[] items = data.getItems();

        CComboWithEntryCellEditor cellEd = new CComboWithEntryCellEditor(parent, items) {
            protected void doSetValue(Object value) {
                super.doSetValue(((CComboWithEntryCellEditorData) value).getActiveItem());
            }

            protected Object doGetValue() {
                return new CComboWithEntryCellEditorData(data, (String) super.doGetValue());
            }
        };

        return cellEd;
    }

    private CellEditor getNumberEditor(FieldValue field, final Class<? extends Object> clazz, Composite parent) {

        FieldDescriptor anot = field.getAnnotation();
        NumberCellEditor textEd = null;
        if (anot != null) {
            textEd = new NumberCellEditor(parent, clazz, getMinimum(field, anot), getMaximum(field, anot),
                    getUnit(field, anot), SWT.NONE);

            if (anot.numberFormat() != null && !"".equals(anot.numberFormat())) {
                textEd.setDecimalFormat(anot.numberFormat());
            }

        } else {
            textEd = new NumberCellEditor(parent, clazz, SWT.NONE);
        }

        //textEd.setAllowInvalidValues(true);
        if (anot != null && anot.validif().length() > 0) {
            final ValidIfDecorator deco = new ValidIfDecorator(field.getName(), field.getModel(), anot.validif());
            textEd.setDelegateDecorator(deco);
        }

        return textEd;
    }

    private String getUnit(FieldValue field, FieldDescriptor anot) {
        if (anot.unit().length() > 0)
            return anot.unit();
        IScannable<Number> scannable = getScannable(field, anot);
        return scannable != null ? scannable.getUnit() : null;
    }

    private Number getMinimum(FieldValue field, FieldDescriptor anot) {
        if (!Double.isInfinite(anot.minimum()))
            return anot.minimum();
        IScannable<Number> scannable = getScannable(field, anot);
        return scannable != null ? scannable.getMinimum() : null;
    }

    private Number getMaximum(FieldValue field, FieldDescriptor anot) {
        if (!Double.isInfinite(anot.maximum()))
            return anot.maximum();
        IScannable<Number> scannable = getScannable(field, anot);
        return scannable != null ? scannable.getMaximum() : null;
    }

    private IScannable<Number> getScannable(FieldValue field, FieldDescriptor anot) {

        if (anot.scannable().length() < 1 || cservice == null)
            return null;
        String scannableName;
        try {
            scannableName = (String) FieldValue.get(field.getModel(), anot.scannable());
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            return null;
        }

        if (scannableName != null && scannableName.length() > 0) {
            try {
                return cservice.getScannable(scannableName);
            } catch (Exception ne) {
                return null;
            }
        }
        return null;
    }

    private static TextCellEditor getDatasetEditor(final FieldValue field, Composite parent) {

        final TextCellEditorWithContentProposal ed = new TextCellEditorWithContentProposal(parent, null, null);

        Job job = new Job("dataset name read") {

            @Override
            protected IStatus run(IProgressMonitor monitor) {
                String fileField = field.getAnnotation().dataset();
                Object object;
                try {
                    object = FieldValue.get(field.getModel(), fileField);
                } catch (Exception e) {
                    return Status.CANCEL_STATUS;
                }

                if (object == null)
                    return Status.CANCEL_STATUS;
                final Map<String, int[]> datasetInfo = DatasetNameUtils.getDatasetInfo(object.toString());
                datasetInfo.toString();

                final IContentProposalProvider cpp = new IContentProposalProvider() {

                    @Override
                    public IContentProposal[] getProposals(String contents, int position) {
                        List<IContentProposal> prop = new ArrayList<IContentProposal>();

                        for (String key : datasetInfo.keySet()) {
                            if (key.startsWith(contents))
                                prop.add(new ContentProposal(key));
                        }

                        if (prop.isEmpty()) {
                            for (String key : datasetInfo.keySet())
                                prop.add(new ContentProposal(key));
                        }

                        return prop.toArray(new IContentProposal[prop.size()]);
                    }
                };

                Display.getDefault().syncExec(new Runnable() {

                    @Override
                    public void run() {
                        ed.setContentProposalProvider(cpp);
                        ed.getContentProposalAdapter()
                                .setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
                        ed.getContentProposalAdapter().setAutoActivationCharacters(null);
                    }
                });

                return Status.OK_STATUS;
            }
        };

        job.schedule();

        return ed;
    }

    public IScannableDeviceService getScannableDeviceService() {
        return cservice;
    }

}