X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fgallery%2FGalleryViewer.java;h=d09ecf4a6b6f753f7f0adbf57060b3a36ac987c1;hp=d8ddad9adb2dcf0ebd149f8bbf2d7c67cab1c5c5;hb=215cb2930ed3fb362b2951d1da3080abec976972;hpb=969bd23cab98a79ca9101af33334000879fb60c5 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 index d8ddad9ad..d09ecf4a6 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/gallery/GalleryViewer.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/gallery/GalleryViewer.java @@ -1,716 +1,693 @@ -/******************************************************************************* - * 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; - -/** - * @author Toni Kalajainen - * @author Tuukka Lehtonen - */ -public class GalleryViewer extends ContentViewer { - - /** - * 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. - * - *

- * This method will not refresh the viewer, this needs to be done separately - * using {@link #refresh()} or {@link #refresh(Consumer)}. - *

- * - * @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 sel = selection.getSelection(0); - if (sel.isEmpty()) - return StructuredSelection.EMPTY; - List elements = new ArrayList(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 selectedObjects = Collections.EMPTY_LIST; - if (selection instanceof IStructuredSelection) { - selectedObjects = ((IStructuredSelection) selection).toList(); - } - - DataElementMap map = diagram.getDiagramClass().getSingleItem(DataElementMap.class); - List selectionElements = new ArrayList(); - 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 filtered = new ArrayList(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 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 objs = new HashSet(); - 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) { - System.err.println("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); - } - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.g2d.gallery; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.TexturePaint; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.eclipse.jface.resource.FontRegistry; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ContentViewer; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.simantics.g2d.canvas.Hints; +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.canvas.impl.CanvasContext; +import org.simantics.g2d.chassis.SWTChassis; +import org.simantics.g2d.diagram.DiagramClass; +import org.simantics.g2d.diagram.DiagramHints; +import org.simantics.g2d.diagram.IDiagram; +import org.simantics.g2d.diagram.handler.DataElementMap; +import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy; +import org.simantics.g2d.diagram.handler.layout.FlowLayout; +import org.simantics.g2d.diagram.impl.Diagram; +import org.simantics.g2d.diagram.participant.DiagramParticipant; +import org.simantics.g2d.diagram.participant.ElementInteractor; +import org.simantics.g2d.diagram.participant.ElementPainter; +import org.simantics.g2d.diagram.participant.Selection; +import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor; +import org.simantics.g2d.dnd.IDragSourceParticipant; +import org.simantics.g2d.dnd.IDropTargetParticipant; +import org.simantics.g2d.element.ElementClass; +import org.simantics.g2d.element.ElementHints; +import org.simantics.g2d.element.ElementUtils; +import org.simantics.g2d.element.IElement; +import org.simantics.g2d.element.handler.Clickable; +import org.simantics.g2d.element.handler.Resize; +import org.simantics.g2d.element.handler.impl.DefaultTransform; +import org.simantics.g2d.element.handler.impl.Resizeable; +import org.simantics.g2d.element.handler.impl.TextImpl; +import org.simantics.g2d.element.impl.Element; +import org.simantics.g2d.image.Image; +import org.simantics.g2d.image.Image.Feature; +import org.simantics.g2d.image.Image.ImageListener; +import org.simantics.g2d.image.impl.Shadow.ShadowParameters; +import org.simantics.g2d.participant.BackgroundPainter; +import org.simantics.g2d.participant.KeyToCommand; +import org.simantics.g2d.participant.KeyUtil; +import org.simantics.g2d.participant.MouseUtil; +import org.simantics.g2d.participant.SymbolUtil; +import org.simantics.g2d.participant.TransformUtil; +import org.simantics.g2d.tooltip.TooltipParticipant; +import org.simantics.g2d.utils.FontHelper; +import org.simantics.scenegraph.g2d.G2DParentNode; +import org.simantics.scenegraph.g2d.events.command.CommandKeyBinding; +import org.simantics.scenegraph.g2d.nodes.ShapeNode; +import org.simantics.scenegraph.utils.GeometryUtils; +import org.simantics.scenegraph.utils.TextUtil; +import org.simantics.utils.datastructures.hints.IHintContext; +import org.simantics.utils.datastructures.hints.IHintContext.Key; +import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; +import org.simantics.utils.threads.AWTThread; +import org.simantics.utils.threads.IThreadWorkQueue; +import org.simantics.utils.threads.SWTThread; +import org.simantics.utils.threads.ThreadUtils; +import org.simantics.utils.threads.logger.ITask; +import org.simantics.utils.threads.logger.ThreadLogger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.simantics.utils.ui.SWTDPIUtil; + +/** + * @author Toni Kalajainen + * @author Tuukka Lehtonen + */ +public class GalleryViewer extends ContentViewer { + + private static final Logger LOGGER = LoggerFactory.getLogger(GalleryViewer.class); + /** + * A hint key for storing a GalleryViewer within the hint stack of + * {@link GalleryViewer}'s {@link ICanvasContext}. + */ + public static final Key KEY_VIEWER = new KeyOf(GalleryViewer.class, "GALLERY_VIEWER"); + + public static final ShadowParameters SHADOW = new ShadowParameters(0.5, Color.BLACK, 5); + + ViewerFilter filter; + IThreadWorkQueue swtThread; + Display display; + SWTChassis chassis; + Component awtComponent; + CanvasContext ctx; + Selection selection; + GalleryItemPainter itemPainter; + IDiagram diagram; + /** element size */ + Rectangle2D itemSize = new Rectangle2D.Double(0, 0, 50, 50); + Rectangle2D maxItemSize = itemSize.getBounds2D(); + FlowLayout fl = new FlowLayout(); + int hMargin = 5; + int vMargin = 5; + ElementClass itemClass; + FontRegistry fontRegistry; + Font currentItemFont; + + GalleryTooltipProvider tooltipProvider = new GalleryTooltipProvider(); + + /** Background paint */ + public static final Paint BG_PAINT; + static { + // Create checkerboard pattern paint with 5x5 square size + BufferedImage bi = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < 2; ++x) + for (int y = 0; y < 2; ++y) + bi.setRGB(x, y, (((x ^ y) & 1) == 0) ? 0xffffffff : 0xfffdfdfd); + BG_PAINT = new TexturePaint(bi, new Rectangle2D.Double(0, 0, 10, 10)); + //BG_PAINT = Color.WHITE; + } + + public GalleryViewer(Composite composite) { + this(composite, 0); + } + + public GalleryViewer(Composite composite, int style) { + super(); + display = composite.getDisplay(); + swtThread = SWTThread.getThreadAccess(composite); + chassis = new SWTChassis(composite, style) { + @Override + public Point computeSize(int wHint, int hHint, boolean changed) { + if (diagram == null) + return super.computeSize(wHint, hHint, changed); + + // Note: This code must take into account that FlowLayout expects to + // receive pixel coordinates, not SWT API coordinates. + + Rectangle2D rect; +// if (!changed) { +// rect = ElementUtils.getSurroundingElementBoundsOnDiagram(diagram.getSnapshot()); +// } +// else + { + Double wH = wHint==SWT.DEFAULT ? null : (double) SWTDPIUtil.upscaleSwt(wHint)-hMargin*2; + Double hH = hHint==SWT.DEFAULT ? null : (double) SWTDPIUtil.upscaleSwt(hHint)-vMargin*2; + rect = fl.computeSize(diagram, wH, hH); + SWTDPIUtil.downscaleSwt(rect, rect); + } + return new Point((int)rect.getWidth()+hMargin*2, (int)rect.getHeight()+vMargin*2); + } + }; + + // Create IDiagram for gallery + this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT); + diagram.setHint(FlowLayout.HGAP, 5.0); + diagram.setHint(FlowLayout.VGAP, 5.0); + diagram.setHint(DiagramHints.KEY_ELEMENT_RASTER_TARGET_SIZE, + new java.awt.Point((int) itemSize.getWidth(), (int) itemSize.getHeight())); + + // Create canvas context here in SWT thread but do not initialize it yet. + this.ctx = new CanvasContext(AWTThread.getThreadAccess()); + + chassis.populate(parameter -> { + awtComponent = parameter.getAWTComponent(); + + // Initialize the canvas context + ITask task = ThreadLogger.getInstance().begin("createCanvas"); + initializeCanvasContext(ctx); + task.finish(); + + // Initialize canvas context hint context with KEY_VIEWER key. + IHintContext hintCtx = ctx.getDefaultHintContext(); + hintCtx.setHint(KEY_VIEWER, GalleryViewer.this); + + // Set IDiagram for canvas context + hintCtx.setHint(DiagramHints.KEY_DIAGRAM, diagram); + + // Force layout + ThreadUtils.asyncExec(swtThread, () -> resized(false)); + }); + + chassis.addControlListener(new ControlListener() { + @Override + public void controlMoved(ControlEvent e) {} + @Override + public void controlResized(ControlEvent e) { + resized(); + }}); + + ITask task2 = ThreadLogger.getInstance().begin("fonts"); + try { + this.fontRegistry = FontHelper.getCurrentThemeFontRegistry(); + fontRegistry.addListener(fontRegistryListener); + currentItemFont = FontHelper.toAwt(fontRegistry, "org.simantics.gallery.itemfont"); + } catch (IllegalStateException e) { + // No workbench available, use SWT control fonts for viewer items. + org.eclipse.swt.graphics.Font f = chassis.getFont(); + currentItemFont = FontHelper.toAwt(f.getFontData()[0]); + } + task2.finish(); + + + itemClass = ElementClass.compile( + DefaultTransform.INSTANCE, + TextImpl.INSTANCE, + //TextAsTooltip.INSTANCE, + Clickable.INSTANCE, + Resizeable.UNCONSTRICTED, + new GalleryItemSGNode(currentItemFont) + ); + + chassis.addListener(SWT.Dispose, new Listener() { + @Override + public void handleEvent(Event event) { + if (fontRegistry != null) + fontRegistry.removeListener(fontRegistryListener); + + // Prevent memory leaks. + ThreadUtils.asyncExec(ctx.getThreadAccess(), () -> { + chassis.getAWTComponent().setCanvasContext(null); + ctx.dispose(); + }); + } + }); + } + + IPropertyChangeListener fontRegistryListener = new IPropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent event) { + FontData fdn = ((FontData[]) event.getNewValue())[0]; + //System.out.println(event.getSource() + ": font changed: " + event.getProperty() + ": " + fdn); + currentItemFont = FontHelper.toAwt(fdn); + itemClass.getSingleItem(GalleryItemSGNode.class).setFont(currentItemFont); + // FIXME: a bug exists in this case. The group size will not be refreshed even though the sizes of the gallery items are recalculated and changed. + ThreadUtils.asyncExec(swtThread, () -> resized(true)); + } + }; + + /** + * Set alignment of elements (left, right, center, fill) + * @param align + */ + public void setAlign(FlowLayout.Align align) { + diagram.setHint(FlowLayout.ALIGN, align); + resized(); + } + + /** + * Sets the content filter of this viewer. The filter will be invoked after + * getting input data elements from the content provider and before setting + * the new content for the viewer. Any previously set filter will be + * replaced. + * + *

+ * This method will not refresh the viewer, this needs to be done separately + * using {@link #refresh()} or {@link #refresh(Consumer)}. + *

+ * + * @param filter the new filter + */ + public void setFilter(ViewerFilter filter) { + if (filter == this.filter || (filter != null && filter.equals(this.filter))) + return; + + this.filter = filter; + } + + public ViewerFilter getFilter() { + return filter; + } + + private void resized() { + resized(false); + } + + private void resized(final boolean refreshElementSizes) { + //System.out.println(this + ".resized(" + refreshElementSizes + ")"); + if (chassis.isDisposed()) + return; + org.eclipse.swt.graphics.Rectangle b = SWTDPIUtil.upscaleSwt(chassis.getBounds()); + //System.out.println("chassis bounds: " + b); + final Rectangle2D bounds = new Rectangle2D.Double(hMargin, vMargin, b.width-hMargin*2, b.height-vMargin*2); + ctx.getThreadAccess().asyncExec(() -> { + if (ctx.isDisposed()) + return; + if (diagram == null) + return; + //System.out.println(this + ".resized(" + refreshElementSizes + ") AWT update"); + if (refreshElementSizes) + refreshElementSizes(); + fl.layout(diagram, bounds); + + // Makes sure RTreeNode is marked dirty and everything is + // properly repainted. + if (itemPainter != null) + itemPainter.updateAll(); + ctx.getContentContext().setDirty(); + }); + } + + /** + * Invoke only from AWT thread. + * + * @param thread + * @return + */ + private void initializeCanvasContext(final CanvasContext canvasContext) { + // Create canvas context and a layer of interactors + final IHintContext h = canvasContext.getDefaultHintContext(); + + // Support & Util Participants + canvasContext.add( new TransformUtil() ); + + canvasContext.add( new MouseUtil() ); + canvasContext.add( new KeyUtil() ); + canvasContext.add( new SymbolUtil() ); + + // Grid & Ruler & Background + h.setHint(Hints.KEY_BACKGROUND_PAINT, BG_PAINT); + canvasContext.add( new BackgroundPainter() ); + + // Key bindings + canvasContext.add( new KeyToCommand( CommandKeyBinding.DEFAULT_BINDINGS ) ); + + ////// Diagram Participants ////// + PointerInteractor pi = new PointerInteractor(true, true, false, true, false, null); + pi.setBoxSelectMode(PickPolicy.PICK_INTERSECTING_OBJECTS); + canvasContext.add( pi ); + canvasContext.add( selection = new Selection() ); + canvasContext.add( new DiagramParticipant() ); + canvasContext.add( itemPainter = new GalleryItemPainter() ); + canvasContext.add( new ElementInteractor() ); + canvasContext.add( new TooltipParticipant()); + + h.setHint(ElementPainter.KEY_SELECTION_FRAME_COLOR, Color.WHITE); + h.setHint(ElementPainter.KEY_SELECTION_CONTENT_COLOR, new Color(0.7f, 0.7f, 1.f, 0.5f)); + h.setHint(Hints.KEY_TOOL, Hints.POINTERTOOL); + + // Adds DragInteractor & DropInteractor + + // therefore has to be done before assertParticipantDependencies. + // Also, this must be invoked BEFORE SWTChassis chassis.setCanvasContext + // because otherwise setCanvasContext would be ran in the + // wrong thread (SWT) for AWTChassis. + chassis.getAWTComponent().setCanvasContext(canvasContext); + + swtThread.asyncExec(() -> { + if (!chassis.isDisposed()) + chassis.setCanvasContext(canvasContext); + }); + + canvasContext.assertParticipantDependencies(); + } + + @Override + public Control getControl() { + return chassis; + } + + @Override + protected void inputChanged(Object input, Object oldInput) { + // Skip automatic refreshing at setInput to allow room for manual + // optimization in the client. + //refresh(); + } + + @Override + public ISelection getSelection() { + Set sel = selection.getSelection(0); + if (sel.isEmpty()) + return StructuredSelection.EMPTY; + List elements = new ArrayList(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 selectedObjects = Collections.EMPTY_LIST; + if (selection instanceof IStructuredSelection) { + selectedObjects = ((IStructuredSelection) selection).toList(); + } + + DataElementMap map = diagram.getDiagramClass().getSingleItem(DataElementMap.class); + List selectionElements = new ArrayList(); + 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 filtered = new ArrayList(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 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 objs = new HashSet(); + for (Object o : objectsCopy) + objs.add(o); + + // 1. Remove unused + for (IElement e : diagram.getSnapshot()) { + Object backendObject = e.getHint(ElementHints.KEY_OBJECT); + if (!objs.remove(backendObject)) { + //System.out.println("Removing " + e); + diagram.removeElement(e); + } + } + for (int i = 0; i < objectsCopy.length; i++) + if (!objs.contains(objectsCopy[i])) + objectsCopy[i] = null; + + // 2. Add new elements + for (Object o : objectsCopy) { + if (o == null) + continue; + + IElement e = Element.spawnNew(itemClass); + e.setHint(ElementHints.KEY_OBJECT, o); +// e.getElementClass().getSingleItem(Resize.class).resize(e, itemSize); + ILabelProvider lp = (ILabelProvider) getLabelProvider(); + Image i = lp.getImage(o); + if (i.getFeatures().contains(Feature.Volatile)) + i.addImageListener(imageListener); + e.setHint(GalleryItemSGNode.KEY_IMAGE, i); + + // tooltips + String tooltipText = lp.getToolTipText(o); + java.awt.Image tooltipImage = lp.getToolTipImage(o); + if (tooltipText != null || tooltipImage != null) { + e.setHint(TooltipParticipant.TOOLTIP_KEY, tooltipProvider); + if (tooltipText != null) + e.setHint(GalleryTooltipProvider.TOOLTIP_TEXT, tooltipText); + if (tooltipImage != null) + e.setHint(GalleryTooltipProvider.TOOLTIP_IMAGE, tooltipImage); + } + diagram.addElement(e); + + e.getElementClass().getSingleItem(GalleryItemSGNode.class).update(e); + +// Image si = ImageUtils.createShadow(i, SHADOW, (int) itemSize.getWidth(), (int) itemSize.getHeight()); +// si = ImageUtils.createBuffer(si); +// e.setHint(GalleryItemPainter.KEY_IMAGE_SHADOW, si); + + ElementUtils.setText(e, lp.getText(o)); + //System.out.println("Added: " + e); + } + + // 3. Calculate maximum vertical space needed by current diagram element texts + refreshElementSizes(); + + ThreadUtils.asyncExec(swtThread, () -> resized(false)); + // $AWT-Thread-End$ + } + }); + + boolean done = false; + while (!done) { + try { + done = barrier.tryAcquire(10, TimeUnit.MILLISECONDS); + while (!done && display.readAndDispatch()) { + done = barrier.tryAcquire(); + } + } catch (InterruptedException e) { + done = true; + } + } + } + + /** + * Invoke from canvas context thread only. + */ + void refreshElementSizes() { + if (awtComponent == null) { + LOGGER.error("GalleryViewer.refreshElementSizes: awtComponent is null"); + return; + } + + //System.out.println(this + ".refreshElementSizes()"); + // Calculate maximum vertical space needed by current diagram element texts + FontMetrics metrics = awtComponent.getFontMetrics(currentItemFont); + int fontHeight = metrics.getHeight(); + Rectangle2D size = SWTDPIUtil.upscaleSwt(itemSize); + int maxWidth = (int) size.getWidth(); + java.awt.Point targetSize = new java.awt.Point((int) size.getWidth(), (int) size.getHeight()); + diagram.setHint(DiagramHints.KEY_ELEMENT_RASTER_TARGET_SIZE, targetSize); + int maxLinesNeeded = 0; + for (IElement el : diagram.getElements()) { + // This makes it possible to give the gallery item a reference size + // for caching rendered images in the correct size only. + // NOTE: currently this is not used in GalleryItemPainter since the + // target size is now propagated through the element class loading + // process through the diagram hint KEY_ELEMENT_RASTER_TARGET_SIZE. + el.setHint(GalleryItemSGNode.KEY_TARGET_IMAGE_SIZE, targetSize); + + String text = ElementUtils.getText(el); + int linesNeeded = TextUtil.wordWrap(text, metrics, maxWidth).length; + maxLinesNeeded = Math.max(maxLinesNeeded, linesNeeded); + int textSpaceNeeded = fontHeight * linesNeeded + metrics.getMaxDescent(); + Rectangle2D s = new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight() + textSpaceNeeded); + el.getElementClass().getSingleItem(Resize.class).resize(el, s); + //System.out.println(this + " lines needed: " + linesNeeded + " = " + s); + + el.getElementClass().getSingleItem(GalleryItemSGNode.class).update(el); + } + int maxTextSpaceNeeded = fontHeight * maxLinesNeeded + metrics.getMaxDescent(); + maxItemSize = new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight() + maxTextSpaceNeeded); + //System.out.println(this + "[" + diagram.getElements().size() + "]: max lines needed: " + maxLinesNeeded + " = " + fontHeight*maxLinesNeeded + " pixels"); + } + + ImageListener imageListener = new ImageListener() { + @Override + public void onContentChangedNotification(Image image) { + //System.out.println(Thread.currentThread() + ": contentChanged(" + image + ")"); + // Update the image of the element if the element is found. + for (final IElement el : diagram.getSnapshot()) { + Image i = GalleryItemSGNode.getImage(el); + if (image != i) + continue; + + ctx.getThreadAccess().asyncExec(() -> { + //System.out.println(Thread.currentThread() + ": update scene graph(" + el + ")"); + // Update scene graph and repaint. + if (!ctx.isDisposed()) { + el.getElementClass().getSingleItem(GalleryItemSGNode.class).update(el); + ctx.getContentContext().setDirty(); + } + }); + break; + } + } + }; + + public void addDropSupport(final IDropTargetParticipant p) { + if (ctx.getThreadAccess().currentThreadAccess()) { + ctx.add(p); + } else { + ctx.getThreadAccess().asyncExec(() -> { + if (!ctx.isDisposed()) + ctx.add(p); + }); + } + } + + public void addDragSupport(final IDragSourceParticipant p) { + if (ctx.getThreadAccess().currentThreadAccess()) { + ctx.add(p); + } else { + ctx.getThreadAccess().asyncExec(() -> { + if (!ctx.isDisposed()) + ctx.add(p); + }); + } + } + + public CanvasContext getCanvasContext() { + return ctx; + } + + public IDiagram getDiagram() { + return diagram; + } + + //private final static Color BG_COLOR = new Color(0.3f, 0.3f, 1.0f, 0.35f); + + static class GalleryItemPainter extends ElementPainter { + @Override + public void paintSelectionFrame(int selectionId, G2DParentNode elementNode, G2DParentNode selectionNode, IElement e, Color color) { + final Shape outline = ElementUtils.getElementBoundsOnDiagram(e); + Rectangle2D bounds = outline.getBounds2D(); + GeometryUtils.expandRectangle(bounds, 2, 2, 2, 2); + + ShapeNode shapenode = selectionNode.getOrCreateNode("shape_"+e.hashCode(), ShapeNode.class); + shapenode.setShape(bounds); + shapenode.setColor(new Color(0.3f, 0.3f, 1.0f, 0.25f)); + shapenode.setFill(true); + + // Paint selection before anything else in elementNode + selectionNode.setZIndex(Integer.MIN_VALUE); + } + } + +}