org.eclipse.sapphire.ui.renderers.swt.CheckBoxListPropertyEditorRenderer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.sapphire.ui.renderers.swt.CheckBoxListPropertyEditorRenderer.java

Source

/******************************************************************************
 * Copyright (c) 2013 Oracle
 * 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:
 *    Konstantin Komissarchik - initial implementation and ongoing maintenance
 *    Ling Hao - [404482] CheckBoxListPropertyEditorRenderer needs a listener
 ******************************************************************************/

package org.eclipse.sapphire.ui.renderers.swt;

import static org.eclipse.sapphire.ui.swt.renderer.GridLayoutUtil.gd;
import static org.eclipse.sapphire.ui.swt.renderer.GridLayoutUtil.gdfill;
import static org.eclipse.sapphire.ui.swt.renderer.GridLayoutUtil.gdvalign;
import static org.eclipse.sapphire.ui.swt.renderer.GridLayoutUtil.gdwhint;
import static org.eclipse.sapphire.ui.swt.renderer.GridLayoutUtil.glayout;
import static org.eclipse.sapphire.ui.swt.renderer.GridLayoutUtil.glspacing;
import static org.eclipse.sapphire.ui.swt.renderer.SwtUtil.makeTableSortable;
import static org.eclipse.sapphire.ui.swt.renderer.SwtUtil.suppressDashedTableEntryBorder;
import static org.eclipse.sapphire.util.CollectionsUtil.equalsBasedOnEntryIdentity;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;

import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ICheckStateProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.sapphire.Element;
import org.eclipse.sapphire.ElementType;
import org.eclipse.sapphire.Event;
import org.eclipse.sapphire.ListProperty;
import org.eclipse.sapphire.Listener;
import org.eclipse.sapphire.Property;
import org.eclipse.sapphire.PropertyContentEvent;
import org.eclipse.sapphire.PropertyDef;
import org.eclipse.sapphire.ValueProperty;
import org.eclipse.sapphire.modeling.CapitalizationType;
import org.eclipse.sapphire.modeling.ImageData;
import org.eclipse.sapphire.modeling.LoggingService;
import org.eclipse.sapphire.modeling.Status;
import org.eclipse.sapphire.modeling.annotations.NoDuplicates;
import org.eclipse.sapphire.modeling.localization.LocalizationService;
import org.eclipse.sapphire.modeling.util.NLS;
import org.eclipse.sapphire.services.ImageService;
import org.eclipse.sapphire.services.PossibleTypesService;
import org.eclipse.sapphire.services.PossibleValuesService;
import org.eclipse.sapphire.services.ValueImageService;
import org.eclipse.sapphire.services.ValueLabelService;
import org.eclipse.sapphire.ui.ListSelectionService;
import org.eclipse.sapphire.ui.ListSelectionService.ListSelectionChangedEvent;
import org.eclipse.sapphire.ui.PropertyEditorPart;
import org.eclipse.sapphire.ui.SapphireRenderingContext;
import org.eclipse.sapphire.ui.assist.internal.PropertyEditorAssistDecorator;
import org.eclipse.sapphire.ui.def.PropertyEditorDef;
import org.eclipse.sapphire.ui.internal.SapphireUiFrameworkPlugin;
import org.eclipse.sapphire.ui.swt.SwtResourceCache;
import org.eclipse.sapphire.util.ListFactory;
import org.eclipse.sapphire.util.SetFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

/**
 * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
 * @author <a href="mailto:ling.hao@oracle.com">Ling Hao</a>
 */

public class CheckBoxListPropertyEditorRenderer extends ListPropertyEditorRenderer {
    private Table table;
    private CheckboxTableViewer tableViewer;
    private ElementType memberType;
    private ValueProperty memberProperty;
    private PossibleValuesService possibleValuesService;
    private Listener possibleValuesServiceListener;

    public CheckBoxListPropertyEditorRenderer(final SapphireRenderingContext context,
            final PropertyEditorPart part) {
        super(context, part);
    }

