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&trade;
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          */
BasicArrowButton(int direction, Color background, Color shadow, Color darkShadow, Color highlight)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          */
BasicArrowButton(int direction)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          */
getDirection()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          */
setDirection(int direction)121         public void setDirection(int direction) {
122             this.direction = direction;
123         }
124 
paint(Graphics g)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          */
getPreferredSize()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          */
getMinimumSize()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          */
getMaximumSize()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")
isFocusTraversable()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          */
paintTriangle(Graphics g, int x, int y, int size, int direction, boolean isEnabled)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 
paintUnscaledTriangle(Graphics g, int x, int y, int size, int direction, boolean isEnabled)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 
paintScaledTriangle(Graphics g, double x, double y, double size, int direction, boolean isEnabled)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 }
355