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