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 javax.swing.*;
28 import java.awt.Component;
29 import java.awt.Color;
30 import java.awt.Dimension;
31 import java.awt.Font;
32 import java.awt.FontMetrics;
33 import java.awt.Graphics;
34 import java.awt.Graphics2D;
35 import java.awt.Insets;
36 import java.awt.Rectangle;
37 import java.awt.Toolkit;
38 import java.awt.event.InputEvent;
39 
40 import sun.swing.SwingUtilities2;
41 
42 
43 /**
44  * Convenient util class.
45  *
46  * @author Hans Muller
47  */
48 public class BasicGraphicsUtils
49 {
50 
51     private static final Insets GROOVE_INSETS = new Insets(2, 2, 2, 2);
52     private static final Insets ETCHED_INSETS = new Insets(2, 2, 2, 2);
53 
54     /**
55      * Draws an etched rectangle.
56      *
57      * @param g an instance of {@code Graphics}
58      * @param x an X coordinate
59      * @param y an Y coordinate
60      * @param w a width
61      * @param h a height
62      * @param shadow a color of shadow
63      * @param darkShadow a color of dark shadow
64      * @param highlight a color highlighting
65      * @param lightHighlight a color of light highlighting
66      */
drawEtchedRect(Graphics g, int x, int y, int w, int h, Color shadow, Color darkShadow, Color highlight, Color lightHighlight)67     public static void drawEtchedRect(Graphics g, int x, int y, int w, int h,
68                                       Color shadow, Color darkShadow,
69                                       Color highlight, Color lightHighlight)
70     {
71         Color oldColor = g.getColor();  // Make no net change to g
72         g.translate(x, y);
73 
74         g.setColor(shadow);
75         g.drawLine(0, 0, w-1, 0);      // outer border, top
76         g.drawLine(0, 1, 0, h-2);      // outer border, left
77 
78         g.setColor(darkShadow);
79         g.drawLine(1, 1, w-3, 1);      // inner border, top
80         g.drawLine(1, 2, 1, h-3);      // inner border, left
81 
82         g.setColor(lightHighlight);
83         g.drawLine(w-1, 0, w-1, h-1);  // outer border, bottom
84         g.drawLine(0, h-1, w-1, h-1);  // outer border, right
85 
86         g.setColor(highlight);
87         g.drawLine(w-2, 1, w-2, h-3);  // inner border, right
88         g.drawLine(1, h-2, w-2, h-2);  // inner border, bottom
89 
90         g.translate(-x, -y);
91         g.setColor(oldColor);
92     }
93 
94 
95     /**
96      * Returns the amount of space taken up by a border drawn by
97      * <code>drawEtchedRect()</code>
98      *
99      * @return  the inset of an etched rect
100      */
getEtchedInsets()101     public static Insets getEtchedInsets() {
102         return ETCHED_INSETS;
103     }
104 
105 
106     /**
107      * Draws a groove.
108      *
109      * @param g an instance of {@code Graphics}
110      * @param x an X coordinate
111      * @param y an Y coordinate
112      * @param w a width
113      * @param h a height
114      * @param shadow a color of shadow
115      * @param highlight a color highlighting
116      */
drawGroove(Graphics g, int x, int y, int w, int h, Color shadow, Color highlight)117     public static void drawGroove(Graphics g, int x, int y, int w, int h,
118                                   Color shadow, Color highlight)
119     {
120         Color oldColor = g.getColor();  // Make no net change to g
121         g.translate(x, y);
122 
123         g.setColor(shadow);
124         g.drawRect(0, 0, w-2, h-2);
125 
126         g.setColor(highlight);
127         g.drawLine(1, h-3, 1, 1);
128         g.drawLine(1, 1, w-3, 1);
129 
130         g.drawLine(0, h-1, w-1, h-1);
131         g.drawLine(w-1, h-1, w-1, 0);
132 
133         g.translate(-x, -y);
134         g.setColor(oldColor);
135     }
136 
137     /**
138      * Returns the amount of space taken up by a border drawn by
139      * <code>drawGroove()</code>
140      *
141      * @return  the inset of a groove border
142      */
getGrooveInsets()143     public static Insets getGrooveInsets() {
144         return GROOVE_INSETS;
145     }
146 
147 
148     /**
149      * Draws a bezel.
150      *
151      * @param g an instance of {@code Graphics}
152      * @param x an X coordinate
153      * @param y an Y coordinate
154      * @param w a width
155      * @param h a height
156      * @param isPressed is component pressed
157      * @param isDefault is default drawing
158      * @param shadow a color of shadow
159      * @param darkShadow a color of dark shadow
160      * @param highlight a color highlighting
161      * @param lightHighlight a color of light highlighting
162      */
drawBezel(Graphics g, int x, int y, int w, int h, boolean isPressed, boolean isDefault, Color shadow, Color darkShadow, Color highlight, Color lightHighlight)163     public static void drawBezel(Graphics g, int x, int y, int w, int h,
164                                  boolean isPressed, boolean isDefault,
165                                  Color shadow, Color darkShadow,
166                                  Color highlight, Color lightHighlight)
167     {
168         Color oldColor = g.getColor();  // Make no net change to g
169         g.translate(x, y);
170 
171         if (isPressed && isDefault) {
172             g.setColor(darkShadow);
173             g.drawRect(0, 0, w - 1, h - 1);
174             g.setColor(shadow);
175             g.drawRect(1, 1, w - 3, h - 3);
176         } else if (isPressed) {
177             drawLoweredBezel(g, x, y, w, h,
178                              shadow, darkShadow, highlight, lightHighlight);
179         } else if (isDefault) {
180             g.setColor(darkShadow);
181             g.drawRect(0, 0, w-1, h-1);
182 
183             g.setColor(lightHighlight);
184             g.drawLine(1, 1, 1, h-3);
185             g.drawLine(2, 1, w-3, 1);
186 
187             g.setColor(highlight);
188             g.drawLine(2, 2, 2, h-4);
189             g.drawLine(3, 2, w-4, 2);
190 
191             g.setColor(shadow);
192             g.drawLine(2, h-3, w-3, h-3);
193             g.drawLine(w-3, 2, w-3, h-4);
194 
195             g.setColor(darkShadow);
196             g.drawLine(1, h-2, w-2, h-2);
197             g.drawLine(w-2, h-2, w-2, 1);
198         } else {
199             g.setColor(lightHighlight);
200             g.drawLine(0, 0, 0, h-1);
201             g.drawLine(1, 0, w-2, 0);
202 
203             g.setColor(highlight);
204             g.drawLine(1, 1, 1, h-3);
205             g.drawLine(2, 1, w-3, 1);
206 
207             g.setColor(shadow);
208             g.drawLine(1, h-2, w-2, h-2);
209             g.drawLine(w-2, 1, w-2, h-3);
210 
211             g.setColor(darkShadow);
212             g.drawLine(0, h-1, w-1, h-1);
213             g.drawLine(w-1, h-1, w-1, 0);
214         }
215         g.translate(-x, -y);
216         g.setColor(oldColor);
217     }
218 
219     /**
220      * Draws a lowered bezel.
221      *
222      * @param g an instance of {@code Graphics}
223      * @param x an X coordinate
224      * @param y an Y coordinate
225      * @param w a width
226      * @param h a height
227      * @param shadow a color of shadow
228      * @param darkShadow a color of dark shadow
229      * @param highlight a color highlighting
230      * @param lightHighlight a color of light highlighting
231      */
drawLoweredBezel(Graphics g, int x, int y, int w, int h, Color shadow, Color darkShadow, Color highlight, Color lightHighlight)232     public static void drawLoweredBezel(Graphics g, int x, int y, int w, int h,
233                                         Color shadow, Color darkShadow,
234                                         Color highlight, Color lightHighlight)  {
235         g.setColor(darkShadow);
236         g.drawLine(0, 0, 0, h-1);
237         g.drawLine(1, 0, w-2, 0);
238 
239         g.setColor(shadow);
240         g.drawLine(1, 1, 1, h-2);
241         g.drawLine(1, 1, w-3, 1);
242 
243         g.setColor(lightHighlight);
244         g.drawLine(0, h-1, w-1, h-1);
245         g.drawLine(w-1, h-1, w-1, 0);
246 
247         g.setColor(highlight);
248         g.drawLine(1, h-2, w-2, h-2);
249         g.drawLine(w-2, h-2, w-2, 1);
250      }
251 
252 
253     /**
254      * Draw a string with the graphics {@code g} at location (x,y)
255      * just like {@code g.drawString} would. The first occurrence
256      * of {@code underlineChar} in text will be underlined.
257      * The matching algorithm is not case sensitive.
258      *
259      * @param g an instance of {@code Graphics}
260      * @param text a text
261      * @param underlinedChar an underlined char
262      * @param x an X coordinate
263      * @param y an Y coordinate
264      */
drawString(Graphics g,String text,int underlinedChar,int x,int y)265     public static void drawString(Graphics g,String text,int underlinedChar,int x,int y) {
266         int index=-1;
267 
268         if (underlinedChar != '\0') {
269             char uc = Character.toUpperCase((char)underlinedChar);
270             char lc = Character.toLowerCase((char)underlinedChar);
271             int uci = text.indexOf(uc);
272             int lci = text.indexOf(lc);
273 
274             if(uci == -1) {
275                 index = lci;
276             }
277             else if(lci == -1) {
278                 index = uci;
279             }
280             else {
281                 index = (lci < uci) ? lci : uci;
282             }
283         }
284         drawStringUnderlineCharAt(g, text, index, x, y);
285     }
286 
287     /**
288      * Draw a string with the graphics <code>g</code> at location
289      * (<code>x</code>, <code>y</code>)
290      * just like <code>g.drawString</code> would.
291      * The character at index <code>underlinedIndex</code>
292      * in text will be underlined. If <code>index</code> is beyond the
293      * bounds of <code>text</code> (including &lt; 0), nothing will be
294      * underlined.
295      *
296      * @param g Graphics to draw with
297      * @param text String to draw
298      * @param underlinedIndex Index of character in text to underline
299      * @param x x coordinate to draw at
300      * @param y y coordinate to draw at
301      * @since 1.4
302      */
drawStringUnderlineCharAt(Graphics g, String text, int underlinedIndex, int x,int y)303     public static void drawStringUnderlineCharAt(Graphics g, String text,
304                            int underlinedIndex, int x,int y) {
305         SwingUtilities2.drawStringUnderlineCharAt(null, g, text,
306                 underlinedIndex, x, y);
307     }
308 
309     /**
310      * Draws dashed rectangle.
311      *
312      * @param g an instance of {@code Graphics}
313      * @param x an X coordinate
314      * @param y an Y coordinate
315      * @param width a width of rectangle
316      * @param height a height of rectangle
317      */
drawDashedRect(Graphics g,int x,int y,int width,int height)318     public static void drawDashedRect(Graphics g,int x,int y,int width,int height) {
319         int vx,vy;
320 
321         // draw upper and lower horizontal dashes
322         for (vx = x; vx < (x + width); vx+=2) {
323             g.fillRect(vx, y, 1, 1);
324             g.fillRect(vx, y + height-1, 1, 1);
325         }
326 
327         // draw left and right vertical dashes
328         for (vy = y; vy < (y + height); vy+=2) {
329             g.fillRect(x, vy, 1, 1);
330             g.fillRect(x+width-1, vy, 1, 1);
331         }
332     }
333 
334     /**
335      * Returns the preferred size of the button.
336      *
337      * @param b an instance of {@code AbstractButton}
338      * @param textIconGap a gap between text and icon
339      * @return the preferred size of the button
340      */
getPreferredButtonSize(AbstractButton b, int textIconGap)341     public static Dimension getPreferredButtonSize(AbstractButton b, int textIconGap)
342     {
343         if(b.getComponentCount() > 0) {
344             return null;
345         }
346 
347         Icon icon = b.getIcon();
348         String text = b.getText();
349 
350         Font font = b.getFont();
351         FontMetrics fm = b.getFontMetrics(font);
352 
353         Rectangle iconR = new Rectangle();
354         Rectangle textR = new Rectangle();
355         Rectangle viewR = new Rectangle(Short.MAX_VALUE, Short.MAX_VALUE);
356 
357         SwingUtilities.layoutCompoundLabel(
358             b, fm, text, icon,
359             b.getVerticalAlignment(), b.getHorizontalAlignment(),
360             b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
361             viewR, iconR, textR, (text == null ? 0 : textIconGap)
362         );
363 
364         /* The preferred size of the button is the size of
365          * the text and icon rectangles plus the buttons insets.
366          */
367 
368         Rectangle r = iconR.union(textR);
369 
370         Insets insets = b.getInsets();
371         r.width += insets.left + insets.right;
372         r.height += insets.top + insets.bottom;
373 
374         return r.getSize();
375     }
376 
377     /*
378      * Convenience function for determining ComponentOrientation.  Helps us
379      * avoid having Munge directives throughout the code.
380      */
isLeftToRight( Component c )381     static boolean isLeftToRight( Component c ) {
382         return c.getComponentOrientation().isLeftToRight();
383     }
384 
isMenuShortcutKeyDown(InputEvent event)385     static boolean isMenuShortcutKeyDown(InputEvent event) {
386         return (event.getModifiersEx() &
387                 Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()) != 0;
388     }
389 
390     /**
391      * Draws the given string at the specified location using text properties
392      * and anti-aliasing hints from the provided component.
393      * Nothing is drawn for the null string.
394      *
395      * @param c the component that will display the string, may be null
396      * @param g the graphics context, must not be null
397      * @param string the string to display, may be null
398      * @param x the x coordinate to draw the text at
399      * @param y the y coordinate to draw the text at
400      * @throws NullPointerException if the specified {@code g} is {@code null}
401      *
402      * @since 9
403      */
drawString(JComponent c, Graphics2D g, String string, float x, float y)404     public static void drawString(JComponent c, Graphics2D g, String string,
405                                   float x, float y) {
406         SwingUtilities2.drawString(c, g, string, x, y, true);
407     }
408 
409     /**
410      * Draws the given string at the specified location underlining
411      * the specified character. The provided component is used to query text
412      * properties and anti-aliasing hints.
413      * <p>
414      * The {@code underlinedIndex} parameter points to a char value
415      * (Unicode code unit) in the given string.
416      * If the char value specified at the underlined index is in
417      * the high-surrogate range and the char value at the following index is in
418      * the low-surrogate range then the supplementary character corresponding
419      * to this surrogate pair is underlined.
420      * <p>
421      * No character is underlined if the index is negative or greater
422      * than the string length {@code (index < 0 || index >= string.length())}
423      * or if the char value specified at the given index
424      * is in the low-surrogate range.
425      *
426      * @param c the component that will display the string, may be null
427      * @param g the graphics context, must not be null
428      * @param string the string to display, may be null
429      * @param underlinedIndex index of a a char value (Unicode code unit)
430      *        in the string to underline
431      * @param x the x coordinate to draw the text at
432      * @param y the y coordinate to draw the text at
433      * @throws NullPointerException if the specified {@code g} is {@code null}
434      *
435      * @see #getStringWidth
436      *
437      * @since 9
438      */
drawStringUnderlineCharAt(JComponent c, Graphics2D g, String string, int underlinedIndex, float x, float y)439     public static void drawStringUnderlineCharAt(JComponent c, Graphics2D g,
440             String string, int underlinedIndex, float x, float y) {
441         SwingUtilities2.drawStringUnderlineCharAt(c, g, string, underlinedIndex,
442                                                   x, y, true);
443     }
444 
445     /**
446      * Clips the passed in string to the space provided.
447      * The provided component is used to query text properties and anti-aliasing hints.
448      * The unchanged string is returned if the space provided is greater than
449      * the string width.
450      *
451      * @param c the component, may be null
452      * @param fm the FontMetrics used to measure the string width, must be
453      *           obtained from the correct font and graphics. Must not be null.
454      * @param string the string to clip, may be null
455      * @param availTextWidth the amount of space that the string can be drawn in
456      * @return the clipped string that fits in the provided space, an empty
457      *         string if the given string argument is {@code null} or empty
458      * @throws NullPointerException if the specified {@code fm} is {@code null}
459      *
460      * @see #getStringWidth
461      *
462      * @since 9
463      */
getClippedString(JComponent c, FontMetrics fm, String string, int availTextWidth)464     public static String getClippedString(JComponent c, FontMetrics fm,
465                                           String string, int availTextWidth) {
466         return SwingUtilities2.clipStringIfNecessary(c, fm, string, availTextWidth);
467     }
468 
469     /**
470      * Returns the width of the passed in string using text properties
471      * and anti-aliasing hints from the provided component.
472      * If the passed string is {@code null}, returns zero.
473      *
474      * @param c the component, may be null
475      * @param fm the FontMetrics used to measure the advance string width, must
476      *           be obtained from the correct font and graphics. Must not be null.
477      * @param string the string to get the advance width of, may be null
478      * @return the advance width of the specified string, zero is returned for
479      *         {@code null} string
480      * @throws NullPointerException if the specified {@code fm} is {@code null}
481      *
482      * @since 9
483      */
getStringWidth(JComponent c, FontMetrics fm, String string)484     public static float getStringWidth(JComponent c, FontMetrics fm, String string) {
485         return SwingUtilities2.stringWidth(c, fm, string, true);
486     }
487 }
488