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