    @Override
    protected void createContents(final Composite parent) {
        // Initialize

        final PropertyEditorPart part = getPart();
        final Property property = part.property();

        this.memberType = property.definition().getType();

        final SortedSet<PropertyDef> allMemberProperties = this.memberType.properties();

        if (allMemberProperties.size() == 1) {
            final PropertyDef prop = allMemberProperties.first();

            if (prop instanceof ValueProperty) {
                this.memberProperty = (ValueProperty) prop;
            } else {
                throw new IllegalStateException();
            }
        } else {
            throw new IllegalStateException();
        }

        this.possibleValuesService = property.service(PossibleValuesService.class);

        this.possibleValuesServiceListener = new Listener() {
            @Override
            public void handle(final Event event) {
                CheckBoxListPropertyEditorRenderer.this.tableViewer.refresh();
            }
        };

        this.possibleValuesService.attach(this.possibleValuesServiceListener);

        // Create Controls

        final Composite mainComposite = createMainComposite(parent);
        mainComposite.setLayout(glspacing(glayout(2, 0, 0), 2));

        final PropertyEditorAssistDecorator decorator = createDecorator(mainComposite);
        decorator.control().setLayoutData(gdvalign(gd(), SWT.TOP));

        // Setting the whint in the following code is a hacky workaround for the problem
        // tracked by the following JFace bug:
        //
        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=215997
        //

        final Composite tableComposite = new Composite(mainComposite, SWT.NONE);
        tableComposite.setLayoutData(gdwhint(gdfill(), 1));

        final TableColumnLayout tableColumnLayout = new TableColumnLayout();
        tableComposite.setLayout(tableColumnLayout);

        this.tableViewer = CheckboxTableViewer.newCheckList(tableComposite, SWT.BORDER | SWT.FULL_SELECTION);
        this.table = this.tableViewer.getTable();

        final TableViewerColumn viewerColumn = new TableViewerColumn(this.tableViewer, SWT.NONE);
        final TableColumn column = viewerColumn.getColumn();
        column.setText(this.memberProperty.getLabel(false, CapitalizationType.TITLE_STYLE, false));
        tableColumnLayout.setColumnData(column, new ColumnWeightData(1, 100, true));

        decorator.addEditorControl(mainComposite);

        suppressDashedTableEntryBorder(this.table);

        // Bind to Model

        final ColumnSortComparator comparator = new ColumnSortComparator() {
            @Override
            protected String convertToString(final Object obj) {
                return ((Entry) obj).value;
            }
        };

        final IStructuredContentProvider contentProvider = new IStructuredContentProvider() {
            private List<Entry> entries = new ArrayList<Entry>();

            public Object[] getElements(final Object input) {
                if (this.entries != null) {
                    for (Entry entry : this.entries) {
                        entry.dispose();
                    }

                    this.entries = null;
                }

                final Map<String, LinkedList<Element>> valueToElements = new HashMap<String, LinkedList<Element>>();

                for (Element element : getList()) {
                    final String value = readMemberProperty(element);
                    LinkedList<Element> elements = valueToElements.get(value);

                    if (elements == null) {
                        elements = new LinkedList<Element>();
                        valueToElements.put(value, elements);
                    }

                    elements.add(element);
                }

                this.entries = new ArrayList<Entry>();

                Set<String> possibleValues;

                try {
                    possibleValues = CheckBoxListPropertyEditorRenderer.this.possibleValuesService.values();
                } catch (Exception e) {
                    SapphireUiFrameworkPlugin.log(e);
                    possibleValues = SetFactory.empty();
                }

                for (String value : possibleValues) {
                    final Entry entry;
                    final LinkedList<Element> elements = valueToElements.get(value);

                    if (elements == null) {
                        entry = new Entry(value, null);
                    } else {
                        final Element element = elements.remove();

                        if (elements.isEmpty()) {
                            valueToElements.remove(value);
                        }

                        entry = new Entry(value, element);
                    }

                    this.entries.add(entry);
                }

                for (Map.Entry<String, LinkedList<Element>> entry : valueToElements.entrySet()) {
                    final String value = entry.getKey();

                    for (Element element : entry.getValue()) {
                        this.entries.add(new Entry(value, element));
                    }
                }

                return this.entries.toArray();
            }

            public void dispose() {
                for (Entry entry : this.entries) {
                    entry.dispose();
                }

                this.entries = null;
            }

            public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) {
            }
        };

        this.tableViewer.setContentProvider(contentProvider);

        final ColumnLabelProvider labelProvider = new ColumnLabelProvider() {
            @Override
            public String getText(final Object element) {
                return ((Entry) element).label();
            }

            @Override
            public Image getImage(final Object element) {
                return ((Entry) element).image();
            }

            @Override
            public Color getForeground(final Object element) {
                return ((Entry) element).foreground();
            }
        };

        viewerColumn.setLabelProvider(labelProvider);

        final ICheckStateProvider checkStateProvider = new ICheckStateProvider() {
            public boolean isChecked(final Object element) {
                return ((Entry) element).selected();
            }

            public boolean isGrayed(final Object element) {
                return false;
            }
        };

        this.tableViewer.setCheckStateProvider(checkStateProvider);

