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      */
  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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 381     static boolean isLeftToRight( Component c ) {
 382         return c.getComponentOrientation().isLeftToRight();
 383     }
 384 
 385     @SuppressWarnings("deprecation")
 386     static boolean isMenuShortcutKeyDown(InputEvent event) {
 387         return (event.getModifiers() &
 388                 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0;
 389     }
 390 
 391     /**
 392      * Draws the given string at the specified location using text properties
 393      * and anti-aliasing hints from the provided component.
 394      * Nothing is drawn for the null string.
 395      *
 396      * @param c the component that will display the string, may be null
 397      * @param g the graphics context, must not be null
 398      * @param string the string to display, may be null
 399      * @param x the x coordinate to draw the text at
 400      * @param y the y coordinate to draw the text at
 401      * @throws NullPointerException if the specified {@code g} is {@code null}
 402      *
 403      * @since 9
 404      */
 405     public static void drawString(JComponent c, Graphics2D g, String string,
 406                                   float x, float y) {
 407         SwingUtilities2.drawString(c, g, string, x, y, true);
 408     }
 409 
 410     /**
 411      * Draws the given string at the specified location underlining
 412      * the specified character. The provided component is used to query text
 413      * properties and anti-aliasing hints.
 414      * <p>
 415      * The {@code underlinedIndex} parameter points to a char value
 416      * (Unicode code unit) in the given string.
 417      * If the char value specified at the underlined index is in
 418      * the high-surrogate range and the char value at the following index is in
 419      * the low-surrogate range then the supplementary character corresponding
 420      * to this surrogate pair is underlined.
 421      * <p>
 422      * No character is underlined if the index is negative or greater
 423      * than the string length {@code (index < 0 || index >= string.length())}
 424      * or if the char value specified at the given index
 425      * is in the low-surrogate range.
 426      *
 427      * @param c the component that will display the string, may be null
 428      * @param g the graphics context, must not be null
 429      * @param string the string to display, may be null
 430      * @param underlinedIndex index of a a char value (Unicode code unit)
 431      *        in the string to underline
 432      * @param x the x coordinate to draw the text at
 433      * @param y the y coordinate to draw the text at
 434      * @throws NullPointerException if the specified {@code g} is {@code null}
 435      *
 436      * @see #getStringWidth
 437      *
 438      * @since 9
 439      */
 440     public static void drawStringUnderlineCharAt(JComponent c, Graphics2D g,
 441             String string, int underlinedIndex, float x, float y) {
 442         SwingUtilities2.drawStringUnderlineCharAt(c, g, string, underlinedIndex,
 443                                                   x, y, true);
 444     }
 445 
 446     /**
 447      * Clips the passed in string to the space provided.
 448      * The provided component is used to query text properties and anti-aliasing hints.
 449      * The unchanged string is returned if the space provided is greater than
 450      * the string width.
 451      *
 452      * @param c the component, may be null
 453      * @param fm the FontMetrics used to measure the string width, must be
 454      *           obtained from the correct font and graphics. Must not be null.
 455      * @param string the string to clip, may be null
 456      * @param availTextWidth the amount of space that the string can be drawn in
 457      * @return the clipped string that fits in the provided space, an empty
 458      *         string if the given string argument is {@code null} or empty
 459      * @throws NullPointerException if the specified {@code fm} is {@code null}
 460      *
 461      * @see #getStringWidth
 462      *
 463      * @since 9
 464      */
 465     public static String getClippedString(JComponent c, FontMetrics fm,
 466                                           String string, int availTextWidth) {
 467         return SwingUtilities2.clipStringIfNecessary(c, fm, string, availTextWidth);
 468     }
 469 
 470     /**
 471      * Returns the width of the passed in string using text properties
 472      * and anti-aliasing hints from the provided component.
 473      * If the passed string is {@code null}, returns zero.
 474      *
 475      * @param c the component, may be null
 476      * @param fm the FontMetrics used to measure the advance string width, must
 477      *           be obtained from the correct font and graphics. Must not be null.
 478      * @param string the string to get the advance width of, may be null
 479      * @return the advance width of the specified string, zero is returned for
 480      *         {@code null} string
 481      * @throws NullPointerException if the specified {@code fm} is {@code null}
 482      *
 483      * @since 9
 484      */
 485     public static float getStringWidth(JComponent c, FontMetrics fm, String string) {
 486         return SwingUtilities2.stringWidth(c, fm, string, true);
 487     }
 488 }