]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/gallery/GalleryViewer.java
Small but effective HiDPI fixes for platform G2D
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / gallery / GalleryViewer.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.g2d.gallery;
13
14 import java.awt.Color;
15 import java.awt.Component;
16 import java.awt.Font;
17 import java.awt.FontMetrics;
18 import java.awt.Paint;
19 import java.awt.Shape;
20 import java.awt.TexturePaint;
21 import java.awt.geom.Rectangle2D;
22 import java.awt.image.BufferedImage;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Set;
29 import java.util.concurrent.Semaphore;
30 import java.util.concurrent.TimeUnit;
31 import java.util.function.Consumer;
32
33 import org.eclipse.jface.resource.FontRegistry;
34 import org.eclipse.jface.util.IPropertyChangeListener;
35 import org.eclipse.jface.util.PropertyChangeEvent;
36 import org.eclipse.jface.viewers.ContentViewer;
37 import org.eclipse.jface.viewers.ISelection;
38 import org.eclipse.jface.viewers.IStructuredContentProvider;
39 import org.eclipse.jface.viewers.IStructuredSelection;
40 import org.eclipse.jface.viewers.StructuredSelection;
41 import org.eclipse.jface.viewers.ViewerFilter;
42 import org.eclipse.swt.SWT;
43 import org.eclipse.swt.events.ControlEvent;
44 import org.eclipse.swt.events.ControlListener;
45 import org.eclipse.swt.graphics.FontData;
46 import org.eclipse.swt.graphics.Point;
47 import org.eclipse.swt.widgets.Composite;
48 import org.eclipse.swt.widgets.Control;
49 import org.eclipse.swt.widgets.Display;
50 import org.eclipse.swt.widgets.Event;
51 import org.eclipse.swt.widgets.Listener;
52 import org.simantics.g2d.canvas.Hints;
53 import org.simantics.g2d.canvas.ICanvasContext;
54 import org.simantics.g2d.canvas.impl.CanvasContext;
55 import org.simantics.g2d.chassis.SWTChassis;
56 import org.simantics.g2d.diagram.DiagramClass;
57 import org.simantics.g2d.diagram.DiagramHints;
58 import org.simantics.g2d.diagram.IDiagram;
59 import org.simantics.g2d.diagram.handler.DataElementMap;
60 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
61 import org.simantics.g2d.diagram.handler.layout.FlowLayout;
62 import org.simantics.g2d.diagram.impl.Diagram;
63 import org.simantics.g2d.diagram.participant.DiagramParticipant;
64 import org.simantics.g2d.diagram.participant.ElementInteractor;
65 import org.simantics.g2d.diagram.participant.ElementPainter;
66 import org.simantics.g2d.diagram.participant.Selection;
67 import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
68 import org.simantics.g2d.dnd.IDragSourceParticipant;
69 import org.simantics.g2d.dnd.IDropTargetParticipant;
70 import org.simantics.g2d.element.ElementClass;
71 import org.simantics.g2d.element.ElementHints;
72 import org.simantics.g2d.element.ElementUtils;
73 import org.simantics.g2d.element.IElement;
74 import org.simantics.g2d.element.handler.Clickable;
75 import org.simantics.g2d.element.handler.Resize;
76 import org.simantics.g2d.element.handler.impl.DefaultTransform;
77 import org.simantics.g2d.element.handler.impl.Resizeable;
78 import org.simantics.g2d.element.handler.impl.TextImpl;
79 import org.simantics.g2d.element.impl.Element;
80 import org.simantics.g2d.image.Image;
81 import org.simantics.g2d.image.Image.Feature;
82 import org.simantics.g2d.image.Image.ImageListener;
83 import org.simantics.g2d.image.impl.Shadow.ShadowParameters;
84 import org.simantics.g2d.participant.BackgroundPainter;
85 import org.simantics.g2d.participant.KeyToCommand;
86 import org.simantics.g2d.participant.KeyUtil;
87 import org.simantics.g2d.participant.MouseUtil;
88 import org.simantics.g2d.participant.SymbolUtil;
89 import org.simantics.g2d.participant.TransformUtil;
90 import org.simantics.g2d.tooltip.TooltipParticipant;
91 import org.simantics.g2d.utils.FontHelper;
92 import org.simantics.scenegraph.g2d.G2DParentNode;
93 import org.simantics.scenegraph.g2d.events.command.CommandKeyBinding;
94 import org.simantics.scenegraph.g2d.nodes.ShapeNode;
95 import org.simantics.scenegraph.utils.GeometryUtils;
96 import org.simantics.scenegraph.utils.TextUtil;
97 import org.simantics.utils.datastructures.hints.IHintContext;
98 import org.simantics.utils.datastructures.hints.IHintContext.Key;
99 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
100 import org.simantics.utils.threads.AWTThread;
101 import org.simantics.utils.threads.IThreadWorkQueue;
102 import org.simantics.utils.threads.SWTThread;
103 import org.simantics.utils.threads.ThreadUtils;
104 import org.simantics.utils.threads.logger.ITask;
105 import org.simantics.utils.threads.logger.ThreadLogger;
106 import org.simantics.utils.ui.SWTDPIUtil;
107
108 /**
109  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
110  * @author Tuukka Lehtonen
111  */
112 public class GalleryViewer extends ContentViewer {
113
114     /**
115      * A hint key for storing a GalleryViewer within the hint stack of
116      * {@link GalleryViewer}'s {@link ICanvasContext}.
117      */
118     public static final Key              KEY_VIEWER      = new KeyOf(GalleryViewer.class, "GALLERY_VIEWER");
119
120     public static final ShadowParameters SHADOW          = new ShadowParameters(0.5, Color.BLACK, 5);
121
122     ViewerFilter                         filter;
123     IThreadWorkQueue                     swtThread;
124     Display                              display;
125     SWTChassis                           chassis;
126     Component                            awtComponent;
127     CanvasContext                        ctx;
128     Selection                            selection;
129     GalleryItemPainter                   itemPainter;
130     IDiagram                             diagram;
131     /** element size */
132     Rectangle2D                          itemSize    = new Rectangle2D.Double(0, 0, 50, 50);
133     Rectangle2D                          maxItemSize = itemSize.getBounds2D();
134     FlowLayout                           fl          = new FlowLayout();
135     int                                  hMargin     = 5;
136     int                                  vMargin     = 5;
137     ElementClass                         itemClass;
138     FontRegistry                         fontRegistry;
139     Font                                 currentItemFont;
140
141     GalleryTooltipProvider               tooltipProvider = new GalleryTooltipProvider();
142
143     /** Background paint */
144     public static final Paint BG_PAINT;
145     static {
146         // Create checkerboard pattern paint with 5x5 square size
147         BufferedImage bi = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB);
148         for (int x = 0; x < 2; ++x)
149             for (int y = 0; y < 2; ++y)
150                 bi.setRGB(x, y, (((x ^ y) & 1) == 0) ? 0xffffffff : 0xfffdfdfd);
151         BG_PAINT = new TexturePaint(bi, new Rectangle2D.Double(0, 0, 10, 10));
152         //BG_PAINT = Color.WHITE;
153     }
154
155     public GalleryViewer(Composite composite) {
156         this(composite, 0);
157     }
158
159     public GalleryViewer(Composite composite, int style) {
160         super();
161         display = composite.getDisplay();
162         swtThread = SWTThread.getThreadAccess(composite);
163         chassis = new SWTChassis(composite, style) {
164             @Override
165             public Point computeSize(int wHint, int hHint, boolean changed) {
166                 if (diagram == null)
167                     return super.computeSize(wHint, hHint, changed);
168
169                 // Note: This code must take into account that FlowLayout expects to 
170                 // receive pixel coordinates, not SWT API coordinates.
171
172                 Rectangle2D rect;
173 //                if (!changed) {
174 //                    rect = ElementUtils.getSurroundingElementBoundsOnDiagram(diagram.getSnapshot());
175 //                }
176 //                else
177                 {
178                     Double wH = wHint==SWT.DEFAULT ? null : (double) SWTDPIUtil.upscaleSwt(wHint)-hMargin*2;
179                     Double hH = hHint==SWT.DEFAULT ? null : (double) SWTDPIUtil.upscaleSwt(hHint)-vMargin*2;
180                     rect = fl.computeSize(diagram, wH, hH);
181                     SWTDPIUtil.downscaleSwt(rect, rect);
182                 }
183                 return new Point((int)rect.getWidth()+hMargin*2, (int)rect.getHeight()+vMargin*2);
184             }
185         };
186
187         // Create IDiagram for gallery
188         this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
189         diagram.setHint(FlowLayout.HGAP, 5.0);
190         diagram.setHint(FlowLayout.VGAP, 5.0);
191         diagram.setHint(DiagramHints.KEY_ELEMENT_RASTER_TARGET_SIZE,
192                 new java.awt.Point((int) itemSize.getWidth(), (int) itemSize.getHeight()));
193
194         // Create canvas context here in SWT thread but do not initialize it yet.
195         this.ctx = new CanvasContext(AWTThread.getThreadAccess());
196
197         chassis.populate(parameter -> {
198             awtComponent = parameter.getAWTComponent();
199
200             // Initialize the canvas context
201             ITask task = ThreadLogger.getInstance().begin("createCanvas");
202             initializeCanvasContext(ctx);
203             task.finish();
204
205             // Initialize canvas context hint context with KEY_VIEWER key.
206             IHintContext hintCtx = ctx.getDefaultHintContext();
207             hintCtx.setHint(KEY_VIEWER, GalleryViewer.this);
208
209             // Set IDiagram for canvas context
210             hintCtx.setHint(DiagramHints.KEY_DIAGRAM, diagram);
211
212             // Force layout
213             ThreadUtils.asyncExec(swtThread, () -> resized(false));
214         });
215
216         chassis.addControlListener(new ControlListener() {
217             @Override
218             public void controlMoved(ControlEvent e) {}
219             @Override
220             public void controlResized(ControlEvent e) {
221                 resized();
222             }});
223
224         ITask task2 = ThreadLogger.getInstance().begin("fonts");
225         try {
226             this.fontRegistry = FontHelper.getCurrentThemeFontRegistry();
227             fontRegistry.addListener(fontRegistryListener);
228             currentItemFont = FontHelper.toAwt(fontRegistry, "org.simantics.gallery.itemfont");
229         } catch (IllegalStateException e) {
230             // No workbench available, use SWT control fonts for viewer items.
231             org.eclipse.swt.graphics.Font f = chassis.getFont();
232             currentItemFont = FontHelper.toAwt(f.getFontData()[0]);
233         }
234         task2.finish();
235
236
237         itemClass = ElementClass.compile(
238                 DefaultTransform.INSTANCE,
239                 TextImpl.INSTANCE,
240                 //TextAsTooltip.INSTANCE,
241                 Clickable.INSTANCE,
242                 Resizeable.UNCONSTRICTED,
243                 new GalleryItemSGNode(currentItemFont)
244         );
245
246         chassis.addListener(SWT.Dispose, new Listener() {
247             @Override
248             public void handleEvent(Event event) {
249                 if (fontRegistry != null)
250                     fontRegistry.removeListener(fontRegistryListener);
251
252                 // Prevent memory leaks.
253                 ThreadUtils.asyncExec(ctx.getThreadAccess(), () -> {
254                     chassis.getAWTComponent().setCanvasContext(null);
255                     ctx.dispose();
256                 });
257             }
258         });
259     }
260
261     IPropertyChangeListener fontRegistryListener = new IPropertyChangeListener() {
262         @Override
263         public void propertyChange(PropertyChangeEvent event) {
264             FontData fdn = ((FontData[]) event.getNewValue())[0];
265             //System.out.println(event.getSource() + ": font changed: " + event.getProperty() + ": " + fdn);
266             currentItemFont = FontHelper.toAwt(fdn);
267             itemClass.getSingleItem(GalleryItemSGNode.class).setFont(currentItemFont);
268             // 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.
269             ThreadUtils.asyncExec(swtThread, () -> resized(true));
270         }
271     };
272
273     /**
274      * Set alignment of elements (left, right, center, fill)
275      * @param align
276      */
277     public void setAlign(FlowLayout.Align align) {
278         diagram.setHint(FlowLayout.ALIGN, align);
279         resized();
280     }
281
282     /**
283      * Sets the content filter of this viewer. The filter will be invoked after
284      * getting input data elements from the content provider and before setting
285      * the new content for the viewer. Any previously set filter will be
286      * replaced.
287      *
288      * <p>
289      * This method will not refresh the viewer, this needs to be done separately
290      * using {@link #refresh()} or {@link #refresh(Consumer)}.
291      * </p>
292      *
293      * @param filter the new filter
294      */
295     public void setFilter(ViewerFilter filter) {
296         if (filter == this.filter || (filter != null && filter.equals(this.filter)))
297             return;
298
299         this.filter = filter;
300     }
301
302     public ViewerFilter getFilter() {
303         return filter;
304     }
305
306     private void resized() {
307         resized(false);
308     }
309
310     private void resized(final boolean refreshElementSizes) {
311         //System.out.println(this + ".resized(" + refreshElementSizes + ")");
312         if (chassis.isDisposed())
313             return;
314         org.eclipse.swt.graphics.Rectangle b = SWTDPIUtil.upscaleSwt(chassis.getBounds());
315         //System.out.println("chassis bounds: " + b);
316         final Rectangle2D bounds = new Rectangle2D.Double(hMargin, vMargin, b.width-hMargin*2, b.height-vMargin*2);
317         ctx.getThreadAccess().asyncExec(() -> {
318             if (ctx.isDisposed())
319                 return;
320             if (diagram == null)
321                 return;
322             //System.out.println(this + ".resized(" + refreshElementSizes + ") AWT update");
323             if (refreshElementSizes)
324                 refreshElementSizes();
325             fl.layout(diagram, bounds);
326
327             // Makes sure RTreeNode is marked dirty and everything is
328             // properly repainted.
329             if (itemPainter != null)
330                 itemPainter.updateAll();
331             ctx.getContentContext().setDirty();
332         });
333     }
334
335     /**
336      * Invoke only from AWT thread.
337      * 
338      * @param thread
339      * @return
340      */
341     private void initializeCanvasContext(final CanvasContext canvasContext) {
342         // Create canvas context and a layer of interactors
343         final IHintContext h = canvasContext.getDefaultHintContext();
344
345         // Support & Util Participants
346         canvasContext.add( new TransformUtil() );
347
348         canvasContext.add( new MouseUtil() );
349         canvasContext.add( new KeyUtil() );
350         canvasContext.add( new SymbolUtil() );
351
352         // Grid & Ruler & Background
353         h.setHint(Hints.KEY_BACKGROUND_PAINT, BG_PAINT);
354         canvasContext.add( new BackgroundPainter() );
355
356         // Key bindings
357         canvasContext.add( new KeyToCommand( CommandKeyBinding.DEFAULT_BINDINGS ) );
358
359         ////// Diagram Participants //////
360         PointerInteractor pi = new PointerInteractor(true, true, false, true, false, null);
361         pi.setBoxSelectMode(PickPolicy.PICK_INTERSECTING_OBJECTS);
362         canvasContext.add( pi );
363         canvasContext.add( selection = new Selection() );
364         canvasContext.add( new DiagramParticipant() );
365         canvasContext.add( itemPainter = new GalleryItemPainter() );
366         canvasContext.add( new ElementInteractor() );
367         canvasContext.add( new TooltipParticipant());
368
369         h.setHint(ElementPainter.KEY_SELECTION_FRAME_COLOR, Color.WHITE);
370         h.setHint(ElementPainter.KEY_SELECTION_CONTENT_COLOR, new Color(0.7f, 0.7f, 1.f, 0.5f));
371         h.setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);
372
373         // Adds DragInteractor & DropInteractor
374
375         // therefore has to be done before assertParticipantDependencies.
376         // Also, this must be invoked BEFORE SWTChassis chassis.setCanvasContext
377         // because otherwise setCanvasContext would be ran in the
378         // wrong thread (SWT) for AWTChassis.
379         chassis.getAWTComponent().setCanvasContext(canvasContext);
380
381         swtThread.asyncExec(() -> {
382             if (!chassis.isDisposed())
383                 chassis.setCanvasContext(canvasContext);
384         });
385
386         canvasContext.assertParticipantDependencies();
387     }
388
389     @Override
390     public Control getControl() {
391         return chassis;
392     }
393
394     @Override
395     protected void inputChanged(Object input, Object oldInput) {
396         // Skip automatic refreshing at setInput to allow room for manual
397         // optimization in the client.
398         //refresh();
399     }
400
401     @Override
402     public ISelection getSelection() {
403         Set<IElement> sel = selection.getSelection(0);
404         if (sel.isEmpty())
405             return StructuredSelection.EMPTY;
406         List<Object> elements = new ArrayList<Object>(sel.size());
407         for (IElement e : sel)
408             elements.add(e.getHint(ElementHints.KEY_OBJECT));
409         return new StructuredSelection(elements);
410     }
411
412     @SuppressWarnings("unchecked")
413     @Override
414     public void setSelection(ISelection selection, boolean reveal) {
415         List<Object> selectedObjects = Collections.EMPTY_LIST;
416         if (selection instanceof IStructuredSelection) {
417             selectedObjects = ((IStructuredSelection) selection).toList();
418         }
419
420         DataElementMap map = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
421         List<IElement> selectionElements = new ArrayList<IElement>();
422         for (Object o : selectedObjects)
423             selectionElements.add( map.getElement(diagram, o) );
424
425         this.selection.setSelection(0, selectionElements);
426     }
427
428     /**
429      * Refreshes this viewer completely with information freshly obtained from this
430      * viewer's model.
431      */
432     @Override
433     public void refresh() {
434         refresh(null);
435     }
436
437     public Object[] getFilteredElements() {
438         IStructuredContentProvider cp = (IStructuredContentProvider) getContentProvider();
439         if (cp == null)
440             return new Object[0];
441
442         Object[] elements = cp.getElements( getInput() );
443         Object[] filtered = filter( elements );
444         return filtered;
445     }
446
447     protected Object[] filter(Object[] items) {
448         if (filter != null) {
449             ArrayList<Object> filtered = new ArrayList<Object>(items.length);
450             Object root = getInput();
451             for (int i = 0; i < items.length; i++) {
452                 if (!filter.select(this, root, items[i]))
453                     continue;
454                 filtered.add(items[i]);
455             }
456             return filtered.toArray();
457         }
458         return items;
459     }
460
461     /**
462      * @param contentCallback a callback for receiving the final filtered
463      *        elements left visible
464      */
465     public void refresh(Consumer<Object[]> contentCallback) {
466         //System.out.println(this + ".refresh(" + contentCallback + ")");
467         Object[] elements = getFilteredElements();
468         refreshWithContent(elements);
469         if (contentCallback != null) {
470             contentCallback.accept(elements);
471         }
472     }
473
474     /**
475      * Set viewer contents to the specified objects.
476      * This method must be invoked from the SWT thread.
477      * 
478      * @param objects
479      */
480     public void refreshWithContent(final Object[] objects) {
481         if (!swtThread.currentThreadAccess())
482             throw new IllegalStateException("Not invoked from SWT thread");
483
484         //System.out.println(this + ".refreshWithContent(" + Arrays.toString(objects) + ")");
485
486         final Semaphore barrier = new Semaphore(0);
487         IThreadWorkQueue t = ctx.getThreadAccess();
488         ThreadUtils.asyncExec(t, new Runnable() {
489             @Override
490             public void run() {
491                 try {
492                     perform();
493                 } finally {
494                     barrier.release();
495                 }
496             }
497
498             public void perform() {
499                 // $AWT-Thread-Begin$
500                 //System.out.println(this + ".refreshWithContent(" + Arrays.toString(objects) + ") AWT WORK");
501
502                 Object[] objectsCopy = Arrays.copyOf(objects, objects.length);
503
504                 Set<Object> objs = new HashSet<Object>();
505                 for (Object o : objectsCopy)
506                     objs.add(o);
507
508                 // 1. Remove unused
509                 for (IElement e : diagram.getSnapshot()) {
510                     Object backendObject = e.getHint(ElementHints.KEY_OBJECT);
511                     if (!objs.remove(backendObject)) {
512                         //System.out.println("Removing " + e);
513                         diagram.removeElement(e);
514                     }
515                 }
516                 for (int i = 0; i < objectsCopy.length; i++)
517                     if (!objs.contains(objectsCopy[i]))
518                         objectsCopy[i] = null;
519
520                 // 2. Add new elements
521                 for (Object o : objectsCopy) {
522                     if (o == null)
523                         continue;
524
525                     IElement e = Element.spawnNew(itemClass);
526                     e.setHint(ElementHints.KEY_OBJECT, o);
527 //                    e.getElementClass().getSingleItem(Resize.class).resize(e, itemSize);
528                     ILabelProvider lp = (ILabelProvider) getLabelProvider();
529                     Image i = lp.getImage(o);
530                     if (i.getFeatures().contains(Feature.Volatile))
531                         i.addImageListener(imageListener);
532                     e.setHint(GalleryItemSGNode.KEY_IMAGE, i);
533
534                     // tooltips
535                     String tooltipText = lp.getToolTipText(o);
536                     java.awt.Image tooltipImage = lp.getToolTipImage(o);
537                     if (tooltipText != null || tooltipImage != null) {
538                         e.setHint(TooltipParticipant.TOOLTIP_KEY, tooltipProvider);
539                         if (tooltipText != null)
540                             e.setHint(GalleryTooltipProvider.TOOLTIP_TEXT, tooltipText);
541                         if (tooltipImage != null)
542                             e.setHint(GalleryTooltipProvider.TOOLTIP_IMAGE, tooltipImage);
543                     }
544                     diagram.addElement(e);
545
546                     e.getElementClass().getSingleItem(GalleryItemSGNode.class).update(e);
547
548 //                    Image si = ImageUtils.createShadow(i, SHADOW, (int) itemSize.getWidth(), (int) itemSize.getHeight());
549 //                    si = ImageUtils.createBuffer(si);
550 //                    e.setHint(GalleryItemPainter.KEY_IMAGE_SHADOW, si);
551
552                     ElementUtils.setText(e, lp.getText(o));
553                     //System.out.println("Added: " + e);
554                 }
555
556                 // 3. Calculate maximum vertical space needed by current diagram element texts
557                 refreshElementSizes();
558
559                 ThreadUtils.asyncExec(swtThread, () -> resized(false));
560                 // $AWT-Thread-End$
561             }
562         });
563
564         boolean done = false;
565         while (!done) {
566             try {
567                 done = barrier.tryAcquire(10, TimeUnit.MILLISECONDS);
568                 while (!done && display.readAndDispatch()) {
569                     done = barrier.tryAcquire();
570                 }
571             } catch (InterruptedException e) {
572                 done = true;
573             }
574         }
575     }
576
577     /**
578      * Invoke from canvas context thread only.
579      */
580     void refreshElementSizes() {
581         if (awtComponent == null) {
582             System.err.println("GalleryViewer.refreshElementSizes: awtComponent is null");
583             return;
584         }
585
586         //System.out.println(this + ".refreshElementSizes()");
587         // Calculate maximum vertical space needed by current diagram element texts
588         FontMetrics metrics = awtComponent.getFontMetrics(currentItemFont);
589         int fontHeight = metrics.getHeight();
590         Rectangle2D size = SWTDPIUtil.upscaleSwt(itemSize);
591         int maxWidth = (int) size.getWidth();
592         java.awt.Point targetSize = new java.awt.Point((int) size.getWidth(), (int) size.getHeight());
593         diagram.setHint(DiagramHints.KEY_ELEMENT_RASTER_TARGET_SIZE, targetSize);
594         int maxLinesNeeded = 0;
595         for (IElement el : diagram.getElements()) {
596             // This makes it possible to give the gallery item a reference size
597             // for caching rendered images in the correct size only.
598             // NOTE: currently this is not used in GalleryItemPainter since the
599             // target size is now propagated through the element class loading
600             // process through the diagram hint KEY_ELEMENT_RASTER_TARGET_SIZE.
601             el.setHint(GalleryItemSGNode.KEY_TARGET_IMAGE_SIZE, targetSize);
602
603             String text = ElementUtils.getText(el);
604             int linesNeeded = TextUtil.wordWrap(text, metrics, maxWidth).length;
605             maxLinesNeeded = Math.max(maxLinesNeeded, linesNeeded);
606             int textSpaceNeeded = fontHeight * linesNeeded + metrics.getMaxDescent();
607             Rectangle2D s = new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight() + textSpaceNeeded);
608             el.getElementClass().getSingleItem(Resize.class).resize(el, s);
609             //System.out.println(this + "  lines needed: " + linesNeeded + " = " + s);
610
611             el.getElementClass().getSingleItem(GalleryItemSGNode.class).update(el);
612         }
613         int maxTextSpaceNeeded = fontHeight * maxLinesNeeded + metrics.getMaxDescent();
614         maxItemSize = new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight() + maxTextSpaceNeeded);
615         //System.out.println(this + "[" + diagram.getElements().size() + "]: max lines needed: " + maxLinesNeeded + " = " + fontHeight*maxLinesNeeded + " pixels");
616     }
617
618     ImageListener imageListener = new ImageListener() {
619         @Override
620         public void onContentChangedNotification(Image image) {
621             //System.out.println(Thread.currentThread() + ": contentChanged(" + image + ")");
622             // Update the image of the element if the element is found.
623             for (final IElement el : diagram.getSnapshot()) {
624                 Image i = GalleryItemSGNode.getImage(el);
625                 if (image != i)
626                     continue;
627
628                 ctx.getThreadAccess().asyncExec(() -> {
629                     //System.out.println(Thread.currentThread() + ": update scene graph(" + el + ")");
630                     // Update scene graph and repaint.
631                     el.getElementClass().getSingleItem(GalleryItemSGNode.class).update(el);
632                     ctx.getContentContext().setDirty();
633                 });
634                 break;
635             }
636         }
637     };
638
639     public void addDropSupport(final IDropTargetParticipant p) {
640         if (ctx.getThreadAccess().currentThreadAccess()) {
641             ctx.add(p);
642         } else {
643             ctx.getThreadAccess().asyncExec(() -> {
644                 if (!ctx.isDisposed())
645                     ctx.add(p);
646             });
647         }
648     }
649
650     public void addDragSupport(final IDragSourceParticipant p) {
651         if (ctx.getThreadAccess().currentThreadAccess()) {
652             ctx.add(p);
653         } else {
654             ctx.getThreadAccess().asyncExec(() -> {
655                 if (!ctx.isDisposed())
656                     ctx.add(p);
657             });
658         }
659     }
660
661     public CanvasContext getCanvasContext() {
662         return ctx;
663     }
664
665     public IDiagram getDiagram() {
666         return diagram;
667     }
668
669     //private final static Color BG_COLOR = new Color(0.3f, 0.3f, 1.0f, 0.35f);
670
671     static class GalleryItemPainter extends ElementPainter {
672         @Override
673         public void paintSelectionFrame(G2DParentNode elementNode, G2DParentNode selectionNode, IElement e, Color color) {
674             final Shape outline = ElementUtils.getElementBoundsOnDiagram(e);
675             Rectangle2D bounds = outline.getBounds2D();
676             GeometryUtils.expandRectangle(bounds, 2, 2, 2, 2);
677
678             ShapeNode shapenode = selectionNode.getOrCreateNode("shape_"+e.hashCode(), ShapeNode.class);
679             shapenode.setShape(bounds);
680             shapenode.setColor(new Color(0.3f, 0.3f, 1.0f, 0.25f));
681             shapenode.setFill(true);
682
683             // Paint selection before anything else in elementNode
684             selectionNode.setZIndex(Integer.MIN_VALUE);
685         }
686     }
687
688 }