1 /*
   2  * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.plaf.nimbus;
  26 
  27 import javax.swing.border.Border;
  28 import javax.swing.JComponent;
  29 import java.awt.Insets;
  30 import java.awt.Component;
  31 import java.awt.Graphics;
  32 import java.awt.Graphics2D;
  33 import java.awt.Color;
  34 import java.awt.RenderingHints;
  35 import java.awt.Dimension;
  36 import java.awt.image.BufferedImage;
  37 
  38 /**
  39  * LoweredBorder - A recessed rounded inner shadowed border. Used as the
  40  * standard Nimbus TitledBorder. This class is both a painter and a swing
  41  * border.
  42  *
  43  * @author Jasper Potts
  44  */
  45 class LoweredBorder extends AbstractRegionPainter implements Border {
  46     private static final int IMG_SIZE = 30;
  47     private static final int RADIUS = 13;
  48     private static final Insets INSETS = new Insets(10,10,10,10);
  49     private static final PaintContext PAINT_CONTEXT = new PaintContext(INSETS,
  50             new Dimension(IMG_SIZE,IMG_SIZE),false,
  51             PaintContext.CacheMode.NINE_SQUARE_SCALE,
  52             Integer.MAX_VALUE, Integer.MAX_VALUE);
  53 
  54     // =========================================================================
  55     // Painter Methods
  56 
  57     @Override
  58     protected Object[] getExtendedCacheKeys(JComponent c) {
  59         return (c != null)
  60                 ? new Object[] { c.getBackground() }
  61                 : null;
  62     }
  63 
  64     /**
  65      * Actually performs the painting operation. Subclasses must implement this
  66      * method. The graphics object passed may represent the actual surface being
  67      * rendered to, or it may be an intermediate buffer. It has also been
  68      * pre-translated. Simply render the component as if it were located at 0, 0
  69      * and had a width of {@code width} and a height of
  70      * {@code height}. For performance reasons, you may want to read the
  71      * clip from the Graphics2D object and only render within that space.
  72      *
  73      * @param g      The Graphics2D surface to paint to
  74      * @param c      The JComponent related to the drawing event. For example,
  75      *               if the region being rendered is Button, then {@code c}
  76      *               will be a JButton. If the region being drawn is
  77      *               ScrollBarSlider, then the component will be JScrollBar.
  78      *               This value may be null.
  79      * @param width  The width of the region to paint. Note that in the case of
  80      *               painting the foreground, this value may differ from
  81      *               c.getWidth().
  82      * @param height The height of the region to paint. Note that in the case of
  83      *               painting the foreground, this value may differ from
  84      *               c.getHeight().
  85      */
  86     protected void doPaint(Graphics2D g, JComponent c, int width, int height,
  87             Object[] extendedCacheKeys) {
  88         Color color = (c == null) ? Color.BLACK : c.getBackground();
  89         BufferedImage img1 = new BufferedImage(IMG_SIZE,IMG_SIZE,
  90                     BufferedImage.TYPE_INT_ARGB);
  91         BufferedImage img2 = new BufferedImage(IMG_SIZE,IMG_SIZE,
  92                     BufferedImage.TYPE_INT_ARGB);
  93         // draw shadow shape
  94         Graphics2D g2 = (Graphics2D)img1.getGraphics();
  95         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
  96                 RenderingHints.VALUE_ANTIALIAS_ON);
  97         g2.setColor(color);
  98         g2.fillRoundRect(2,0,26,26,RADIUS,RADIUS);
  99         g2.dispose();
 100         // draw shadow
 101         InnerShadowEffect effect = new InnerShadowEffect();
 102         effect.setDistance(1);
 103         effect.setSize(3);
 104         effect.setColor(getLighter(color, 2.1f));
 105         effect.setAngle(90);
 106         effect.applyEffect(img1,img2,IMG_SIZE,IMG_SIZE);
 107         // draw outline to img2
 108         g2 = (Graphics2D)img2.getGraphics();
 109         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 110                 RenderingHints.VALUE_ANTIALIAS_ON);
 111         g2.setClip(0,28,IMG_SIZE,1);
 112         g2.setColor(getLighter(color, 0.90f));
 113         g2.drawRoundRect(2,1,25,25,RADIUS,RADIUS);
 114         g2.dispose();
 115         // draw final image
 116         if (width != IMG_SIZE || height != IMG_SIZE){
 117             ImageScalingHelper.paint(g,0,0,width,height,img2, INSETS, INSETS,
 118                     ImageScalingHelper.PaintType.PAINT9_STRETCH,
 119                     ImageScalingHelper.PAINT_ALL);
 120         } else {
 121             g.drawImage(img2,0,0,c);
 122         }
 123         img1 = null;
 124         img2 = null;
 125     }
 126 
 127     /**
 128      * <p>Gets the PaintContext for this painting operation. This method is
 129      * called on every paint, and so should be fast and produce no garbage. The
 130      * PaintContext contains information such as cache hints. It also contains
 131      * data necessary for decoding points at runtime, such as the stretching
 132      * insets, the canvas size at which the encoded points were defined, and
 133      * whether the stretching insets are inverted.</p>
 134      * <p/>
 135      * <p> This method allows for subclasses to package the painting of
 136      * different states with possibly different canvas sizes, etc, into one
 137      * AbstractRegionPainter implementation.</p>
 138      *
 139      * @return a PaintContext associated with this paint operation.
 140      */
 141     protected PaintContext getPaintContext() {
 142         return PAINT_CONTEXT;
 143     }
 144 
 145     // =========================================================================
 146     // Border Methods
 147 
 148     /**
 149      * Returns the insets of the border.
 150      *
 151      * @param c the component for which this border insets value applies
 152      */
 153     public Insets getBorderInsets(Component c) {
 154         return (Insets) INSETS.clone();
 155     }
 156 
 157     /**
 158      * Returns whether or not the border is opaque.  If the border is opaque, it
 159      * is responsible for filling in it's own background when painting.
 160      */
 161     public boolean isBorderOpaque() {
 162         return false;
 163     }
 164 
 165     /**
 166      * Paints the border for the specified component with the specified position
 167      * and size.
 168      *
 169      * @param c      the component for which this border is being painted
 170      * @param g      the paint graphics
 171      * @param x      the x position of the painted border
 172      * @param y      the y position of the painted border
 173      * @param width  the width of the painted border
 174      * @param height the height of the painted border
 175      */
 176     public void paintBorder(Component c, Graphics g, int x, int y, int width,
 177                             int height) {
 178         JComponent comp = (c instanceof JComponent)?(JComponent)c:null;
 179         if (g instanceof Graphics2D){
 180             Graphics2D g2 = (Graphics2D)g;
 181             g2.translate(x,y);
 182             paint(g2,comp, width, height);
 183             g2.translate(-x,-y);
 184         } else {
 185             BufferedImage img =  new BufferedImage(IMG_SIZE,IMG_SIZE,
 186                     BufferedImage.TYPE_INT_ARGB);
 187             Graphics2D g2 = (Graphics2D)img.getGraphics();
 188             paint(g2,comp, width, height);
 189             g2.dispose();
 190             ImageScalingHelper.paint(g,x,y,width,height,img,INSETS, INSETS,
 191                     ImageScalingHelper.PaintType.PAINT9_STRETCH,
 192                     ImageScalingHelper.PAINT_ALL);
 193         }
 194     }
 195 
 196     private Color getLighter(Color c, float factor){
 197         return new Color(Math.min((int)(c.getRed()/factor), 255),
 198                          Math.min((int)(c.getGreen()/factor), 255),
 199                          Math.min((int)(c.getBlue()/factor), 255));
 200     }
 201 }
 202