-/*******************************************************************************\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;
+
+/**
+ * @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) {
+// System.out.println("chassis compute size: " + wHint + ", " + hHint + ", " + changed);
+
+ if (diagram == null)
+ return super.computeSize(wHint, hHint, changed);
+
+ Rectangle2D rect;
+// if (!changed) {
+// rect = ElementUtils.getSurroundingElementBoundsOnDiagram(diagram.getSnapshot());
+// }
+// else
+ {
+ Double wH = wHint==SWT.DEFAULT ? null : (double) wHint-vMargin-vMargin;
+ Double hH = hHint==SWT.DEFAULT ? null : (double) hHint-hMargin-hMargin;
+ rect = fl.computeSize(diagram, wH, hH);
+ }
+ return new Point((int)rect.getMaxX()+hMargin*2, (int)rect.getMaxY()+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, new Runnable() {
+ @Override
+ public void run() {
+ 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(), new Runnable() {
+ @Override
+ public void run() {
+ 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, new Runnable() {
+ @Override
+ public void run() {
+ 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 = chassis.getBounds();
+ final Rectangle2D bounds = new Rectangle2D.Double(hMargin, vMargin, b.width-hMargin*2, b.height-vMargin*2);
+ ctx.getThreadAccess().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ 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(new Runnable() {
+ @Override
+ public void run() {
+ 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, new Runnable() {
+ @Override
+ public void run() {
+ 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();
+ int maxWidth = (int) itemSize.getWidth();
+ Rectangle2D size = itemSize;
+ java.awt.Point targetSize = new java.awt.Point((int) itemSize.getWidth(), (int) itemSize.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_REFERENCE_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(new Runnable() {
+ @Override
+ public void run() {
+ //System.out.println(Thread.currentThread() + ": update scene graph(" + el + ")");
+ // Update scene graph and repaint.
+ 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(new Runnable() {
+ @Override
+ public void run() {
+ if (!ctx.isDisposed())
+ ctx.add(p);
+ }
+ });
+ }
+ }
+
+ public void addDragSupport(final IDragSourceParticipant p) {
+ if (ctx.getThreadAccess().currentThreadAccess()) {
+ ctx.add(p);
+ } else {
+ ctx.getThreadAccess().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ 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(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);
+ }
+ }
+
+}