]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/gallery/GalleryViewer.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / gallery / GalleryViewer.java
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/gallery/GalleryViewer.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/gallery/GalleryViewer.java
new file mode 100644 (file)
index 0000000..d8ddad9
--- /dev/null
@@ -0,0 +1,716 @@
+/*******************************************************************************\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