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