package com.blazingfrog.imported; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.Icon; import javax.swing.JLabel; /** * This class implements a JLabel variant where the * content of the label is rotated by a specified * angle. The size of the label is automatically * adjusted to accommodate the rotated size of the * content. The key to doing this is to use * an AffineTransform to modify the Graphics2D * object used for painting the content, and to * override the getSize() method to return a * rotated size. Note that this implementation is * not as efficient as it could be, because it * recalculates some stuff that it could cache. * Also, this implementation depends on a special * behavior of its superclass JLabel - the JLabel * getPreferredSize() method returns the bounding * box for the content (text, icon, or both). * If JLabel did not do this, then we would need to * do it in this code, which would make this class * much longer. * * @author nziring */ public class RotatedJLabel extends JLabel implements PropertyChangeListener { private static final long serialVersionUID = 1L; double angle; AffineTransform xform; Rectangle2D rbb; boolean rvalid = false; boolean wantRotatedSize = true; /** * Initialize the rotation support. This would * normally be called exactly once, from a * constructor. */ private void initRotation() { recomputeTransform(0.0, super.getSize()); addPropertyChangeListener(this); } /** * Compute the relevant affine transform. */ private Dimension recomputeTransform(double newAngle, Dimension baseSize) { angle = newAngle; xform = new AffineTransform(); // 1. start with the unrotated size Dimension urs; urs = baseSize; // 2. Compute the rotation xform.rotate(Math.PI * angle / 180.0, 0,0); // 3. Get the bounding box and transform it Rectangle2D bb; bb = new Rectangle2D.Double(0, 0, urs.width, urs.height); Shape rbbs = xform.createTransformedShape(bb); // 4. Get bounds of the transformed shape to get the new bounds rbb = rbbs.getBounds2D(); // 5. Add a pre-translation that ensures the drawing fits in the box AffineTransform prexlate; prexlate = new AffineTransform(); prexlate.translate(- rbb.getX(), - rbb.getY()); xform.preConcatenate(prexlate); // 6. If we have something reasonable, then rotation is valid if (rbb.getWidth() != 0.0) rvalid = true; // 7. Compute a new size to return Dimension ns = new Dimension((int) Math.ceil(rbb.getWidth()), (int) Math.ceil(rbb.getHeight())); return ns; } /** * Return true if both superclass is valid and our local * rotational computations are valid. */ @Override public boolean isValid() { boolean ret = rvalid && super.isValid(); //System.err.println("isValid called, returning " + ret); return ret; } /** * */ public RotatedJLabel() { this("Label"); } /** * Create a RotatedJLabel with a rotation of 0 * and a given text content. * * @param text */ public RotatedJLabel(String text) { super(text); initRotation(); } /** * @param image */ public RotatedJLabel(Icon image) { super(image); initRotation(); } /** * @param text * @param horizontalAlignment */ public RotatedJLabel(String text, int horizontalAlignment) { super(text, horizontalAlignment); initRotation(); } /** * @param image * @param horizontalAlignment */ public RotatedJLabel(Icon image, int horizontalAlignment) { super(image, horizontalAlignment); initRotation(); } /** * @param text * @param icon * @param horizontalAlignment */ public RotatedJLabel(String text, Icon icon, int horizontalAlignment) { super(text, icon, horizontalAlignment); initRotation(); } /** * Override getSize() to return the rotated size. */ @Override public Dimension getSize() { Dimension ns = super.getSize(); Dimension ps = super.getPreferredSize(); //System.err.println("In getSize, returning ns=" + ns + " but could possibly return ps=" + ps); if (wantRotatedSize) return ns; else return ps; } /** * Override getPreferredSize() to tell our container * how much space we need. */ @Override public Dimension getPreferredSize() { //System.err.println("getPreferredSize called!"); Dimension ps = recomputeTransform(angle, super.getPreferredSize()); return ps; } @Override public int getWidth() { return getSize().width; } @Override public int getHeight() { return getSize().height; } /** * Override the paintComponent method. This method * gets called when Swing needs to paint the body * of the label. It receives a Graphics2D, which we * copy, and then apply the affine transform to the * copy, and then call the JLabel paintComponent * method with the transformed copy. Will it work? */ @Override protected void paintComponent(Graphics g) { Graphics2D rg = (Graphics2D) g.create(); rg.transform(xform); wantRotatedSize = false; super.paintComponent(rg); wantRotatedSize = true; } /** * Set the rotation angle of this RotatedJLabel. */ public void setRotation(double a) { double oldangle = angle; angle = a; if (angle != oldangle) { rvalid = false; firePropertyChange("ROTATION", oldangle, angle); } } private boolean ignoreChange = false; @Override public void propertyChange(PropertyChangeEvent evt) { if (ignoreChange) return; Dimension newsize = recomputeTransform(angle, super.getPreferredSize()); ignoreChange = true; setSize(newsize); ignoreChange = false; } }