]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/gallery/GalleryViewer.java
Add isDisposed checking to avoid unexpected NPE
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / gallery / GalleryViewer.java
index d8ddad9adb2dcf0ebd149f8bbf2d7c67cab1c5c5..d09ecf4a6b6f753f7f0adbf57060b3a36ac987c1 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.g2d.gallery;\r
-\r
-import java.awt.Color;\r
-import java.awt.Component;\r
-import java.awt.Font;\r
-import java.awt.FontMetrics;\r
-import java.awt.Paint;\r
-import java.awt.Shape;\r
-import java.awt.TexturePaint;\r
-import java.awt.geom.Rectangle2D;\r
-import java.awt.image.BufferedImage;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Collections;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-import java.util.Set;\r
-import java.util.concurrent.Semaphore;\r
-import java.util.concurrent.TimeUnit;\r
-import java.util.function.Consumer;\r
-\r
-import org.eclipse.jface.resource.FontRegistry;\r
-import org.eclipse.jface.util.IPropertyChangeListener;\r
-import org.eclipse.jface.util.PropertyChangeEvent;\r
-import org.eclipse.jface.viewers.ContentViewer;\r
-import org.eclipse.jface.viewers.ISelection;\r
-import org.eclipse.jface.viewers.IStructuredContentProvider;\r
-import org.eclipse.jface.viewers.IStructuredSelection;\r
-import org.eclipse.jface.viewers.StructuredSelection;\r
-import org.eclipse.jface.viewers.ViewerFilter;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.events.ControlEvent;\r
-import org.eclipse.swt.events.ControlListener;\r
-import org.eclipse.swt.graphics.FontData;\r
-import org.eclipse.swt.graphics.Point;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Control;\r
-import org.eclipse.swt.widgets.Display;\r
-import org.eclipse.swt.widgets.Event;\r
-import org.eclipse.swt.widgets.Listener;\r
-import org.simantics.g2d.canvas.Hints;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.canvas.impl.CanvasContext;\r
-import org.simantics.g2d.chassis.SWTChassis;\r
-import org.simantics.g2d.diagram.DiagramClass;\r
-import org.simantics.g2d.diagram.DiagramHints;\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.handler.DataElementMap;\r
-import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;\r
-import org.simantics.g2d.diagram.handler.layout.FlowLayout;\r
-import org.simantics.g2d.diagram.impl.Diagram;\r
-import org.simantics.g2d.diagram.participant.DiagramParticipant;\r
-import org.simantics.g2d.diagram.participant.ElementInteractor;\r
-import org.simantics.g2d.diagram.participant.ElementPainter;\r
-import org.simantics.g2d.diagram.participant.Selection;\r
-import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;\r
-import org.simantics.g2d.dnd.IDragSourceParticipant;\r
-import org.simantics.g2d.dnd.IDropTargetParticipant;\r
-import org.simantics.g2d.element.ElementClass;\r
-import org.simantics.g2d.element.ElementHints;\r
-import org.simantics.g2d.element.ElementUtils;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.handler.Clickable;\r
-import org.simantics.g2d.element.handler.Resize;\r
-import org.simantics.g2d.element.handler.impl.DefaultTransform;\r
-import org.simantics.g2d.element.handler.impl.Resizeable;\r
-import org.simantics.g2d.element.handler.impl.TextImpl;\r
-import org.simantics.g2d.element.impl.Element;\r
-import org.simantics.g2d.image.Image;\r
-import org.simantics.g2d.image.Image.Feature;\r
-import org.simantics.g2d.image.Image.ImageListener;\r
-import org.simantics.g2d.image.impl.Shadow.ShadowParameters;\r
-import org.simantics.g2d.participant.BackgroundPainter;\r
-import org.simantics.g2d.participant.KeyToCommand;\r
-import org.simantics.g2d.participant.KeyUtil;\r
-import org.simantics.g2d.participant.MouseUtil;\r
-import org.simantics.g2d.participant.SymbolUtil;\r
-import org.simantics.g2d.participant.TransformUtil;\r
-import org.simantics.g2d.tooltip.TooltipParticipant;\r
-import org.simantics.g2d.utils.FontHelper;\r
-import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.g2d.events.command.CommandKeyBinding;\r
-import org.simantics.scenegraph.g2d.nodes.ShapeNode;\r
-import org.simantics.scenegraph.utils.GeometryUtils;\r
-import org.simantics.scenegraph.utils.TextUtil;\r
-import org.simantics.utils.datastructures.hints.IHintContext;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
-import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
-import org.simantics.utils.threads.AWTThread;\r
-import org.simantics.utils.threads.IThreadWorkQueue;\r
-import org.simantics.utils.threads.SWTThread;\r
-import org.simantics.utils.threads.ThreadUtils;\r
-import org.simantics.utils.threads.logger.ITask;\r
-import org.simantics.utils.threads.logger.ThreadLogger;\r
-\r
-/**\r
- * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
- * @author Tuukka Lehtonen\r
- */\r
-public class GalleryViewer extends ContentViewer {\r
-\r
-    /**\r
-     * A hint key for storing a GalleryViewer within the hint stack of\r
-     * {@link GalleryViewer}'s {@link ICanvasContext}.\r
-     */\r
-    public static final Key              KEY_VIEWER      = new KeyOf(GalleryViewer.class, "GALLERY_VIEWER");\r
-\r
-    public static final ShadowParameters SHADOW          = new ShadowParameters(0.5, Color.BLACK, 5);\r
-\r
-    ViewerFilter                         filter;\r
-    IThreadWorkQueue                     swtThread;\r
-    Display                              display;\r
-    SWTChassis                           chassis;\r
-    Component                            awtComponent;\r
-    CanvasContext                        ctx;\r
-    Selection                            selection;\r
-    GalleryItemPainter                   itemPainter;\r
-    IDiagram                             diagram;\r
-    /** element size */\r
-    Rectangle2D                          itemSize    = new Rectangle2D.Double(0, 0, 50, 50);\r
-    Rectangle2D                          maxItemSize = itemSize.getBounds2D();\r
-    FlowLayout                           fl          = new FlowLayout();\r
-    int                                  hMargin     = 5;\r
-    int                                  vMargin     = 5;\r
-    ElementClass                         itemClass;\r
-    FontRegistry                         fontRegistry;\r
-    Font                                 currentItemFont;\r
-\r
-    GalleryTooltipProvider               tooltipProvider = new GalleryTooltipProvider();\r
-\r
-    /** Background paint */\r
-    public static final Paint BG_PAINT;\r
-    static {\r
-        // Create checkerboard pattern paint with 5x5 square size\r
-        BufferedImage bi = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB);\r
-        for (int x = 0; x < 2; ++x)\r
-            for (int y = 0; y < 2; ++y)\r
-                bi.setRGB(x, y, (((x ^ y) & 1) == 0) ? 0xffffffff : 0xfffdfdfd);\r
-        BG_PAINT = new TexturePaint(bi, new Rectangle2D.Double(0, 0, 10, 10));\r
-        //BG_PAINT = Color.WHITE;\r
-    }\r
-\r
-    public GalleryViewer(Composite composite) {\r
-        this(composite, 0);\r
-    }\r
-\r
-    public GalleryViewer(Composite composite, int style) {\r
-        super();\r
-        display = composite.getDisplay();\r
-        swtThread = SWTThread.getThreadAccess(composite);\r
-        chassis = new SWTChassis(composite, style) {\r
-            @Override\r
-            public Point computeSize(int wHint, int hHint, boolean changed) {\r
-//              System.out.println("chassis compute size: " + wHint + ", " + hHint + ", " + changed);\r
-\r
-                if (diagram == null)\r
-                    return super.computeSize(wHint, hHint, changed);\r
-\r
-                Rectangle2D rect;\r
-//                if (!changed) {\r
-//                    rect = ElementUtils.getSurroundingElementBoundsOnDiagram(diagram.getSnapshot());\r
-//                }\r
-//                else\r
-                {\r
-                    Double wH = wHint==SWT.DEFAULT ? null : (double) wHint-vMargin-vMargin;\r
-                    Double hH = hHint==SWT.DEFAULT ? null : (double) hHint-hMargin-hMargin;\r
-                    rect = fl.computeSize(diagram, wH, hH);\r
-                }\r
-                return new Point((int)rect.getMaxX()+hMargin*2, (int)rect.getMaxY()+vMargin*2);\r
-            }\r
-        };\r
-\r
-        // Create IDiagram for gallery\r
-        this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);\r
-        diagram.setHint(FlowLayout.HGAP, 5.0);\r
-        diagram.setHint(FlowLayout.VGAP, 5.0);\r
-        diagram.setHint(DiagramHints.KEY_ELEMENT_RASTER_TARGET_SIZE,\r
-                new java.awt.Point((int) itemSize.getWidth(), (int) itemSize.getHeight()));\r
-\r
-        // Create canvas context here in SWT thread but do not initialize it yet.\r
-        this.ctx = new CanvasContext(AWTThread.getThreadAccess());\r
-\r
-        chassis.populate(parameter -> {\r
-            awtComponent = parameter.getAWTComponent();\r
-\r
-            // Initialize the canvas context\r
-            ITask task = ThreadLogger.getInstance().begin("createCanvas");\r
-            initializeCanvasContext(ctx);\r
-            task.finish();\r
-\r
-            // Initialize canvas context hint context with KEY_VIEWER key.\r
-            IHintContext hintCtx = ctx.getDefaultHintContext();\r
-            hintCtx.setHint(KEY_VIEWER, GalleryViewer.this);\r
-\r
-            // Set IDiagram for canvas context\r
-            hintCtx.setHint(DiagramHints.KEY_DIAGRAM, diagram);\r
-\r
-            // Force layout\r
-            ThreadUtils.asyncExec(swtThread, new Runnable() {\r
-                @Override\r
-                public void run() {\r
-                    resized(false);\r
-                }\r
-            });\r
-        });\r
-\r
-        chassis.addControlListener(new ControlListener() {\r
-            @Override\r
-            public void controlMoved(ControlEvent e) {}\r
-            @Override\r
-            public void controlResized(ControlEvent e) {\r
-                resized();\r
-            }});\r
-\r
-        ITask task2 = ThreadLogger.getInstance().begin("fonts");\r
-        try {\r
-            this.fontRegistry = FontHelper.getCurrentThemeFontRegistry();\r
-            fontRegistry.addListener(fontRegistryListener);\r
-            currentItemFont = FontHelper.toAwt(fontRegistry, "org.simantics.gallery.itemfont");\r
-        } catch (IllegalStateException e) {\r
-            // No workbench available, use SWT control fonts for viewer items.\r
-            org.eclipse.swt.graphics.Font f = chassis.getFont();\r
-            currentItemFont = FontHelper.toAwt(f.getFontData()[0]);\r
-        }\r
-        task2.finish();\r
-\r
-\r
-        itemClass = ElementClass.compile(\r
-                DefaultTransform.INSTANCE,\r
-                TextImpl.INSTANCE,\r
-                //TextAsTooltip.INSTANCE,\r
-                Clickable.INSTANCE,\r
-                Resizeable.UNCONSTRICTED,\r
-                new GalleryItemSGNode(currentItemFont)\r
-        );\r
-\r
-        chassis.addListener(SWT.Dispose, new Listener() {\r
-            @Override\r
-            public void handleEvent(Event event) {\r
-                if (fontRegistry != null)\r
-                    fontRegistry.removeListener(fontRegistryListener);\r
-\r
-                // Prevent memory leaks.\r
-                ThreadUtils.asyncExec(ctx.getThreadAccess(), new Runnable() {\r
-                    @Override\r
-                    public void run() {\r
-                        chassis.getAWTComponent().setCanvasContext(null);\r
-                        ctx.dispose();\r
-                    }\r
-                });\r
-            }\r
-        });\r
-    }\r
-\r
-    IPropertyChangeListener fontRegistryListener = new IPropertyChangeListener() {\r
-        @Override\r
-        public void propertyChange(PropertyChangeEvent event) {\r
-            FontData fdn = ((FontData[]) event.getNewValue())[0];\r
-            //System.out.println(event.getSource() + ": font changed: " + event.getProperty() + ": " + fdn);\r
-            currentItemFont = FontHelper.toAwt(fdn);\r
-            itemClass.getSingleItem(GalleryItemSGNode.class).setFont(currentItemFont);\r
-            // FIXME: a bug exists in this case. The group size will not be refreshed even though the sizes of the gallery items are recalculated and changed.\r
-            ThreadUtils.asyncExec(swtThread, new Runnable() {\r
-                @Override\r
-                public void run() {\r
-                    resized(true);\r
-                }\r
-            });\r
-        }\r
-    };\r
-\r
-    /**\r
-     * Set alignment of elements (left, right, center, fill)\r
-     * @param align\r
-     */\r
-    public void setAlign(FlowLayout.Align align) {\r
-        diagram.setHint(FlowLayout.ALIGN, align);\r
-        resized();\r
-    }\r
-\r
-    /**\r
-     * Sets the content filter of this viewer. The filter will be invoked after\r
-     * getting input data elements from the content provider and before setting\r
-     * the new content for the viewer. Any previously set filter will be\r
-     * replaced.\r
-     *\r
-     * <p>\r
-     * This method will not refresh the viewer, this needs to be done separately\r
-     * using {@link #refresh()} or {@link #refresh(Consumer)}.\r
-     * </p>\r
-     *\r
-     * @param filter the new filter\r
-     */\r
-    public void setFilter(ViewerFilter filter) {\r
-        if (filter == this.filter || (filter != null && filter.equals(this.filter)))\r
-            return;\r
-\r
-        this.filter = filter;\r
-    }\r
-\r
-    public ViewerFilter getFilter() {\r
-        return filter;\r
-    }\r
-\r
-    private void resized() {\r
-        resized(false);\r
-    }\r
-\r
-    private void resized(final boolean refreshElementSizes) {\r
-        //System.out.println(this + ".resized(" + refreshElementSizes + ")");\r
-        if (chassis.isDisposed())\r
-            return;\r
-        org.eclipse.swt.graphics.Rectangle b = chassis.getBounds();\r
-        final Rectangle2D bounds = new Rectangle2D.Double(hMargin, vMargin, b.width-hMargin*2, b.height-vMargin*2);\r
-        ctx.getThreadAccess().asyncExec(new Runnable() {\r
-            @Override\r
-            public void run() {\r
-                if (ctx.isDisposed())\r
-                    return;\r
-                if (diagram == null)\r
-                    return;\r
-                //System.out.println(this + ".resized(" + refreshElementSizes + ") AWT update");\r
-                if (refreshElementSizes)\r
-                    refreshElementSizes();\r
-                fl.layout(diagram, bounds);\r
-\r
-                // Makes sure RTreeNode is marked dirty and everything is\r
-                // properly repainted.\r
-                if (itemPainter != null)\r
-                    itemPainter.updateAll();\r
-                ctx.getContentContext().setDirty();\r
-            }});\r
-    }\r
-\r
-    /**\r
-     * Invoke only from AWT thread.\r
-     * \r
-     * @param thread\r
-     * @return\r
-     */\r
-    private void initializeCanvasContext(final CanvasContext canvasContext) {\r
-        // Create canvas context and a layer of interactors\r
-        final IHintContext h = canvasContext.getDefaultHintContext();\r
-\r
-        // Support & Util Participants\r
-        canvasContext.add( new TransformUtil() );\r
-\r
-        canvasContext.add( new MouseUtil() );\r
-        canvasContext.add( new KeyUtil() );\r
-        canvasContext.add( new SymbolUtil() );\r
-\r
-        // Grid & Ruler & Background\r
-        h.setHint(Hints.KEY_BACKGROUND_PAINT, BG_PAINT);\r
-        canvasContext.add( new BackgroundPainter() );\r
-\r
-        // Key bindings\r
-        canvasContext.add( new KeyToCommand( CommandKeyBinding.DEFAULT_BINDINGS ) );\r
-\r
-        ////// Diagram Participants //////\r
-        PointerInteractor pi = new PointerInteractor(true, true, false, true, false, null);\r
-        pi.setBoxSelectMode(PickPolicy.PICK_INTERSECTING_OBJECTS);\r
-        canvasContext.add( pi );\r
-        canvasContext.add( selection = new Selection() );\r
-        canvasContext.add( new DiagramParticipant() );\r
-        canvasContext.add( itemPainter = new GalleryItemPainter() );\r
-        canvasContext.add( new ElementInteractor() );\r
-        canvasContext.add( new TooltipParticipant());\r
-\r
-        h.setHint(ElementPainter.KEY_SELECTION_FRAME_COLOR, Color.WHITE);\r
-        h.setHint(ElementPainter.KEY_SELECTION_CONTENT_COLOR, new Color(0.7f, 0.7f, 1.f, 0.5f));\r
-        h.setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);\r
-\r
-        // Adds DragInteractor & DropInteractor\r
-\r
-        // therefore has to be done before assertParticipantDependencies.\r
-        // Also, this must be invoked BEFORE SWTChassis chassis.setCanvasContext\r
-        // because otherwise setCanvasContext would be ran in the\r
-        // wrong thread (SWT) for AWTChassis.\r
-        chassis.getAWTComponent().setCanvasContext(canvasContext);\r
-\r
-        swtThread.asyncExec(new Runnable() {\r
-            @Override\r
-            public void run() {\r
-                if (!chassis.isDisposed())\r
-                    chassis.setCanvasContext(canvasContext);\r
-            }\r
-        });\r
-\r
-        canvasContext.assertParticipantDependencies();\r
-    }\r
-\r
-    @Override\r
-    public Control getControl() {\r
-        return chassis;\r
-    }\r
-\r
-    @Override\r
-    protected void inputChanged(Object input, Object oldInput) {\r
-        // Skip automatic refreshing at setInput to allow room for manual\r
-        // optimization in the client.\r
-        //refresh();\r
-    }\r
-\r
-    @Override\r
-    public ISelection getSelection() {\r
-        Set<IElement> sel = selection.getSelection(0);\r
-        if (sel.isEmpty())\r
-            return StructuredSelection.EMPTY;\r
-        List<Object> elements = new ArrayList<Object>(sel.size());\r
-        for (IElement e : sel)\r
-            elements.add(e.getHint(ElementHints.KEY_OBJECT));\r
-        return new StructuredSelection(elements);\r
-    }\r
-\r
-    @SuppressWarnings("unchecked")\r
-    @Override\r
-    public void setSelection(ISelection selection, boolean reveal) {\r
-        List<Object> selectedObjects = Collections.EMPTY_LIST;\r
-        if (selection instanceof IStructuredSelection) {\r
-            selectedObjects = ((IStructuredSelection) selection).toList();\r
-        }\r
-\r
-        DataElementMap map = diagram.getDiagramClass().getSingleItem(DataElementMap.class);\r
-        List<IElement> selectionElements = new ArrayList<IElement>();\r
-        for (Object o : selectedObjects)\r
-            selectionElements.add( map.getElement(diagram, o) );\r
-\r
-        this.selection.setSelection(0, selectionElements);\r
-    }\r
-\r
-    /**\r
-     * Refreshes this viewer completely with information freshly obtained from this\r
-     * viewer's model.\r
-     */\r
-    @Override\r
-    public void refresh() {\r
-        refresh(null);\r
-    }\r
-\r
-    public Object[] getFilteredElements() {\r
-        IStructuredContentProvider cp = (IStructuredContentProvider) getContentProvider();\r
-        if (cp == null)\r
-            return new Object[0];\r
-\r
-        Object[] elements = cp.getElements( getInput() );\r
-        Object[] filtered = filter( elements );\r
-        return filtered;\r
-    }\r
-\r
-    protected Object[] filter(Object[] items) {\r
-        if (filter != null) {\r
-            ArrayList<Object> filtered = new ArrayList<Object>(items.length);\r
-            Object root = getInput();\r
-            for (int i = 0; i < items.length; i++) {\r
-                if (!filter.select(this, root, items[i]))\r
-                    continue;\r
-                filtered.add(items[i]);\r
-            }\r
-            return filtered.toArray();\r
-        }\r
-        return items;\r
-    }\r
-\r
-    /**\r
-     * @param contentCallback a callback for receiving the final filtered\r
-     *        elements left visible\r
-     */\r
-    public void refresh(Consumer<Object[]> contentCallback) {\r
-        //System.out.println(this + ".refresh(" + contentCallback + ")");\r
-        Object[] elements = getFilteredElements();\r
-        refreshWithContent(elements);\r
-        if (contentCallback != null) {\r
-            contentCallback.accept(elements);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Set viewer contents to the specified objects.\r
-     * This method must be invoked from the SWT thread.\r
-     * \r
-     * @param objects\r
-     */\r
-    public void refreshWithContent(final Object[] objects) {\r
-        if (!swtThread.currentThreadAccess())\r
-            throw new IllegalStateException("Not invoked from SWT thread");\r
-\r
-        //System.out.println(this + ".refreshWithContent(" + Arrays.toString(objects) + ")");\r
-\r
-        final Semaphore barrier = new Semaphore(0);\r
-        IThreadWorkQueue t = ctx.getThreadAccess();\r
-        ThreadUtils.asyncExec(t, new Runnable() {\r
-            @Override\r
-            public void run() {\r
-                try {\r
-                    perform();\r
-                } finally {\r
-                    barrier.release();\r
-                }\r
-            }\r
-\r
-            public void perform() {\r
-                // $AWT-Thread-Begin$\r
-                //System.out.println(this + ".refreshWithContent(" + Arrays.toString(objects) + ") AWT WORK");\r
-\r
-                Object[] objectsCopy = Arrays.copyOf(objects, objects.length);\r
-\r
-                Set<Object> objs = new HashSet<Object>();\r
-                for (Object o : objectsCopy)\r
-                    objs.add(o);\r
-\r
-                // 1. Remove unused\r
-                for (IElement e : diagram.getSnapshot()) {\r
-                    Object backendObject = e.getHint(ElementHints.KEY_OBJECT);\r
-                    if (!objs.remove(backendObject)) {\r
-                        //System.out.println("Removing " + e);\r
-                        diagram.removeElement(e);\r
-                    }\r
-                }\r
-                for (int i = 0; i < objectsCopy.length; i++)\r
-                    if (!objs.contains(objectsCopy[i]))\r
-                        objectsCopy[i] = null;\r
-\r
-                // 2. Add new elements\r
-                for (Object o : objectsCopy) {\r
-                    if (o == null)\r
-                        continue;\r
-\r
-                    IElement e = Element.spawnNew(itemClass);\r
-                    e.setHint(ElementHints.KEY_OBJECT, o);\r
-//                    e.getElementClass().getSingleItem(Resize.class).resize(e, itemSize);\r
-                    ILabelProvider lp = (ILabelProvider) getLabelProvider();\r
-                    Image i = lp.getImage(o);\r
-                    if (i.getFeatures().contains(Feature.Volatile))\r
-                        i.addImageListener(imageListener);\r
-                    e.setHint(GalleryItemSGNode.KEY_IMAGE, i);\r
-\r
-                    // tooltips\r
-                    String tooltipText = lp.getToolTipText(o);\r
-                    java.awt.Image tooltipImage = lp.getToolTipImage(o);\r
-                    if (tooltipText != null || tooltipImage != null) {\r
-                        e.setHint(TooltipParticipant.TOOLTIP_KEY, tooltipProvider);\r
-                        if (tooltipText != null)\r
-                            e.setHint(GalleryTooltipProvider.TOOLTIP_TEXT, tooltipText);\r
-                        if (tooltipImage != null)\r
-                            e.setHint(GalleryTooltipProvider.TOOLTIP_IMAGE, tooltipImage);\r
-                    }\r
-                    diagram.addElement(e);\r
-\r
-                    e.getElementClass().getSingleItem(GalleryItemSGNode.class).update(e);\r
-\r
-//                    Image si = ImageUtils.createShadow(i, SHADOW, (int) itemSize.getWidth(), (int) itemSize.getHeight());\r
-//                    si = ImageUtils.createBuffer(si);\r
-//                    e.setHint(GalleryItemPainter.KEY_IMAGE_SHADOW, si);\r
-\r
-                    ElementUtils.setText(e, lp.getText(o));\r
-                    //System.out.println("Added: " + e);\r
-                }\r
-\r
-                // 3. Calculate maximum vertical space needed by current diagram element texts\r
-                refreshElementSizes();\r
-\r
-                ThreadUtils.asyncExec(swtThread, new Runnable() {\r
-                    @Override\r
-                    public void run() {\r
-                        resized(false);\r
-                    }\r
-                });\r
-                // $AWT-Thread-End$\r
-            }\r
-        });\r
-\r
-        boolean done = false;\r
-        while (!done) {\r
-            try {\r
-                done = barrier.tryAcquire(10, TimeUnit.MILLISECONDS);\r
-                while (!done && display.readAndDispatch()) {\r
-                    done = barrier.tryAcquire();\r
-                }\r
-            } catch (InterruptedException e) {\r
-                done = true;\r
-            }\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Invoke from canvas context thread only.\r
-     */\r
-    void refreshElementSizes() {\r
-        if (awtComponent == null) {\r
-            System.err.println("GalleryViewer.refreshElementSizes: awtComponent is null");\r
-            return;\r
-        }\r
-\r
-        //System.out.println(this + ".refreshElementSizes()");\r
-        // Calculate maximum vertical space needed by current diagram element texts\r
-        FontMetrics metrics = awtComponent.getFontMetrics(currentItemFont);\r
-        int fontHeight = metrics.getHeight();\r
-        int maxWidth = (int) itemSize.getWidth();\r
-        Rectangle2D size = itemSize;\r
-        java.awt.Point targetSize = new java.awt.Point((int) itemSize.getWidth(), (int) itemSize.getHeight());\r
-        diagram.setHint(DiagramHints.KEY_ELEMENT_RASTER_TARGET_SIZE, targetSize);\r
-        int maxLinesNeeded = 0;\r
-        for (IElement el : diagram.getElements()) {\r
-            // This makes it possible to give the gallery item a reference size\r
-            // for caching rendered images in the correct size only.\r
-            // NOTE: currently this is not used in GalleryItemPainter since the\r
-            // target size is now propagated through the element class loading\r
-            // process through the diagram hint KEY_ELEMENT_RASTER_REFERENCE_SIZE.\r
-            el.setHint(GalleryItemSGNode.KEY_TARGET_IMAGE_SIZE, targetSize);\r
-\r
-            String text = ElementUtils.getText(el);\r
-            int linesNeeded = TextUtil.wordWrap(text, metrics, maxWidth).length;\r
-            maxLinesNeeded = Math.max(maxLinesNeeded, linesNeeded);\r
-            int textSpaceNeeded = fontHeight * linesNeeded + metrics.getMaxDescent();\r
-            Rectangle2D s = new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight() + textSpaceNeeded);\r
-            el.getElementClass().getSingleItem(Resize.class).resize(el, s);\r
-            //System.out.println(this + "  lines needed: " + linesNeeded + " = " + s);\r
-\r
-            el.getElementClass().getSingleItem(GalleryItemSGNode.class).update(el);\r
-        }\r
-        int maxTextSpaceNeeded = fontHeight * maxLinesNeeded + metrics.getMaxDescent();\r
-        maxItemSize = new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight() + maxTextSpaceNeeded);\r
-        //System.out.println(this + "[" + diagram.getElements().size() + "]: max lines needed: " + maxLinesNeeded + " = " + fontHeight*maxLinesNeeded + " pixels");\r
-    }\r
-\r
-    ImageListener imageListener = new ImageListener() {\r
-        @Override\r
-        public void onContentChangedNotification(Image image) {\r
-            //System.out.println(Thread.currentThread() + ": contentChanged(" + image + ")");\r
-            // Update the image of the element if the element is found.\r
-            for (final IElement el : diagram.getSnapshot()) {\r
-                Image i = GalleryItemSGNode.getImage(el);\r
-                if (image != i)\r
-                    continue;\r
-\r
-                ctx.getThreadAccess().asyncExec(new Runnable() {\r
-                    @Override\r
-                    public void run() {\r
-                        //System.out.println(Thread.currentThread() + ": update scene graph(" + el + ")");\r
-                        // Update scene graph and repaint.\r
-                        el.getElementClass().getSingleItem(GalleryItemSGNode.class).update(el);\r
-                        ctx.getContentContext().setDirty();\r
-                    }\r
-                });\r
-                break;\r
-            }\r
-        }\r
-    };\r
-\r
-    public void addDropSupport(final IDropTargetParticipant p) {\r
-        if (ctx.getThreadAccess().currentThreadAccess()) {\r
-            ctx.add(p);\r
-        } else {\r
-            ctx.getThreadAccess().asyncExec(new Runnable() {\r
-                @Override\r
-                public void run() {\r
-                    if (!ctx.isDisposed())\r
-                        ctx.add(p);\r
-                }\r
-            });\r
-        }\r
-    }\r
-\r
-    public void addDragSupport(final IDragSourceParticipant p) {\r
-        if (ctx.getThreadAccess().currentThreadAccess()) {\r
-            ctx.add(p);\r
-        } else {\r
-            ctx.getThreadAccess().asyncExec(new Runnable() {\r
-                @Override\r
-                public void run() {\r
-                    if (!ctx.isDisposed())\r
-                        ctx.add(p);\r
-                }\r
-            });\r
-        }\r
-    }\r
-\r
-    public CanvasContext getCanvasContext() {\r
-        return ctx;\r
-    }\r
-\r
-    public IDiagram getDiagram() {\r
-        return diagram;\r
-    }\r
-\r
-    //private final static Color BG_COLOR = new Color(0.3f, 0.3f, 1.0f, 0.35f);\r
-\r
-    static class GalleryItemPainter extends ElementPainter {\r
-        @Override\r
-        public void paintSelectionFrame(G2DParentNode elementNode, G2DParentNode selectionNode, IElement e, Color color) {\r
-            final Shape outline = ElementUtils.getElementBoundsOnDiagram(e);\r
-            Rectangle2D bounds = outline.getBounds2D();\r
-            GeometryUtils.expandRectangle(bounds, 2, 2, 2, 2);\r
-\r
-            ShapeNode shapenode = selectionNode.getOrCreateNode("shape_"+e.hashCode(), ShapeNode.class);\r
-            shapenode.setShape(bounds);\r
-            shapenode.setColor(new Color(0.3f, 0.3f, 1.0f, 0.25f));\r
-            shapenode.setFill(true);\r
-\r
-            // Paint selection before anything else in elementNode\r
-            selectionNode.setZIndex(Integer.MIN_VALUE);\r
-        }\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * 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:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.g2d.gallery;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.TexturePaint;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import org.eclipse.jface.resource.FontRegistry;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.jface.viewers.ContentViewer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.simantics.g2d.canvas.Hints;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.impl.CanvasContext;
+import org.simantics.g2d.chassis.SWTChassis;
+import org.simantics.g2d.diagram.DiagramClass;
+import org.simantics.g2d.diagram.DiagramHints;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.DataElementMap;
+import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
+import org.simantics.g2d.diagram.handler.layout.FlowLayout;
+import org.simantics.g2d.diagram.impl.Diagram;
+import org.simantics.g2d.diagram.participant.DiagramParticipant;
+import org.simantics.g2d.diagram.participant.ElementInteractor;
+import org.simantics.g2d.diagram.participant.ElementPainter;
+import org.simantics.g2d.diagram.participant.Selection;
+import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
+import org.simantics.g2d.dnd.IDragSourceParticipant;
+import org.simantics.g2d.dnd.IDropTargetParticipant;
+import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementHints;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.handler.Clickable;
+import org.simantics.g2d.element.handler.Resize;
+import org.simantics.g2d.element.handler.impl.DefaultTransform;
+import org.simantics.g2d.element.handler.impl.Resizeable;
+import org.simantics.g2d.element.handler.impl.TextImpl;
+import org.simantics.g2d.element.impl.Element;
+import org.simantics.g2d.image.Image;
+import org.simantics.g2d.image.Image.Feature;
+import org.simantics.g2d.image.Image.ImageListener;
+import org.simantics.g2d.image.impl.Shadow.ShadowParameters;
+import org.simantics.g2d.participant.BackgroundPainter;
+import org.simantics.g2d.participant.KeyToCommand;
+import org.simantics.g2d.participant.KeyUtil;
+import org.simantics.g2d.participant.MouseUtil;
+import org.simantics.g2d.participant.SymbolUtil;
+import org.simantics.g2d.participant.TransformUtil;
+import org.simantics.g2d.tooltip.TooltipParticipant;
+import org.simantics.g2d.utils.FontHelper;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.events.command.CommandKeyBinding;
+import org.simantics.scenegraph.g2d.nodes.ShapeNode;
+import org.simantics.scenegraph.utils.GeometryUtils;
+import org.simantics.scenegraph.utils.TextUtil;
+import org.simantics.utils.datastructures.hints.IHintContext;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
+import org.simantics.utils.threads.AWTThread;
+import org.simantics.utils.threads.IThreadWorkQueue;
+import org.simantics.utils.threads.SWTThread;
+import org.simantics.utils.threads.ThreadUtils;
+import org.simantics.utils.threads.logger.ITask;
+import org.simantics.utils.threads.logger.ThreadLogger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.simantics.utils.ui.SWTDPIUtil;
+
+/**
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
+ * @author Tuukka Lehtonen
+ */
+public class GalleryViewer extends ContentViewer {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(GalleryViewer.class);
+    /**
+     * A hint key for storing a GalleryViewer within the hint stack of
+     * {@link GalleryViewer}'s {@link ICanvasContext}.
+     */
+    public static final Key              KEY_VIEWER      = new KeyOf(GalleryViewer.class, "GALLERY_VIEWER");
+
+    public static final ShadowParameters SHADOW          = new ShadowParameters(0.5, Color.BLACK, 5);
+
+    ViewerFilter                         filter;
+    IThreadWorkQueue                     swtThread;
+    Display                              display;
+    SWTChassis                           chassis;
+    Component                            awtComponent;
+    CanvasContext                        ctx;
+    Selection                            selection;
+    GalleryItemPainter                   itemPainter;
+    IDiagram                             diagram;
+    /** element size */
+    Rectangle2D                          itemSize    = new Rectangle2D.Double(0, 0, 50, 50);
+    Rectangle2D                          maxItemSize = itemSize.getBounds2D();
+    FlowLayout                           fl          = new FlowLayout();
+    int                                  hMargin     = 5;
+    int                                  vMargin     = 5;
+    ElementClass                         itemClass;
+    FontRegistry                         fontRegistry;
+    Font                                 currentItemFont;
+
+    GalleryTooltipProvider               tooltipProvider = new GalleryTooltipProvider();
+
+    /** Background paint */
+    public static final Paint BG_PAINT;
+    static {
+        // Create checkerboard pattern paint with 5x5 square size
+        BufferedImage bi = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB);
+        for (int x = 0; x < 2; ++x)
+            for (int y = 0; y < 2; ++y)
+                bi.setRGB(x, y, (((x ^ y) & 1) == 0) ? 0xffffffff : 0xfffdfdfd);
+        BG_PAINT = new TexturePaint(bi, new Rectangle2D.Double(0, 0, 10, 10));
+        //BG_PAINT = Color.WHITE;
+    }
+
+    public GalleryViewer(Composite composite) {
+        this(composite, 0);
+    }
+
+    public GalleryViewer(Composite composite, int style) {
+        super();
+        display = composite.getDisplay();
+        swtThread = SWTThread.getThreadAccess(composite);
+        chassis = new SWTChassis(composite, style) {
+            @Override
+            public Point computeSize(int wHint, int hHint, boolean changed) {
+                if (diagram == null)
+                    return super.computeSize(wHint, hHint, changed);
+
+                // Note: This code must take into account that FlowLayout expects to 
+                // receive pixel coordinates, not SWT API coordinates.
+
+                Rectangle2D rect;
+//                if (!changed) {
+//                    rect = ElementUtils.getSurroundingElementBoundsOnDiagram(diagram.getSnapshot());
+//                }
+//                else
+                {
+                    Double wH = wHint==SWT.DEFAULT ? null : (double) SWTDPIUtil.upscaleSwt(wHint)-hMargin*2;
+                    Double hH = hHint==SWT.DEFAULT ? null : (double) SWTDPIUtil.upscaleSwt(hHint)-vMargin*2;
+                    rect = fl.computeSize(diagram, wH, hH);
+                    SWTDPIUtil.downscaleSwt(rect, rect);
+                }
+                return new Point((int)rect.getWidth()+hMargin*2, (int)rect.getHeight()+vMargin*2);
+            }
+        };
+
+        // Create IDiagram for gallery
+        this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
+        diagram.setHint(FlowLayout.HGAP, 5.0);
+        diagram.setHint(FlowLayout.VGAP, 5.0);
+        diagram.setHint(DiagramHints.KEY_ELEMENT_RASTER_TARGET_SIZE,
+                new java.awt.Point((int) itemSize.getWidth(), (int) itemSize.getHeight()));
+
+        // Create canvas context here in SWT thread but do not initialize it yet.
+        this.ctx = new CanvasContext(AWTThread.getThreadAccess());
+
+        chassis.populate(parameter -> {
+            awtComponent = parameter.getAWTComponent();
+
+            // Initialize the canvas context
+            ITask task = ThreadLogger.getInstance().begin("createCanvas");
+            initializeCanvasContext(ctx);
+            task.finish();
+
+            // Initialize canvas context hint context with KEY_VIEWER key.
+            IHintContext hintCtx = ctx.getDefaultHintContext();
+            hintCtx.setHint(KEY_VIEWER, GalleryViewer.this);
+
+            // Set IDiagram for canvas context
+            hintCtx.setHint(DiagramHints.KEY_DIAGRAM, diagram);
+
+            // Force layout
+            ThreadUtils.asyncExec(swtThread, () -> resized(false));
+        });
+
+        chassis.addControlListener(new ControlListener() {
+            @Override
+            public void controlMoved(ControlEvent e) {}
+            @Override
+            public void controlResized(ControlEvent e) {
+                resized();
+            }});
+
+        ITask task2 = ThreadLogger.getInstance().begin("fonts");
+        try {
+            this.fontRegistry = FontHelper.getCurrentThemeFontRegistry();
+            fontRegistry.addListener(fontRegistryListener);
+            currentItemFont = FontHelper.toAwt(fontRegistry, "org.simantics.gallery.itemfont");
+        } catch (IllegalStateException e) {
+            // No workbench available, use SWT control fonts for viewer items.
+            org.eclipse.swt.graphics.Font f = chassis.getFont();
+            currentItemFont = FontHelper.toAwt(f.getFontData()[0]);
+        }
+        task2.finish();
+
+
+        itemClass = ElementClass.compile(
+                DefaultTransform.INSTANCE,
+                TextImpl.INSTANCE,
+                //TextAsTooltip.INSTANCE,
+                Clickable.INSTANCE,
+                Resizeable.UNCONSTRICTED,
+                new GalleryItemSGNode(currentItemFont)
+        );
+
+        chassis.addListener(SWT.Dispose, new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                if (fontRegistry != null)
+                    fontRegistry.removeListener(fontRegistryListener);
+
+                // Prevent memory leaks.
+                ThreadUtils.asyncExec(ctx.getThreadAccess(), () -> {
+                    chassis.getAWTComponent().setCanvasContext(null);
+                    ctx.dispose();
+                });
+            }
+        });
+    }
+
+    IPropertyChangeListener fontRegistryListener = new IPropertyChangeListener() {
+        @Override
+        public void propertyChange(PropertyChangeEvent event) {
+            FontData fdn = ((FontData[]) event.getNewValue())[0];
+            //System.out.println(event.getSource() + ": font changed: " + event.getProperty() + ": " + fdn);
+            currentItemFont = FontHelper.toAwt(fdn);
+            itemClass.getSingleItem(GalleryItemSGNode.class).setFont(currentItemFont);
+            // FIXME: a bug exists in this case. The group size will not be refreshed even though the sizes of the gallery items are recalculated and changed.
+            ThreadUtils.asyncExec(swtThread, () -> resized(true));
+        }
+    };
+
+    /**
+     * Set alignment of elements (left, right, center, fill)
+     * @param align
+     */
+    public void setAlign(FlowLayout.Align align) {
+        diagram.setHint(FlowLayout.ALIGN, align);
+        resized();
+    }
+
+    /**
+     * Sets the content filter of this viewer. The filter will be invoked after
+     * getting input data elements from the content provider and before setting
+     * the new content for the viewer. Any previously set filter will be
+     * replaced.
+     *
+     * <p>
+     * This method will not refresh the viewer, this needs to be done separately
+     * using {@link #refresh()} or {@link #refresh(Consumer)}.
+     * </p>
+     *
+     * @param filter the new filter
+     */
+    public void setFilter(ViewerFilter filter) {
+        if (filter == this.filter || (filter != null && filter.equals(this.filter)))
+            return;
+
+        this.filter = filter;
+    }
+
+    public ViewerFilter getFilter() {
+        return filter;
+    }
+
+    private void resized() {
+        resized(false);
+    }
+
+    private void resized(final boolean refreshElementSizes) {
+        //System.out.println(this + ".resized(" + refreshElementSizes + ")");
+        if (chassis.isDisposed())
+            return;
+        org.eclipse.swt.graphics.Rectangle b = SWTDPIUtil.upscaleSwt(chassis.getBounds());
+        //System.out.println("chassis bounds: " + b);
+        final Rectangle2D bounds = new Rectangle2D.Double(hMargin, vMargin, b.width-hMargin*2, b.height-vMargin*2);
+        ctx.getThreadAccess().asyncExec(() -> {
+            if (ctx.isDisposed())
+                return;
+            if (diagram == null)
+                return;
+            //System.out.println(this + ".resized(" + refreshElementSizes + ") AWT update");
+            if (refreshElementSizes)
+                refreshElementSizes();
+            fl.layout(diagram, bounds);
+
+            // Makes sure RTreeNode is marked dirty and everything is
+            // properly repainted.
+            if (itemPainter != null)
+                itemPainter.updateAll();
+            ctx.getContentContext().setDirty();
+        });
+    }
+
+    /**
+     * Invoke only from AWT thread.
+     * 
+     * @param thread
+     * @return
+     */
+    private void initializeCanvasContext(final CanvasContext canvasContext) {
+        // Create canvas context and a layer of interactors
+        final IHintContext h = canvasContext.getDefaultHintContext();
+
+        // Support & Util Participants
+        canvasContext.add( new TransformUtil() );
+
+        canvasContext.add( new MouseUtil() );
+        canvasContext.add( new KeyUtil() );
+        canvasContext.add( new SymbolUtil() );
+
+        // Grid & Ruler & Background
+        h.setHint(Hints.KEY_BACKGROUND_PAINT, BG_PAINT);
+        canvasContext.add( new BackgroundPainter() );
+
+        // Key bindings
+        canvasContext.add( new KeyToCommand( CommandKeyBinding.DEFAULT_BINDINGS ) );
+
+        ////// Diagram Participants //////
+        PointerInteractor pi = new PointerInteractor(true, true, false, true, false, null);
+        pi.setBoxSelectMode(PickPolicy.PICK_INTERSECTING_OBJECTS);
+        canvasContext.add( pi );
+        canvasContext.add( selection = new Selection() );
+        canvasContext.add( new DiagramParticipant() );
+        canvasContext.add( itemPainter = new GalleryItemPainter() );
+        canvasContext.add( new ElementInteractor() );
+        canvasContext.add( new TooltipParticipant());
+
+        h.setHint(ElementPainter.KEY_SELECTION_FRAME_COLOR, Color.WHITE);
+        h.setHint(ElementPainter.KEY_SELECTION_CONTENT_COLOR, new Color(0.7f, 0.7f, 1.f, 0.5f));
+        h.setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);
+
+        // Adds DragInteractor & DropInteractor
+
+        // therefore has to be done before assertParticipantDependencies.
+        // Also, this must be invoked BEFORE SWTChassis chassis.setCanvasContext
+        // because otherwise setCanvasContext would be ran in the
+        // wrong thread (SWT) for AWTChassis.
+        chassis.getAWTComponent().setCanvasContext(canvasContext);
+
+        swtThread.asyncExec(() -> {
+            if (!chassis.isDisposed())
+                chassis.setCanvasContext(canvasContext);
+        });
+
+        canvasContext.assertParticipantDependencies();
+    }
+
+    @Override
+    public Control getControl() {
+        return chassis;
+    }
+
+    @Override
+    protected void inputChanged(Object input, Object oldInput) {
+        // Skip automatic refreshing at setInput to allow room for manual
+        // optimization in the client.
+        //refresh();
+    }
+
+    @Override
+    public ISelection getSelection() {
+        Set<IElement> sel = selection.getSelection(0);
+        if (sel.isEmpty())
+            return StructuredSelection.EMPTY;
+        List<Object> elements = new ArrayList<Object>(sel.size());
+        for (IElement e : sel)
+            elements.add(e.getHint(ElementHints.KEY_OBJECT));
+        return new StructuredSelection(elements);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void setSelection(ISelection selection, boolean reveal) {
+        List<Object> selectedObjects = Collections.EMPTY_LIST;
+        if (selection instanceof IStructuredSelection) {
+            selectedObjects = ((IStructuredSelection) selection).toList();
+        }
+
+        DataElementMap map = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
+        List<IElement> selectionElements = new ArrayList<IElement>();
+        for (Object o : selectedObjects)
+            selectionElements.add( map.getElement(diagram, o) );
+
+        this.selection.setSelection(0, selectionElements);
+    }
+
+    /**
+     * Refreshes this viewer completely with information freshly obtained from this
+     * viewer's model.
+     */
+    @Override
+    public void refresh() {
+        refresh(null);
+    }
+
+    public Object[] getFilteredElements() {
+        IStructuredContentProvider cp = (IStructuredContentProvider) getContentProvider();
+        if (cp == null)
+            return new Object[0];
+
+        Object[] elements = cp.getElements( getInput() );
+        Object[] filtered = filter( elements );
+        return filtered;
+    }
+
+    protected Object[] filter(Object[] items) {
+        if (filter != null) {
+            ArrayList<Object> filtered = new ArrayList<Object>(items.length);
+            Object root = getInput();
+            for (int i = 0; i < items.length; i++) {
+                if (!filter.select(this, root, items[i]))
+                    continue;
+                filtered.add(items[i]);
+            }
+            return filtered.toArray();
+        }
+        return items;
+    }
+
+    /**
+     * @param contentCallback a callback for receiving the final filtered
+     *        elements left visible
+     */
+    public void refresh(Consumer<Object[]> contentCallback) {
+        //System.out.println(this + ".refresh(" + contentCallback + ")");
+        Object[] elements = getFilteredElements();
+        refreshWithContent(elements);
+        if (contentCallback != null) {
+            contentCallback.accept(elements);
+        }
+    }
+
+    /**
+     * Set viewer contents to the specified objects.
+     * This method must be invoked from the SWT thread.
+     * 
+     * @param objects
+     */
+    public void refreshWithContent(final Object[] objects) {
+        if (!swtThread.currentThreadAccess())
+            throw new IllegalStateException("Not invoked from SWT thread");
+
+        //System.out.println(this + ".refreshWithContent(" + Arrays.toString(objects) + ")");
+
+        final Semaphore barrier = new Semaphore(0);
+        IThreadWorkQueue t = ctx.getThreadAccess();
+        ThreadUtils.asyncExec(t, new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    perform();
+                } finally {
+                    barrier.release();
+                }
+            }
+
+            public void perform() {
+                // $AWT-Thread-Begin$
+                //System.out.println(this + ".refreshWithContent(" + Arrays.toString(objects) + ") AWT WORK");
+
+                Object[] objectsCopy = Arrays.copyOf(objects, objects.length);
+
+                Set<Object> objs = new HashSet<Object>();
+                for (Object o : objectsCopy)
+                    objs.add(o);
+
+                // 1. Remove unused
+                for (IElement e : diagram.getSnapshot()) {
+                    Object backendObject = e.getHint(ElementHints.KEY_OBJECT);
+                    if (!objs.remove(backendObject)) {
+                        //System.out.println("Removing " + e);
+                        diagram.removeElement(e);
+                    }
+                }
+                for (int i = 0; i < objectsCopy.length; i++)
+                    if (!objs.contains(objectsCopy[i]))
+                        objectsCopy[i] = null;
+
+                // 2. Add new elements
+                for (Object o : objectsCopy) {
+                    if (o == null)
+                        continue;
+
+                    IElement e = Element.spawnNew(itemClass);
+                    e.setHint(ElementHints.KEY_OBJECT, o);
+//                    e.getElementClass().getSingleItem(Resize.class).resize(e, itemSize);
+                    ILabelProvider lp = (ILabelProvider) getLabelProvider();
+                    Image i = lp.getImage(o);
+                    if (i.getFeatures().contains(Feature.Volatile))
+                        i.addImageListener(imageListener);
+                    e.setHint(GalleryItemSGNode.KEY_IMAGE, i);
+
+                    // tooltips
+                    String tooltipText = lp.getToolTipText(o);
+                    java.awt.Image tooltipImage = lp.getToolTipImage(o);
+                    if (tooltipText != null || tooltipImage != null) {
+                        e.setHint(TooltipParticipant.TOOLTIP_KEY, tooltipProvider);
+                        if (tooltipText != null)
+                            e.setHint(GalleryTooltipProvider.TOOLTIP_TEXT, tooltipText);
+                        if (tooltipImage != null)
+                            e.setHint(GalleryTooltipProvider.TOOLTIP_IMAGE, tooltipImage);
+                    }
+                    diagram.addElement(e);
+
+                    e.getElementClass().getSingleItem(GalleryItemSGNode.class).update(e);
+
+//                    Image si = ImageUtils.createShadow(i, SHADOW, (int) itemSize.getWidth(), (int) itemSize.getHeight());
+//                    si = ImageUtils.createBuffer(si);
+//                    e.setHint(GalleryItemPainter.KEY_IMAGE_SHADOW, si);
+
+                    ElementUtils.setText(e, lp.getText(o));
+                    //System.out.println("Added: " + e);
+                }
+
+                // 3. Calculate maximum vertical space needed by current diagram element texts
+                refreshElementSizes();
+
+                ThreadUtils.asyncExec(swtThread, () -> resized(false));
+                // $AWT-Thread-End$
+            }
+        });
+
+        boolean done = false;
+        while (!done) {
+            try {
+                done = barrier.tryAcquire(10, TimeUnit.MILLISECONDS);
+                while (!done && display.readAndDispatch()) {
+                    done = barrier.tryAcquire();
+                }
+            } catch (InterruptedException e) {
+                done = true;
+            }
+        }
+    }
+
+    /**
+     * Invoke from canvas context thread only.
+     */
+    void refreshElementSizes() {
+        if (awtComponent == null) {
+            LOGGER.error("GalleryViewer.refreshElementSizes: awtComponent is null");
+            return;
+        }
+
+        //System.out.println(this + ".refreshElementSizes()");
+        // Calculate maximum vertical space needed by current diagram element texts
+        FontMetrics metrics = awtComponent.getFontMetrics(currentItemFont);
+        int fontHeight = metrics.getHeight();
+        Rectangle2D size = SWTDPIUtil.upscaleSwt(itemSize);
+        int maxWidth = (int) size.getWidth();
+        java.awt.Point targetSize = new java.awt.Point((int) size.getWidth(), (int) size.getHeight());
+        diagram.setHint(DiagramHints.KEY_ELEMENT_RASTER_TARGET_SIZE, targetSize);
+        int maxLinesNeeded = 0;
+        for (IElement el : diagram.getElements()) {
+            // This makes it possible to give the gallery item a reference size
+            // for caching rendered images in the correct size only.
+            // NOTE: currently this is not used in GalleryItemPainter since the
+            // target size is now propagated through the element class loading
+            // process through the diagram hint KEY_ELEMENT_RASTER_TARGET_SIZE.
+            el.setHint(GalleryItemSGNode.KEY_TARGET_IMAGE_SIZE, targetSize);
+
+            String text = ElementUtils.getText(el);
+            int linesNeeded = TextUtil.wordWrap(text, metrics, maxWidth).length;
+            maxLinesNeeded = Math.max(maxLinesNeeded, linesNeeded);
+            int textSpaceNeeded = fontHeight * linesNeeded + metrics.getMaxDescent();
+            Rectangle2D s = new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight() + textSpaceNeeded);
+            el.getElementClass().getSingleItem(Resize.class).resize(el, s);
+            //System.out.println(this + "  lines needed: " + linesNeeded + " = " + s);
+
+            el.getElementClass().getSingleItem(GalleryItemSGNode.class).update(el);
+        }
+        int maxTextSpaceNeeded = fontHeight * maxLinesNeeded + metrics.getMaxDescent();
+        maxItemSize = new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight() + maxTextSpaceNeeded);
+        //System.out.println(this + "[" + diagram.getElements().size() + "]: max lines needed: " + maxLinesNeeded + " = " + fontHeight*maxLinesNeeded + " pixels");
+    }
+
+    ImageListener imageListener = new ImageListener() {
+        @Override
+        public void onContentChangedNotification(Image image) {
+            //System.out.println(Thread.currentThread() + ": contentChanged(" + image + ")");
+            // Update the image of the element if the element is found.
+            for (final IElement el : diagram.getSnapshot()) {
+                Image i = GalleryItemSGNode.getImage(el);
+                if (image != i)
+                    continue;
+
+                ctx.getThreadAccess().asyncExec(() -> {
+                    //System.out.println(Thread.currentThread() + ": update scene graph(" + el + ")");
+                    // Update scene graph and repaint.
+                    if (!ctx.isDisposed()) {
+                        el.getElementClass().getSingleItem(GalleryItemSGNode.class).update(el);
+                        ctx.getContentContext().setDirty();
+                    }
+                });
+                break;
+            }
+        }
+    };
+
+    public void addDropSupport(final IDropTargetParticipant p) {
+        if (ctx.getThreadAccess().currentThreadAccess()) {
+            ctx.add(p);
+        } else {
+            ctx.getThreadAccess().asyncExec(() -> {
+                if (!ctx.isDisposed())
+                    ctx.add(p);
+            });
+        }
+    }
+
+    public void addDragSupport(final IDragSourceParticipant p) {
+        if (ctx.getThreadAccess().currentThreadAccess()) {
+            ctx.add(p);
+        } else {
+            ctx.getThreadAccess().asyncExec(() -> {
+                if (!ctx.isDisposed())
+                    ctx.add(p);
+            });
+        }
+    }
+
+    public CanvasContext getCanvasContext() {
+        return ctx;
+    }
+
+    public IDiagram getDiagram() {
+        return diagram;
+    }
+
+    //private final static Color BG_COLOR = new Color(0.3f, 0.3f, 1.0f, 0.35f);
+
+    static class GalleryItemPainter extends ElementPainter {
+        @Override
+        public void paintSelectionFrame(int selectionId, G2DParentNode elementNode, G2DParentNode selectionNode, IElement e, Color color) {
+            final Shape outline = ElementUtils.getElementBoundsOnDiagram(e);
+            Rectangle2D bounds = outline.getBounds2D();
+            GeometryUtils.expandRectangle(bounds, 2, 2, 2, 2);
+
+            ShapeNode shapenode = selectionNode.getOrCreateNode("shape_"+e.hashCode(), ShapeNode.class);
+            shapenode.setShape(bounds);
+            shapenode.setColor(new Color(0.3f, 0.3f, 1.0f, 0.25f));
+            shapenode.setFill(true);
+
+            // Paint selection before anything else in elementNode
+            selectionNode.setZIndex(Integer.MIN_VALUE);
+        }
+    }
+
+}