001    /**
002     * Copyright (c) 2009, Piet Blok
003     * All rights reserved.
004     *
005     * Redistribution and use in source and binary forms, with or without
006     * modification, are permitted provided that the following conditions
007     * are met:
008     *
009     *   * Redistributions of source code must retain the above copyright
010     *     notice, this list of conditions and the following disclaimer.
011     *   * Redistributions in binary form must reproduce the above
012     *     copyright notice, this list of conditions and the following
013     *     disclaimer in the documentation and/or other materials provided
014     *     with the distribution.
015     *   * Neither the name of the copyright holder nor the names of the
016     *     contributors may be used to endorse or promote products derived
017     *     from this software without specific prior written permission.
018     *
019     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
020     * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
021     * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
022     * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
023     * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
024     * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
025     * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
026     * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
027     * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
029     * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030     */
031    
032    package org.pbjar.jxlayer.plaf.ext;
033    
034    import java.awt.Color;
035    import java.awt.Component;
036    import java.awt.Container;
037    import java.awt.Dimension;
038    import java.awt.Graphics;
039    import java.awt.Graphics2D;
040    import java.awt.LayoutManager;
041    import java.awt.Point;
042    import java.awt.Rectangle;
043    import java.awt.RenderingHints;
044    import java.awt.Shape;
045    import java.awt.SystemColor;
046    import java.awt.Transparency;
047    import java.awt.geom.AffineTransform;
048    import java.awt.geom.Area;
049    import java.awt.image.BufferedImage;
050    import java.beans.PropertyChangeEvent;
051    import java.beans.PropertyChangeListener;
052    import java.util.HashMap;
053    import java.util.HashSet;
054    import java.util.Map;
055    import java.util.Set;
056    
057    import javax.swing.JComponent;
058    import javax.swing.RepaintManager;
059    import javax.swing.SwingUtilities;
060    import javax.swing.border.Border;
061    import javax.swing.event.ChangeEvent;
062    import javax.swing.event.ChangeListener;
063    import javax.swing.text.JTextComponent;
064    import javax.swing.text.GlyphView.GlyphPainter;
065    
066    import org.jdesktop.jxlayer.JXLayer;
067    import org.jdesktop.jxlayer.plaf.AbstractBufferedLayerUI;
068    import org.jdesktop.jxlayer.plaf.LayerUI;
069    import org.jdesktop.swingx.ForwardingRepaintManager;
070    import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;
071    import org.pbjar.jxlayer.plaf.ext.transform.TransformLayout;
072    import org.pbjar.jxlayer.plaf.ext.transform.TransformModel;
073    import org.pbjar.jxlayer.plaf.ext.transform.TransformRPMAnnotation;
074    import org.pbjar.jxlayer.plaf.ext.transform.TransformRPMFallBack;
075    import org.pbjar.jxlayer.plaf.ext.transform.TransformRPMSwingX;
076    import org.pbjar.jxlayer.repaint.RepaintManagerProvider;
077    import org.pbjar.jxlayer.repaint.RepaintManagerUtils;
078    import org.pbjar.jxlayer.repaint.WrappedRepaintManager;
079    
080    import com.sun.java.swing.SwingUtilities3;
081    
082    /**
083     * This class provides for all necessary functionality when using
084     * transformations in a {@link LayerUI}.
085     * <p/>
086     * Some implementation details:
087     * <p/>
088     * <ul>
089     * <li>
090     * It extends {@link MouseEventUI} because, when applying transformations, the
091     * whereabouts of child components on screen (device space) do not necessarily
092     * match the location according to their bounds as set by layout managers
093     * (component space). So, mouse events must always be redirected to the intended
094     * recipients.</li>
095     * <li>
096     * When enabled, this implementation sets a different {@link LayoutManager} to
097     * be used by {@link JXLayer}.</li> Instead of setting the size of the view to
098     * {@link JXLayer}'s inner area, it sets the size of the view to the view's
099     * <em>preferred</em> size and centers it in the inner area. Also, when
100     * calculating the preferred size of {@link JXLayer}, it transforms the normally
101     * calculated size with the {@link AffineTransform} returned from
102     * {@link #getPreferredTransform(Dimension, JXLayer)}.
103     * <li>
104     * This implementation allocates a fresh {@link BufferedImage} the size of the
105     * clip area, each time that the {@link #paint(Graphics, JComponent)} method is
106     * invoked. This is different from the implementation of
107     * {@link AbstractBufferedLayerUI}, that maintains a cached image, the size of
108     * the view. An important reason to not follow the
109     * {@link AbstractBufferedLayerUI} strategy is that, when applying scaling
110     * transformations with a large scaling factor, a {@link OutOfMemoryError} may
111     * be thrown because it will try to allocate a buffer of an extreme size, even
112     * if not all of its contents will actually be visible on the screen.</li>
113     * <li>
114     * Rather than configuring the screen graphics object, the image's graphics
115     * object is configured through {@link #configureGraphics(Graphics2D, JXLayer)}.
116     * </li>
117     * <li>
118     * Regardless of whether or not the view is opaque, a background color is
119     * painted. It is obtained from the first component upwards in the hierarchy
120     * starting with the view, that is opaque. If an opaque component is not found,
121     * the background color of the layer is used. Painting the background is
122     * necessary to prevent visual artifacts when the transformation is changed
123     * dynamically.</li>
124     * <li>
125     * Rendering hints may be set with {@link #setRenderingHints(Map)},
126     * {@link #addRenderingHint(java.awt.RenderingHints.Key, Object)} and
127     * {@link #addRenderingHints(Map)}.</li>
128     * </ul>
129     * 
130     * <p/>
131     * Known limitations:
132     * <p/>
133     * <ol>
134     * <li>
135     * In Java versions <b>before Java 6u10</b>, this implementation employs a
136     * custom {@link RepaintManager} in order to have descendant's repaint requests
137     * propagated up to the {@link JXLayer} ancestor. This {@link RepaintManager}
138     * will work well with and without other {@link RepaintManager} that are either
139     * subclasses of the {@link WrappedRepaintManager} or SwingX's
140     * {@link ForwardingRepaintManager}. Other {@link RepaintManager}s may cause
141     * conflicts.
142     * <p/>
143     * In Java versions <b>6u10 or higher</b>, an attempt will be made to use the
144     * new RepaintManager delegate facility that has been designed for JavaFX.
145     * </li>
146     * <li>
147     * Transformations will be applied on the whole of the content of the
148     * {@link JXLayer}. The result is that {@link Border}s and other content within
149     * {@link JXLayer}'s insets will generally either be invisible, or will be
150     * rendered in a very undesirable way. If you want a {@link Border} to be
151     * transformed together with {@link JXLayer}'s view, that border should be set
152     * on the view instead. On the other hand, if you want the {@link Border} not to
153     * be transformed, that border must be set on {@link JXLayer}'s parent.</li>
154     * </ol>
155     * 
156     * <p/>
157     * <b>Note:</b> A {@link TransformUI} instance cannot be shared and can be set
158     * to a single {@link JXLayer} instance only.
159     * 
160     * 
161     * @author Piet Blok
162     * 
163     */
164    public class TransformUI extends MouseEventUI<JComponent> {
165    
166        /**
167         * A delegate {@link RepaintManager} that can be set on the view of a
168         * {@link JXLayer} in Java versions starting with Java 6u10.
169         * <p>
170         * For older Java versions,
171         * {@link RepaintManager#setCurrentManager(RepaintManager)} will be used
172         * with either {@link TransformRPMFallBack} or {@link TransformRPMSwingX}.
173         * </p>
174         */
175        protected static class TransformRepaintManager extends RepaintManager {
176    
177            private TransformRepaintManager() {
178    
179            }
180    
181            /**
182             * Finds the JXLayer ancestor and have the ancestor marked as dirty with
183             * the transformed rectangle via the current {@link RepaintManager}.
184             */
185            @Override
186            public void addDirtyRegion(JComponent aComponent, int x, int y, int w,
187                    int h) {
188                if (aComponent.isShowing()) {
189                    JXLayer<JComponent> layer = findJXLayer(aComponent);
190                    TransformUI ui = (TransformUI) layer.getUI();
191                    Point point = aComponent.getLocationOnScreen();
192                    SwingUtilities.convertPointFromScreen(point, layer);
193                    Rectangle transformPortRegion = ui.transform(new Rectangle(x
194                            + point.x, y + point.y, w, h), layer);
195                    RepaintManager.currentManager(layer).addDirtyRegion(layer,
196                            transformPortRegion.x, transformPortRegion.y,
197                            transformPortRegion.width, transformPortRegion.height);
198                }
199            }
200    
201            /**
202             * Finds the JXLayer ancestor and have ancestor marked invalid via the
203             * current {@link RepaintManager}.
204             */
205            @Override
206            public void addInvalidComponent(JComponent invalidComponent) {
207                JXLayer<JComponent> layer = findJXLayer(invalidComponent);
208                RepaintManager.currentManager(layer).addInvalidComponent(layer);
209            }
210    
211            /**
212             * Find the ancestor {@link JXLayer} instance.
213             * 
214             * @param aComponent
215             *            a component
216             * @return the ancestor {@link JXLayer} instance
217             */
218            @SuppressWarnings("unchecked")
219            private JXLayer<JComponent> findJXLayer(JComponent aComponent) {
220                JXLayer<?> layer = (JXLayer<?>) SwingUtilities.getAncestorOfClass(
221                        JXLayer.class, aComponent);
222                if (layer != null) {
223                    if (layer.getUI() instanceof TransformUI) {
224                        return (JXLayer<JComponent>) layer;
225                    } else {
226                        return findJXLayer(layer);
227                    }
228                }
229                throw new Error("No parent JXLayer with TransformUI found");
230            }
231    
232        }
233    
234        private static final LayoutManager transformLayout = new TransformLayout();
235    
236        private static final String KEY_VIEW = "view";
237    
238        private static final boolean delegatePossible;
239    
240        private static final RepaintManager wrappedManager = new TransformRepaintManager();
241    
242        static {
243            boolean value;
244            try {
245                SwingUtilities3.class.getMethod("setDelegateRepaintManager",
246                        JComponent.class, RepaintManager.class);
247                value = true;
248            } catch (Throwable t) {
249                value = false;
250            }
251            delegatePossible = value;
252            System.out
253                    .println("Java "
254                            + System.getProperty("java.version")
255                            + " "
256                            + System.getProperty("java.vm.version")
257                            + (delegatePossible ? ": RepaintManager delegate facility for JavaFX will be used."
258                                    : ": RepaintManager.setCurrentManager() will be used."));
259        }
260    
261        /**
262         * {@link JTextComponent} and its descendants have some caret position
263         * problems when used inside a transformed {@link JXLayer}. When you plan to
264         * use {@link JTextComponent}(s) inside the hierarchy of a transformed
265         * {@link JXLayer}, call this method in an early stage, before instantiating
266         * any {@link JTextComponent} .
267         * <p>
268         * It executes the following method:
269         * 
270         * <pre>
271         * System.setProperty(&quot;i18n&quot;, Boolean.TRUE.toString());
272         * </pre>
273         * 
274         * As a result, a {@link GlyphPainter} will be selected that uses floating
275         * point instead of fixed point calculations.
276         * </p>
277         */
278        public static void prepareForJTextComponent() {
279            System.setProperty("i18n", Boolean.TRUE.toString());
280        }
281    
282        private final PropertyChangeListener viewChangeListener = new PropertyChangeListener() {
283    
284            @Override
285            public void propertyChange(PropertyChangeEvent evt) {
286                setView((JComponent) evt.getNewValue());
287            }
288    
289        };
290    
291        private JComponent view;
292    
293        private final ChangeListener changeListener = new ChangeListener() {
294    
295            @Override
296            public void stateChanged(ChangeEvent e) {
297                revalidateLayer();
298            }
299        };
300    
301        private final RepaintManagerProvider rpmProvider = new RepaintManagerProvider() {
302    
303            @Override
304            public Class<? extends ForwardingRepaintManager> getForwardingRepaintManagerClass() {
305                return TransformRPMSwingX.class;
306            }
307    
308            @Override
309            public Class<? extends WrappedRepaintManager> getWrappedRepaintManagerClass() {
310                return TransformRPMFallBack.class;
311            }
312    
313            @Override
314            public boolean isAdequate(Class<? extends RepaintManager> manager) {
315                return manager.isAnnotationPresent(TransformRPMAnnotation.class);
316            }
317    
318        };
319    
320        private TransformModel transformModel;
321    
322        private LayoutManager originalLayout;
323    
324        private final Map<RenderingHints.Key, Object> renderingHints = new HashMap<RenderingHints.Key, Object>();
325    
326        private final Set<JComponent> originalDoubleBuffered = new HashSet<JComponent>();
327    
328        /**
329         * Construct a {@link TransformUI} with a {@link DefaultTransformModel}.
330         */
331        public TransformUI() {
332            this(new DefaultTransformModel());
333        }
334    
335        /**
336         * Construct a {@link TransformUI} with a specified model.
337         * 
338         * @param model
339         *            the model
340         */
341        public TransformUI(TransformModel model) {
342            super();
343            this.setModel(model);
344        }
345    
346        /**
347         * Add one rendering hint to the currently active rendering hints.
348         * 
349         * @param key
350         *            the key
351         * @param value
352         *            the value
353         */
354        public void addRenderingHint(RenderingHints.Key key, Object value) {
355            this.renderingHints.put(key, value);
356        }
357    
358        /**
359         * Add new rendering hints to the currently active rendering hints;
360         * 
361         * @param hints
362         *            the new rendering hints
363         */
364        public void addRenderingHints(Map<RenderingHints.Key, Object> hints) {
365            this.renderingHints.putAll(hints);
366        }
367    
368        /**
369         * Get the {@link TransformModel}.
370         * 
371         * @return the {@link TransformModel}
372         * @see #setModel(TransformModel)
373         */
374        public final TransformModel getModel() {
375            return transformModel;
376        }
377    
378        /**
379         * Get a preferred {@link AffineTransform}. This method will typically be
380         * invoked by programs that calculate a preferred size.
381         * <p>
382         * The {@code size} argument will be used to compute anchor values for some
383         * types of transformations. If the {@code size} argument is {@code null} a
384         * value of (0,0) is used for the anchor.
385         * </p>
386         * <p>
387         * In {@code enabled} state this method is delegated to the
388         * {@link TransformModel} that has been set. Otherwise {@code null} will be
389         * returned.
390         * </p>
391         * 
392         * @param size
393         *            a {@link Dimension} instance to be used for an anchor or
394         *            {@code null}
395         * @param layer
396         *            the {@link JXLayer}.
397         * @return a {@link AffineTransform} instance or {@code null}
398         */
399        public AffineTransform getPreferredTransform(Dimension size,
400                JXLayer<JComponent> layer) {
401    
402            return this.isEnabled() ? this.transformModel.getPreferredTransform(
403                    size, layer) : null;
404        }
405    
406        /**
407         * Overridden to replace the {@link LayoutManager}, to add some listeners
408         * and to ensure that an appropriate {@link RepaintManager} is installed.
409         * 
410         * @see #uninstallUI(JComponent)
411         */
412        @Override
413        public void installUI(JComponent component) {
414            super.installUI(component);
415            JXLayer<JComponent> installedLayer = this.getInstalledLayer();
416            originalLayout = installedLayer.getLayout();
417            installedLayer.addPropertyChangeListener(KEY_VIEW,
418                    this.viewChangeListener);
419            installedLayer.setLayout(transformLayout);
420            setView(installedLayer.getView());
421            if (!delegatePossible) {
422                RepaintManagerUtils.ensureRepaintManagerSet(installedLayer,
423                        rpmProvider);
424            }
425        }
426    
427        /**
428         * {@inheritDoc}
429         * <p>
430         * This implementation does the following:
431         * <ol>
432         * <li>
433         * A {@link BufferedImage} is created the size of the clip bounds of the
434         * argument graphics object.</li>
435         * <li>
436         * A Graphics object is obtained from the image.</li>
437         * <li>
438         * The image is filled with a background color.</li>
439         * <li>
440         * The image graphics is translated according to x and y of the clip bounds.
441         * </li>
442         * <li>
443         * The clip from the argument graphics object is set to the image graphics.</li>
444         * <li>
445         * {@link #configureGraphics(Graphics2D, JXLayer)} is invoked with the image
446         * graphics as an argument.</li>
447         * <li>
448         * {@link #paintLayer(Graphics2D, JXLayer)} is invoked with the image
449         * graphics as an argument.</li>
450         * <li>
451         * The image graphics is disposed.</li>
452         * <li>
453         * The image is drawn on the argument graphics object.</li>
454         * </ol>
455         */
456        @SuppressWarnings("unchecked")
457        @Override
458        public final void paint(Graphics g, JComponent component) {
459            Graphics2D g2 = (Graphics2D) g;
460            JXLayer<JComponent> layer = (JXLayer<JComponent>) component;
461            Shape clip = g2.getClip();
462            Rectangle clipBounds = g2.getClipBounds();
463            BufferedImage buffer = layer.getGraphicsConfiguration()
464                    .createCompatibleImage(clipBounds.width, clipBounds.height,
465                            Transparency.OPAQUE);
466            Graphics2D g3 = buffer.createGraphics();
467            try {
468                g3.setColor(this.getBackgroundColor(layer));
469                g3.fillRect(0, 0, buffer.getWidth(), buffer.getHeight());
470                g3.translate(-clipBounds.x, -clipBounds.y);
471                g3.setClip(clip);
472                configureGraphics(g3, layer);
473                paintLayer(g3, layer);
474            } catch (Throwable t) {
475                /*
476                 * Under some rare circumstances, the graphics engine may throw a
477                 * transformation exception like this:
478                 * 
479                 * sun.dc.pr.PRError: setPenT4: invalid pen transformation
480                 * (singular)
481                 * 
482                 * As far as I understand this happens when the result of the
483                 * transformation has a zero sized surface.
484                 * 
485                 * It will happen for example when shear X and shear Y are both set
486                 * to 1.
487                 * 
488                 * It will also happen when scale X or scale Y are set to 0.
489                 * 
490                 * Since this Exception only seems to be thrown under the condition
491                 * of a zero sized painting surface, no harm is done. Therefore the
492                 * error logging below has been commented out, but remain in the
493                 * source for the case that someone wants to investigate this
494                 * phenomenon in more depth.
495                 * 
496                 * The Exception however MUST be caught, not only to be able dispose
497                 * the image's graphics object, but also to prevent that JXLayer
498                 * enters a problematic state (the isPainting flag would not be
499                 * reset).
500                 */
501                // System.err.println(t);
502                // AffineTransform at = g3.getTransform();
503                // System.err.println(at);
504                // System.err.println("scaleX = " + at.getScaleX() + " scaleY = "
505                // + at.getScaleY() + " shearX = " + at.getShearX()
506                // + " shearY = " + at.getShearY());
507            } finally {
508                g3.dispose();
509            }
510            g2.drawImage(buffer, clipBounds.x, clipBounds.y, null);
511            setDirty(false);
512        }
513    
514        /**
515         * Overridden to also trigger {@link JComponent#revalidate()} and
516         * {@link JComponent#repaint()}.
517         */
518        @Override
519        public void setEnabled(boolean enabled) {
520            super.setEnabled(enabled);
521            setView(enabled ? getInstalledLayer().getView() : null);
522            revalidateLayer();
523        }
524    
525        /**
526         * Set a new {@link TransformModel}. The new model may not be {@code null}.
527         * 
528         * @param transformModel
529         *            the new model
530         * @throws NullPointerException
531         *             if transformModel is {@code null}
532         * @see #getModel()
533         */
534        public final void setModel(TransformModel transformModel)
535                throws NullPointerException {
536            if (transformModel == null) {
537                throw new NullPointerException("The TransformModel may not be null");
538            }
539            if (this.transformModel != null) {
540                this.transformModel.removeChangeListener(this.changeListener);
541            }
542            this.transformModel = transformModel;
543            this.transformModel.addChangeListener(this.changeListener);
544            revalidateLayer();
545        }
546    
547        /**
548         * Replace the currently active rendering hints with new hints.
549         * 
550         * @param hints
551         *            the new rendering hints or {@code null} to clear all rendering
552         *            hints
553         */
554        public void setRenderingHints(Map<RenderingHints.Key, Object> hints) {
555            this.renderingHints.clear();
556            if (hints != null) {
557                this.renderingHints.putAll(hints);
558            }
559        }
560    
561        /**
562         * Primarily intended for use by {@link RepaintManager}.
563         * 
564         * @param rect
565         *            a rectangle
566         * @param layer
567         *            the layer
568         * @return the argument rectangle if no {@link AffineTransform} is
569         *         available, else a new rectangle
570         */
571        public final Rectangle transform(Rectangle rect, JXLayer<JComponent> layer) {
572            AffineTransform at = getTransform(layer);
573            if (at == null) {
574                return rect;
575            } else {
576                Area area = new Area(rect);
577                area.transform(at);
578                return area.getBounds();
579            }
580        }
581    
582        /**
583         * Overridden to restore the original {@link LayoutManager} and remove some
584         * listeners.
585         */
586        @Override
587        public void uninstallUI(JComponent c) {
588            JXLayer<JComponent> installedLayer = this.getInstalledLayer();
589            installedLayer.removePropertyChangeListener(KEY_VIEW,
590                    this.viewChangeListener);
591            installedLayer.setLayout(originalLayout);
592            setView(null);
593            super.uninstallUI(c);
594        }
595    
596        /**
597         * Mark {@link TransformUI} as dirty if the LookAndFeel was changed.
598         * 
599         * @param layer
600         *            the {@link JXLayer} this {@link TransformUI} is set to
601         */
602        @Override
603        public void updateUI(JXLayer<JComponent> layer) {
604            setDirty(true);
605        }
606    
607        /**
608         * Get the most suitable background color.
609         * 
610         * @param layer
611         * @return
612         */
613        private Color getBackgroundColor(JXLayer<JComponent> layer) {
614            Container colorProvider = layer.getView() == null ? layer : layer
615                    .getView();
616            while (colorProvider != null && !colorProvider.isOpaque()) {
617                colorProvider = colorProvider.getParent();
618            }
619            return colorProvider == null ? SystemColor.desktop : colorProvider
620                    .getBackground();
621        }
622    
623        private void revalidateLayer() {
624            JXLayer<JComponent> installedLayer = this.getInstalledLayer();
625            if (installedLayer != null) {
626                installedLayer.revalidate();
627                installedLayer.repaint();
628            }
629        }
630    
631        /**
632         * Set a complete hierarchy to non double buffered and remember the
633         * components that were double buffered.
634         * 
635         * @param component
636         */
637        private void setToNoDoubleBuffering(Component component) {
638            if (component instanceof JComponent) {
639                JComponent jComp = (JComponent) component;
640                if (jComp.isDoubleBuffered()) {
641                    originalDoubleBuffered.add(jComp);
642                    jComp.setDoubleBuffered(false);
643                }
644            }
645            if (component instanceof Container) {
646                Container container = (Container) component;
647                for (int index = 0; index < container.getComponentCount(); index++) {
648                    setToNoDoubleBuffering(container.getComponent(index));
649                }
650            }
651        }
652    
653        private void setView(JComponent view) {
654            if (delegatePossible) {
655                if (this.view != null) {
656                    SwingUtilities3.setDelegateRepaintManager(this.view, null);
657                }
658            }
659            this.view = view;
660            if (delegatePossible) {
661                if (this.view != null) {
662                    SwingUtilities3.setDelegateRepaintManager(this.view,
663                            wrappedManager);
664                }
665            }
666            setDirty(true);
667        }
668    
669        /**
670         * Get the rendering hints.
671         * 
672         * @return the rendering hints
673         * @see #setRenderingHints(Map)
674         * @see #addRenderingHints(Map)
675         * @see #addRenderingHint(java.awt.RenderingHints.Key, Object)
676         */
677        @Override
678        protected Map<RenderingHints.Key, Object> getRenderingHints(
679                JXLayer<JComponent> layer) {
680            return renderingHints;
681        }
682    
683        /**
684         * Get the {@link AffineTransform} customized for the {@code layer}
685         * argument.
686         * <p>
687         * In {@code enabled} state this method is delegated to the
688         * {@link TransformModel} that has been set. Otherwise {@code null} will be
689         * returned.
690         * </p>
691         */
692        @Override
693        protected final AffineTransform getTransform(JXLayer<JComponent> layer) {
694            return isEnabled() ? transformModel.getTransform(layer) : null;
695        }
696    
697        /**
698         * If the view of the {@link JXLayer} is (partly) obscured by its parent
699         * (this is the case when the size of the view (in component space) is
700         * larger than the size of the {@link JXLayer}), the obscured parts will not
701         * be painted by the super implementation. Therefore, only under this
702         * condition, a special painting technique is executed:
703         * <ol>
704         * <li>
705         * All descendants of the {@link JXLayer} are temporarily set to non double
706         * buffered.</li>
707         * <li>
708         * The graphics object is translated for the X and Y coordinates of the
709         * view.</li>
710         * <li>
711         * The view is painted.</li>
712         * <li>
713         * The original double buffered property is restored for all descendants.</li>
714         * </ol>
715         * <p>
716         * In all other cases, the super method is invoked.
717         * </p>
718         * <p>
719         * The {@code g2} argument is a graphics object obtained from a
720         * {@link BufferedImage}.
721         * </p>
722         * 
723         * @see #paint(Graphics, JComponent) </p>
724         */
725        @Override
726        protected final void paintLayer(Graphics2D g2, JXLayer<JComponent> layer) {
727            JComponent view = layer.getView();
728            if (view != null) {
729                if (view.getX() < 0 || view.getY() < 0) {
730                    setToNoDoubleBuffering(view);
731                    g2.translate(view.getX(), view.getY());
732                    view.paint(g2);
733                    for (JComponent jComp : originalDoubleBuffered) {
734                        jComp.setDoubleBuffered(true);
735                    }
736                    originalDoubleBuffered.clear();
737                    return;
738                }
739            }
740            super.paintLayer(g2, layer);
741        }
742    }