/* * Copyright (c) Andrey Kuznetsov. All Rights Reserved. * * http://jgui.imagero.com * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of Andrey Kuznetsov nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package javax.swing; import org.slf4j.ext.XLogger; import org.slf4j.ext.XLoggerFactory; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; /** * Transform any swing component. */ public class AffinePanel extends JPanel { /** * */ private static final long serialVersionUID = 3486541619930533783L; /** * Static Logger */ private static XLogger logger = XLoggerFactory.getXLogger(AffinePanel.class); AffineTransform at; CellRendererPane crp = new CellRendererPane(); Component view; public AffinePanel(Component renderer) { this(null, renderer); } public Component getView() { return view; } public void setView(Component view) { this.view = view; } public AffinePanel(AffineTransform at, Component renderer) { setLayout(null); this.at = at; this.view = renderer; add(crp); add(view); view.setVisible(true); setTransform(at); enableEvents(AWTEvent.MOUSE_EVENT_MASK); enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK); enableEvents(AWTEvent.KEY_EVENT_MASK); KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(new KeyEventPostProcessor() { public boolean postProcessKeyEvent(KeyEvent e) { if (AffinePanel.this.at == null) { return false; } Component c = e.getComponent(); if (SwingUtilities.isDescendingFrom(c, AffinePanel.this.view)) { KeyboardFocusManager.getCurrentKeyboardFocusManager().redispatchEvent(c, e); repaint(); return true; } return false; } }); } public Dimension getPreferredSize() { if (view == null) { return super.getPreferredSize(); } else { Insets insets = getInsets(); Dimension d = view.getPreferredSize(); if (at == null) { return new Dimension(d.width + insets.left + insets.right, d.height + insets.top + insets.bottom); } else { Rectangle r = new Rectangle(0, 0, d.width, d.height); Shape shape = at.createTransformedShape(r); return shape.getBounds().getSize(); } } } protected void processMouseEvent(MouseEvent e) { if (at != null && view != null) { processME(e); } else { super.processMouseEvent(e); } } protected void processMouseMotionEvent(MouseEvent e) { if (at != null && view != null) { boolean autoscrolls = false; if (view instanceof JComponent) { JComponent comp = (JComponent) view; autoscrolls = comp.getAutoscrolls(); comp.setAutoscrolls(false); } processME(e); if (autoscrolls) { JComponent comp = (JComponent) view; comp.setAutoscrolls(true); } } else { super.processMouseMotionEvent(e); } } boolean pressed; private void processME(MouseEvent e) { Point p = e.getPoint(); repaint(); Rectangle r = view.getBounds(); Shape shape = at.createTransformedShape(r); Rectangle r2 = shape.getBounds(); AffineTransform at2 = new AffineTransform(at); at2.preConcatenate(AffineTransform.getTranslateInstance(r2.x < 0 ? -r2.x : 0, r2.y < 0 ? -r2.y : 0)); shape = at2.createTransformedShape(r); if (!shape.contains(p)) { return; } switch (e.getID()) { case MouseEvent.MOUSE_PRESSED: pressed = true; break; case MouseEvent.MOUSE_RELEASED: pressed = false; break; case MouseEvent.MOUSE_DRAGGED: if (!pressed) { return; } break; } Point p0; try { AffineTransform inverse = at2.createInverse(); p0 = (Point) inverse.transform(p, new Point()); } catch (NoninvertibleTransformException ex) { logger.warn("Unable to invert transform", ex); p0 = findPoint(r, at2, p); } Component c = SwingUtilities.getDeepestComponentAt(view, p0.x, p0.y); if (c != null) { p0 = SwingUtilities.convertPoint(view, p0, c); } else { c = view; } MouseEvent me = createEvent(e, c, p0.x, p0.y); try { c.dispatchEvent(me); } catch (Throwable t) { //ignore } } MouseEvent createEvent(MouseEvent e, Component source, int x, int y) { return new MouseEvent( source, e.getID(), e.getWhen(), e.getModifiers(), x, y, e.getClickCount(), e.isPopupTrigger(), e.getButton()); } /** * used to search point in case of noninvertible AffineTransform * @param r Rectangle * @param at AffineTransform * @param p0 point to search * @return Point on Rectangle <code>r</code> which after transform with AffineTransform <code>at</code> gives Point p0 */ Point findPoint(Rectangle r, AffineTransform at, Point p0) { if (r.width == 1 && r.height == 1) { return r.getLocation(); } int w2 = Math.max((r.width + 1) / 2, 1); int h2 = Math.max((r.height + 1) / 2, 1); Rectangle[] rrs = new Rectangle[4]; rrs[0] = new Rectangle(r.x, r.y, w2, h2); rrs[1] = new Rectangle(r.x + w2, r.y, w2, h2); rrs[2] = new Rectangle(r.x, r.y + h2, w2, h2); rrs[3] = new Rectangle(r.x + w2, r.y + h2, w2, h2); for (int i = 0; i < rrs.length; i++) { Shape s0 = at.createTransformedShape(rrs[i]); if (s0.contains(p0)) { return findPoint(rrs[i], at, p0); } } return null; } protected void paintComponent(Graphics g) { super.paintComponent(g); if (view == null || at == null) { return; } Dimension d = view.getPreferredSize(); Dimension d2 = view.getSize(); if ((d.width != d2.width) || (d.height != d2.height)) { view.setSize(d); } if (at != null) { Graphics2D g2d = (Graphics2D) g.create(); Rectangle r = new Rectangle(0, 0, d.width, d.height); Shape shape = at.createTransformedShape(r); Rectangle r2 = shape.getBounds(); g2d.translate(r2.x < 0 ? -r2.x : 0, r2.y < 0 ? -r2.y : 0); g2d.transform(at); view.paint(g2d); g2d.dispose(); } else { view.paint(g); } } public void scale(double sx, double sy) { if (at == null) { setScale(sx, sy); } else { at.scale(sx, sy); } } public void setScale(double sx, double sy) { setTransform(AffineTransform.getScaleInstance(sx, sy)); } public void rotate(double angle) { if (at == null) { setRotate(angle); } else { at.rotate(Math.toRadians(angle)); } } public void setRotate(double angle) { setTransform(AffineTransform.getRotateInstance(Math.toRadians(angle))); } public void shear(double sx, double sy) { if (at == null) { setShear(sx, sy); } else { at.shear(sx, sy); } } public void setShear(double sx, double sy) { setTransform(AffineTransform.getShearInstance(sx, sy)); } public void setTransform(AffineTransform at) { if (equals(this.at, at)) { return; } if (this.at == null) { crp.add(view); } else if (at == null) { add(view); } this.at = at; revalidate(); repaint(); } private static boolean equals(AffineTransform at1, AffineTransform at2) { if (at1 == null && at2 == null) { return true; } if (at1 == null || at2 == null) { return false; } return at1.equals(at2); } public AffineTransform getTransform() { return at; } }