        if (part.getRenderingHint(PropertyEditorDef.HINT_SHOW_HEADER, true) == true) {
            this.table.setHeaderVisible(true);

            makeTableSortable(this.tableViewer,
                    Collections.<TableColumn, Comparator<Object>>singletonMap(column, comparator),
                    CheckBoxListPropertyEditorRenderer.this.possibleValuesService.ordered() ? null : column);
        }

        final ListSelectionService selectionService = part.service(ListSelectionService.class);

        this.tableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(final SelectionChangedEvent event) {
                selectionService.select(getSelectedElements());
            }
        });

        final Listener selectionServiceListener = new Listener() {
            @Override
            public void handle(final Event event) {
                setSelectedElements(((ListSelectionChangedEvent) event).after());
            }
        };

        selectionService.attach(selectionServiceListener);

        addOnDisposeOperation(new Runnable() {
            public void run() {
                selectionService.detach(selectionServiceListener);

                if (CheckBoxListPropertyEditorRenderer.this.possibleValuesService != null) {
                    CheckBoxListPropertyEditorRenderer.this.possibleValuesService
                            .detach(CheckBoxListPropertyEditorRenderer.this.possibleValuesServiceListener);
                }
            }
        });

        this.tableViewer.addCheckStateListener(new ICheckStateListener() {
            public void checkStateChanged(final CheckStateChangedEvent event) {
                final Entry entry = (Entry) event.getElement();

                entry.flip();
                selectionService.select(getSelectedElements());
            }
        });

        this.table.addMouseListener(new MouseAdapter() {
            public void mouseDoubleClick(final MouseEvent event) {
                Entry entry = null;

                for (TableItem item : CheckBoxListPropertyEditorRenderer.this.table.getItems()) {
                    if (item.getBounds().contains(event.x, event.y)) {
                        entry = (Entry) item.getData();
                        break;
                    }
                }

                if (entry != null) {
                    entry.flip();
                    selectionService.select(getSelectedElements());
                }
            }
        });

        // Finish Up

        this.tableViewer.setInput(new Object());
        addControl(this.table);
    }

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

    @Override
    protected void handleFocusReceivedEvent() {
        this.table.setFocus();
    }

    @Override
    protected void handlePropertyChangedEvent() {
        super.handlePropertyChangedEvent();
        refresh();
    }

    @Override
    protected void handleChildPropertyEvent(final PropertyContentEvent event) {
        super.handleChildPropertyEvent(event);
        refresh();
    }

    public final Element getSelectedElement() {
        final IStructuredSelection sel = (IStructuredSelection) CheckBoxListPropertyEditorRenderer.this.tableViewer
                .getSelection();

        if (sel == null) {
            return null;
        } else {
            return ((Entry) sel.getFirstElement()).element;
        }
    }

    public final List<Element> getSelectedElements() {
        final IStructuredSelection sel = (IStructuredSelection) CheckBoxListPropertyEditorRenderer.this.tableViewer
                .getSelection();
        final ListFactory<Element> elements = ListFactory.start();

        if (sel != null) {
            for (Iterator<?> itr = sel.iterator(); itr.hasNext();) {
                final Element element = ((Entry) itr.next()).element;

                if (element != null) {
                    elements.add(element);
                }
            }
        }

        return elements.result();
    }

    public final void setSelectedElements(final List<Element> elements) {
        if (!equalsBasedOnEntryIdentity(getSelectedElements(), elements)) {
            final ListFactory<Entry> entries = ListFactory.start();

            for (Element element : elements) {
                for (TableItem item : this.table.getItems()) {
                    final Entry entry = (Entry) item.getData();

                    if (entry.element == element) {
                        entries.add(entry);
                    }
                }
            }

            this.tableViewer.setSelection(new StructuredSelection(entries.result()));
        }
    }

    private void refresh() {
        final int oldItemCount = this.table.getItemCount();
        final Entry oldSelection = (Entry) ((IStructuredSelection) this.tableViewer.getSelection())
                .getFirstElement();

        this.tableViewer.refresh();

        final int newItemCount = this.table.getItemCount();

        if (oldSelection != null) {
            final String oldSelectionValue = oldSelection.value;
            Entry newSelection = null;

            for (int i = 0; i < newItemCount && newSelection == null; i++) {
                final Entry entry = (Entry) this.table.getItem(i).getData();

                if (oldSelectionValue.equals(entry.value)) {
                    newSelection = entry;
                }
            }

            if (newSelection != null) {
                this.tableViewer.setSelection(new StructuredSelection(newSelection));
            }
        }

        if (oldItemCount != newItemCount) {
            this.table.getParent().layout(true, true);
        }
    }

    private ElementType getMemberType() {
        return this.memberType;
    }

    private ValueProperty getMemberProperty() {
        return this.memberProperty;
    }

    private String readMemberProperty(final Element element) {
        final String text = element.property(this.memberProperty).text();
        return (text == null ? "" : text);
    }

    public final class Entry {
        private final LocalizationService localizationService;
        private String value;
        private Element element;
        private ValueLabelService valueLabelService;
        private ValueImageService valueImageService;
        private ImageService elementImageService;
        private Listener listener;

        public Entry(final String value, final Element element) {
            this.localizationService = getPart().definition().adapt(LocalizationService.class);
            this.value = value;
            this.element = element;

            this.valueLabelService = CheckBoxListPropertyEditorRenderer.this.memberProperty
                    .service(ValueLabelService.class);
            this.valueImageService = CheckBoxListPropertyEditorRenderer.this.memberProperty
                    .service(ValueImageService.class);

            this.listener = new Listener() {
                @Override
                public void handle(final Event event) {
                    CheckBoxListPropertyEditorRenderer.this.tableViewer.update(Entry.this, null);
                }
            };

            if (this.element != null) {
                this.elementImageService = this.element.service(ImageService.class);

                if (this.elementImageService != null) {
                    this.elementImageService.attach(this.listener);
                }
            }
        }

        public String label() {
            String label = null;

            if (this.value.length() == 0) {
                label = Resources.emptyIndicator;
            } else {
                try {
                    label = this.valueLabelService.provide(this.value);
                } catch (Exception e) {
                    LoggingService.log(e);
                }

                if (label == null) {
                    label = this.value;
                } else if (!label.equals(this.value)) {
                    label = this.localizationService.transform(label, CapitalizationType.FIRST_WORD_ONLY, false);
                }
            }

            return label;
        }

        public Image image() {
            final SwtResourceCache cache = getPart().getSwtResourceCache();
            final Image image;

            if (this.element == null || this.elementImageService == null) {
                ImageData imageData = null;

                try {
                    imageData = this.valueImageService.provide(this.value);
                } catch (Exception e) {
                    LoggingService.log(e);
                }

                if (imageData == null) {
                    imageData = getMemberType().image();
                }

                image = cache.image(imageData);
            } else {
                final Status st = this.element.property(getMemberProperty()).validation();
                image = cache.image(this.elementImageService.image(), st.severity());
            }

            return image;
        }

        public Color foreground() {
            Color color = null;

            if (this.value.length() == 0) {
                color = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY);
            }

            return color;
        }

        public boolean selected() {
            return (this.element != null);
        }

        public void flip() {
            if (this.element == null) {
                this.element = getList().insert();
                this.element.property(getMemberProperty()).write(this.value);

                this.elementImageService = this.element.service(ImageService.class);

                if (this.elementImageService != null) {
                    this.elementImageService.attach(this.listener);
                }
            } else {
                if (this.elementImageService != null) {
                    this.elementImageService.detach(this.listener);
                    this.elementImageService = null;
                }

                // Must null the element field before trying to remove the element as remove will 
                // trigger property change event and it is possible for the resulting refresh to 
                // set the element field to a new value before returning.

                final Element el = this.element;
                this.element = null;
                getList().remove(el);
            }
        }

        public void dispose() {
            if (this.elementImageService != null) {
                this.elementImageService.detach(this.listener);
            }
        }
    }

    public static final class Factory extends PropertyEditorRendererFactory {
        @Override
        public boolean isApplicableTo(final PropertyEditorPart propertyEditorDefinition) {
            return true;
        }

        @Override
        public PropertyEditorRenderer create(final SapphireRenderingContext context,
                final PropertyEditorPart part) {
            return new CheckBoxListPropertyEditorRenderer(context, part);
        }
    }

    public static final class EnumFactory extends PropertyEditorRendererFactory {
        @Override
        public boolean isApplicableTo(final PropertyEditorPart part) {
            final Property property = part.property();

            if (property.definition() instanceof ListProperty
                    && property.service(PossibleTypesService.class).types().size() == 1) {
                final SortedSet<PropertyDef> properties = property.definition().getType().properties();

                if (properties.size() == 1) {
                    final PropertyDef memberProperty = properties.first();

                    if (memberProperty instanceof ValueProperty && memberProperty.hasAnnotation(NoDuplicates.class)
                            && Enum.class.isAssignableFrom(memberProperty.getTypeClass())) {
                        return true;
                    }
                }
            }

            return false;
        }

        @Override
        public PropertyEditorRenderer create(final SapphireRenderingContext context,
                final PropertyEditorPart part) {
            return new CheckBoxListPropertyEditorRenderer(context, part);
        }
    }

    private static final class Resources extends NLS {
        public static String emptyIndicator;

        static {
            initializeMessages(CheckBoxListPropertyEditorRenderer.class.getName(), Resources.class);
        }
    }

}