1 /*
   2  * Copyright (c) 1997, 2014, 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.basic;
  26 
  27 import java.awt.Dimension;
  28 import java.awt.Graphics;
  29 import java.awt.Color;
  30 import java.awt.Graphics2D;
  31 import java.awt.geom.AffineTransform;
  32 import java.awt.geom.Path2D;
  33 
  34 import javax.swing.*;
  35 import javax.swing.plaf.UIResource;
  36 import sun.swing.SwingUtilities2;
  37 
  38 /**
  39  * JButton object that draws a scaled Arrow in one of the cardinal directions.
  40  * <p>
  41  * <strong>Warning:</strong>
  42  * Serialized objects of this class will not be compatible with
  43  * future Swing releases. The current serialization support is
  44  * appropriate for short term storage or RMI between applications running
  45  * the same version of Swing.  As of 1.4, support for long term storage
  46  * of all JavaBeans
  47  * has been added to the <code>java.beans</code> package.
  48  * Please see {@link java.beans.XMLEncoder}.
  49  *
  50  * @author David Kloba
  51  */
  52 @SuppressWarnings("serial") // Same-version serialization only
  53 public class BasicArrowButton extends JButton implements SwingConstants
  54 {
  55         /**
  56          * The direction of the arrow. One of
  57          * {@code SwingConstants.NORTH}, {@code SwingConstants.SOUTH},
  58          * {@code SwingConstants.EAST} or {@code SwingConstants.WEST}.
  59          */
  60         protected int direction;
  61 
  62         private Color shadow;
  63         private Color darkShadow;
  64         private Color highlight;
  65 
  66         /**
  67          * Creates a {@code BasicArrowButton} whose arrow
  68          * is drawn in the specified direction and with the specified
  69          * colors.
  70          *
  71          * @param direction the direction of the arrow; one of
  72          *        {@code SwingConstants.NORTH}, {@code SwingConstants.SOUTH},
  73          *        {@code SwingConstants.EAST} or {@code SwingConstants.WEST}
  74          * @param background the background color of the button
  75          * @param shadow the color of the shadow
  76          * @param darkShadow the color of the dark shadow
  77          * @param highlight the color of the highlight
  78          * @since 1.4
  79          */
  80         public BasicArrowButton(int direction, Color background, Color shadow,
  81                          Color darkShadow, Color highlight) {
  82             super();
  83             setRequestFocusEnabled(false);
  84             setDirection(direction);
  85             setBackground(background);
  86             this.shadow = shadow;
  87             this.darkShadow = darkShadow;
  88             this.highlight = highlight;
  89         }
  90 
  91         /**
  92          * Creates a {@code BasicArrowButton} whose arrow
  93          * is drawn in the specified direction.
  94          *
  95          * @param direction the direction of the arrow; one of
  96          *        {@code SwingConstants.NORTH}, {@code SwingConstants.SOUTH},
  97          *        {@code SwingConstants.EAST} or {@code SwingConstants.WEST}
  98          */
  99         public BasicArrowButton(int direction) {
 100             this(direction, UIManager.getColor("control"), UIManager.getColor("controlShadow"),
 101                  UIManager.getColor("controlDkShadow"), UIManager.getColor("controlLtHighlight"));
 102         }
 103 
 104         /**
 105          * Returns the direction of the arrow.
 106          *
 107          * @return the direction of the arrow
 108          */
 109         public int getDirection() {
 110             return direction;
 111         }
 112 
 113         /**
 114          * Sets the direction of the arrow.
 115          *
 116          * @param direction the direction of the arrow; one of
 117          *        of {@code SwingConstants.NORTH},
 118          *        {@code SwingConstants.SOUTH},
 119          *        {@code SwingConstants.EAST} or {@code SwingConstants.WEST}
 120          */
 121         public void setDirection(int direction) {
 122             this.direction = direction;
 123         }
 124 
 125         public void paint(Graphics g) {
 126             Color origColor;
 127             boolean isPressed, isEnabled;
 128             int w, h, size;
 129 
 130             w = getSize().width;
 131             h = getSize().height;
 132             origColor = g.getColor();
 133             isPressed = getModel().isPressed();
 134             isEnabled = isEnabled();
 135 
 136             g.setColor(getBackground());
 137             g.fillRect(1, 1, w-2, h-2);
 138 
 139             /// Draw the proper Border
 140             if (getBorder() != null && !(getBorder() instanceof UIResource)) {
 141                 paintBorder(g);
 142             } else if (isPressed) {
 143                 g.setColor(shadow);
 144                 g.drawRect(0, 0, w-1, h-1);
 145             } else {
 146                 // Using the background color set above
 147                 g.drawLine(0, 0, 0, h-1);
 148                 g.drawLine(1, 0, w-2, 0);
 149 
 150                 g.setColor(highlight);    // inner 3D border
 151                 g.drawLine(1, 1, 1, h-3);
 152                 g.drawLine(2, 1, w-3, 1);
 153 
 154                 g.setColor(shadow);       // inner 3D border
 155                 g.drawLine(1, h-2, w-2, h-2);
 156                 g.drawLine(w-2, 1, w-2, h-3);
 157 
 158                 g.setColor(darkShadow);     // black drop shadow  __|
 159                 g.drawLine(0, h-1, w-1, h-1);
 160                 g.drawLine(w-1, h-1, w-1, 0);
 161             }
 162 
 163             // If there's no room to draw arrow, bail
 164             if(h < 5 || w < 5)      {
 165                 g.setColor(origColor);
 166                 return;
 167             }
 168 
 169             if (isPressed) {
 170                 g.translate(1, 1);
 171             }
 172 
 173             // Draw the arrow
 174             size = Math.min((h - 4) / 3, (w - 4) / 3);
 175             size = Math.max(size, 2);
 176             paintTriangle(g, (w - size) / 2, (h - size) / 2,
 177                                 size, direction, isEnabled);
 178 
 179             // Reset the Graphics back to it's original settings
 180             if (isPressed) {
 181                 g.translate(-1, -1);
 182             }
 183             g.setColor(origColor);
 184 
 185         }
 186 
 187         /**
 188          * Returns the preferred size of the {@code BasicArrowButton}.
 189          *
 190          * @return the preferred size
 191          */
 192         public Dimension getPreferredSize() {
 193             return new Dimension(16, 16);
 194         }
 195 
 196         /**
 197          * Returns the minimum size of the {@code BasicArrowButton}.
 198          *
 199          * @return the minimum size
 200          */
 201         public Dimension getMinimumSize() {
 202             return new Dimension(5, 5);
 203         }
 204 
 205         /**
 206          * Returns the maximum size of the {@code BasicArrowButton}.
 207          *
 208          * @return the maximum size
 209          */
 210         public Dimension getMaximumSize() {
 211             return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
 212         }
 213 
 214         /**
 215          * Returns whether the arrow button should get the focus.
 216          * {@code BasicArrowButton}s are used as a child component of
 217          * composite components such as {@code JScrollBar} and
 218          * {@code JComboBox}. Since the composite component typically gets the
 219          * focus, this method is overriden to return {@code false}.
 220          *
 221          * @return {@code false}
 222          */
 223         @SuppressWarnings("deprecation")
 224         public boolean isFocusTraversable() {
 225           return false;
 226         }
 227 
 228         /**
 229          * Paints a triangle.
 230          *
 231          * @param g the {@code Graphics} to draw to
 232          * @param x the x coordinate
 233          * @param y the y coordinate
 234          * @param size the size of the triangle to draw
 235          * @param direction the direction in which to draw the arrow;
 236          *        one of {@code SwingConstants.NORTH},
 237          *        {@code SwingConstants.SOUTH}, {@code SwingConstants.EAST} or
 238          *        {@code SwingConstants.WEST}
 239          * @param isEnabled whether or not the arrow is drawn enabled
 240          */
 241         public void paintTriangle(Graphics g, int x, int y, int size,
 242                                         int direction, boolean isEnabled) {
 243             if (SwingUtilities2.isScaledGraphics(g)) {
 244                 paintScaledTriangle(g, x, y, size, direction, isEnabled);
 245             } else {
 246                 paintUnscaledTriangle(g, x, y, size, direction, isEnabled);
 247             }
 248         }
 249 
 250         private void paintUnscaledTriangle(Graphics g, int x, int y, int size,
 251                                            int direction, boolean isEnabled)
 252         {
 253             Color oldColor = g.getColor();
 254             int mid, i, j;
 255 
 256             j = 0;
 257             size = Math.max(size, 2);
 258             mid = (size / 2) - 1;
 259 
 260             g.translate(x, y);
 261             if(isEnabled)
 262                 g.setColor(darkShadow);
 263             else
 264                 g.setColor(shadow);
 265 
 266             switch(direction)       {
 267             case NORTH:
 268                 for(i = 0; i < size; i++)      {
 269                     g.drawLine(mid-i, i, mid+i, i);
 270                 }
 271                 if(!isEnabled)  {
 272                     g.setColor(highlight);
 273                     g.drawLine(mid-i+2, i, mid+i, i);
 274                 }
 275                 break;
 276             case SOUTH:
 277                 if(!isEnabled)  {
 278                     g.translate(1, 1);
 279                     g.setColor(highlight);
 280                     for(i = size-1; i >= 0; i--)   {
 281                         g.drawLine(mid-i, j, mid+i, j);
 282                         j++;
 283                     }
 284                     g.translate(-1, -1);
 285                     g.setColor(shadow);
 286                 }
 287 
 288                 j = 0;
 289                 for(i = size-1; i >= 0; i--)   {
 290                     g.drawLine(mid-i, j, mid+i, j);
 291                     j++;
 292                 }
 293                 break;
 294             case WEST:
 295                 for(i = 0; i < size; i++)      {
 296                     g.drawLine(i, mid-i, i, mid+i);
 297                 }
 298                 if(!isEnabled)  {
 299                     g.setColor(highlight);
 300                     g.drawLine(i, mid-i+2, i, mid+i);
 301                 }
 302                 break;
 303             case EAST:
 304                 if(!isEnabled)  {
 305                     g.translate(1, 1);
 306                     g.setColor(highlight);
 307                     for(i = size-1; i >= 0; i--)   {
 308                         g.drawLine(j, mid-i, j, mid+i);
 309                         j++;
 310                     }
 311                     g.translate(-1, -1);
 312                     g.setColor(shadow);
 313                 }
 314 
 315                 j = 0;
 316                 for(i = size-1; i >= 0; i--)   {
 317                     g.drawLine(j, mid-i, j, mid+i);
 318                     j++;
 319                 }
 320                 break;
 321             }
 322             g.translate(-x, -y);
 323             g.setColor(oldColor);
 324         }
 325 
 326     private void paintScaledTriangle(Graphics g, double x, double y, double size,
 327                                      int direction, boolean isEnabled) {
 328         size = Math.max(size, 2);
 329         Path2D.Double path = new Path2D.Double();
 330         path.moveTo(-size, size / 2);
 331         path.lineTo(size, size / 2);
 332         path.lineTo(0, -size / 2);
 333         path.closePath();
 334         AffineTransform affineTransform = new AffineTransform();
 335         affineTransform.rotate(Math.PI * (direction - 1) / 4);
 336         path.transform(affineTransform);
 337 
 338         Graphics2D g2d = (Graphics2D) g;
 339         double tx = x + size / 2;
 340         double ty = y + size / 2;
 341         g2d.translate(tx, ty);
 342         Color oldColor = g.getColor();
 343         if (!isEnabled) {
 344             g2d.translate(1, 0);
 345             g2d.setColor(highlight);
 346             g2d.fill(path);
 347             g2d.translate(-1, 0);
 348         }
 349         g2d.setColor(isEnabled ? darkShadow : shadow);
 350         g2d.fill(path);
 351         g2d.translate(-tx, -ty);
 352         g2d.setColor(oldColor);
 353     }
 354 }