+/*******************************************************************************\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