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("i18n", 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 }