1 /*
   2  * Copyright (c) 1997, 2008, 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 com.sun.java.swing.plaf.motif;
  26 
  27 import sun.swing.SwingUtilities2;
  28 
  29 import javax.swing.*;
  30 import java.awt.Color;
  31 import java.awt.Dimension;
  32 import java.awt.Graphics;
  33 import java.awt.Font;
  34 import java.awt.FontMetrics;
  35 import java.awt.Rectangle;
  36 import java.awt.Component;
  37 import java.awt.Insets;
  38 import java.awt.event.KeyEvent;
  39 import java.awt.Container;
  40 
  41 import javax.swing.plaf.basic.*;
  42 import javax.swing.text.View;
  43 
  44 /*
  45  * @author Jeff Dinkins
  46  * @author Dave Kloba
  47  */
  48 
  49 public class MotifGraphicsUtils implements SwingConstants
  50 {
  51     /* Client Property keys for text and accelerator text widths */
  52     private static final String MAX_ACC_WIDTH  =  "maxAccWidth";
  53 
  54     /**
  55      * Draws the point (<b>x</b>, <b>y</b>) in the current color.
  56      */
  57     static void drawPoint(Graphics g, int x, int y) {
  58         g.drawLine(x, y, x, y);
  59     }
  60 
  61     /*
  62      * Convenience method for drawing a grooved line
  63      *
  64      */
  65     public static void drawGroove(Graphics g, int x, int y, int w, int h,
  66                                   Color shadow, Color highlight)
  67     {
  68         Color oldColor = g.getColor();  // Make no net change to g
  69         g.translate(x, y);
  70 
  71         g.setColor(shadow);
  72         g.drawRect(0, 0, w-2, h-2);
  73 
  74         g.setColor(highlight);
  75         g.drawLine(1, h-3, 1, 1);
  76         g.drawLine(1, 1, w-3, 1);
  77 
  78         g.drawLine(0, h-1, w-1, h-1);
  79         g.drawLine(w-1, h-1, w-1, 0);
  80 
  81         g.translate(-x, -y);
  82         g.setColor(oldColor);
  83     }
  84 
  85     /** Draws <b>aString</b> in the rectangle defined by
  86       * (<b>x</b>, <b>y</b>, <b>width</b>, <b>height</b>).
  87       * <b>justification</b> specifies the text's justification, one of
  88       * LEFT, CENTER, or RIGHT.
  89       * <b>drawStringInRect()</b> does not clip to the rectangle, but instead
  90       * uses this rectangle and the desired justification to compute the point
  91       * at which to begin drawing the text.
  92       * @see #drawString
  93       */
  94     public static void drawStringInRect(Graphics g, String aString, int x, int y,
  95                                  int width, int height, int justification) {
  96         drawStringInRect(null, g, aString, x, y, width, height, justification);
  97     }
  98 
  99     static void drawStringInRect(JComponent c, Graphics g, String aString,
 100                                  int x, int y, int width, int height,
 101                                  int justification) {
 102         FontMetrics  fontMetrics;
 103         int          drawWidth, startX, startY, delta;
 104 
 105         if (g.getFont() == null) {
 106 //            throw new InconsistencyException("No font set");
 107             return;
 108         }
 109         fontMetrics = SwingUtilities2.getFontMetrics(c, g);
 110         if (fontMetrics == null) {
 111 //            throw new InconsistencyException("No metrics for Font " + font());
 112             return;
 113         }
 114 
 115         if (justification == CENTER) {
 116             drawWidth = SwingUtilities2.stringWidth(c, fontMetrics, aString);
 117             if (drawWidth > width) {
 118                 drawWidth = width;
 119             }
 120             startX = x + (width - drawWidth) / 2;
 121         } else if (justification == RIGHT) {
 122             drawWidth = SwingUtilities2.stringWidth(c, fontMetrics, aString);
 123             if (drawWidth > width) {
 124                 drawWidth = width;
 125             }
 126             startX = x + width - drawWidth;
 127         } else {
 128             startX = x;
 129         }
 130 
 131         delta = (height - fontMetrics.getAscent() - fontMetrics.getDescent()) / 2;
 132         if (delta < 0) {
 133             delta = 0;
 134         }
 135 
 136         startY = y + height - delta - fontMetrics.getDescent();
 137 
 138         SwingUtilities2.drawString(c, g, aString, startX, startY);
 139     }
 140 
 141   /**
 142    * @deprecated This method is not being used to paint menu item since
 143    * 6.0 This code left for compatibility only. Do not use or
 144    * override it, this will not cause any visible effect.
 145    */
 146   @Deprecated(since = "9")
 147   public static void paintMenuItem(Graphics g, JComponent c,
 148                                    Icon checkIcon, Icon arrowIcon,
 149                                    Color background, Color foreground,
 150                                    int defaultTextIconGap)
 151     {
 152 
 153         JMenuItem b = (JMenuItem) c;
 154         ButtonModel model = b.getModel();
 155 
 156         Dimension size = b.getSize();
 157         Insets i = c.getInsets();
 158 
 159         Rectangle viewRect = new Rectangle(size);
 160 
 161         viewRect.x += i.left;
 162         viewRect.y += i.top;
 163         viewRect.width -= (i.right + viewRect.x);
 164         viewRect.height -= (i.bottom + viewRect.y);
 165 
 166         Rectangle iconRect = new Rectangle();
 167         Rectangle textRect = new Rectangle();
 168         Rectangle acceleratorRect = new Rectangle();
 169         Rectangle checkRect = new Rectangle();
 170         Rectangle arrowRect = new Rectangle();
 171 
 172         Font holdf = g.getFont();
 173         Font f = c.getFont();
 174         g.setFont(f);
 175         FontMetrics fm = SwingUtilities2.getFontMetrics(c, g, f);
 176         FontMetrics fmAccel = SwingUtilities2.getFontMetrics(
 177             c, g, UIManager.getFont("MenuItem.acceleratorFont"));
 178 
 179         if (c.isOpaque()) {
 180             if (model.isArmed()|| (c instanceof JMenu && model.isSelected())) {
 181                 g.setColor(background);
 182             } else {
 183                 g.setColor(c.getBackground());
 184             }
 185             g.fillRect(0,0, size.width, size.height);
 186         }
 187 
 188         // get Accelerator text
 189         KeyStroke accelerator =  b.getAccelerator();
 190         String acceleratorText = "";
 191         if (accelerator != null) {
 192             int modifiers = accelerator.getModifiers();
 193             if (modifiers > 0) {
 194                 acceleratorText = KeyEvent.getKeyModifiersText(modifiers);
 195                 acceleratorText += "+";
 196             }
 197             acceleratorText += KeyEvent.getKeyText(accelerator.getKeyCode());
 198         }
 199 
 200         // layout the text and icon
 201         String text = layoutMenuItem(c, fm, b.getText(), fmAccel,
 202                                      acceleratorText, b.getIcon(),
 203                                      checkIcon, arrowIcon,
 204                                      b.getVerticalAlignment(),
 205                                      b.getHorizontalAlignment(),
 206                                      b.getVerticalTextPosition(),
 207                                      b.getHorizontalTextPosition(),
 208                                      viewRect, iconRect,
 209                                      textRect, acceleratorRect,
 210                                      checkRect, arrowRect,
 211                                      b.getText() == null
 212                                      ? 0 : defaultTextIconGap,
 213                                      defaultTextIconGap
 214                                      );
 215 
 216         // Paint the Check
 217         Color holdc = g.getColor();
 218         if (checkIcon != null) {
 219             if(model.isArmed() || (c instanceof JMenu && model.isSelected()))
 220                 g.setColor(foreground);
 221             checkIcon.paintIcon(c, g, checkRect.x, checkRect.y);
 222             g.setColor(holdc);
 223         }
 224 
 225         // Paint the Icon
 226         if(b.getIcon() != null) {
 227             Icon icon;
 228             if(!model.isEnabled()) {
 229                 icon = b.getDisabledIcon();
 230             } else if(model.isPressed() && model.isArmed()) {
 231                 icon = b.getPressedIcon();
 232                 if(icon == null) {
 233                     // Use default icon
 234                     icon = b.getIcon();
 235                 }
 236             } else {
 237                 icon = b.getIcon();
 238             }
 239 
 240             if (icon!=null) {
 241                 icon.paintIcon(c, g, iconRect.x, iconRect.y);
 242             }
 243         }
 244 
 245         // Draw the Text
 246         if(text != null && !text.equals("")) {
 247             // Once BasicHTML becomes public, use BasicHTML.propertyKey
 248             // instead of the hardcoded string below!
 249             View v = (View) c.getClientProperty("html");
 250             if (v != null) {
 251                 v.paint(g, textRect);
 252             } else {
 253                 int mnemIndex = b.getDisplayedMnemonicIndex();
 254 
 255                 if(!model.isEnabled()) {
 256                     // *** paint the text disabled
 257                     g.setColor(b.getBackground().brighter());
 258                     SwingUtilities2.drawStringUnderlineCharAt(b, g,text,
 259                         mnemIndex,
 260                         textRect.x, textRect.y + fmAccel.getAscent());
 261                     g.setColor(b.getBackground().darker());
 262                     SwingUtilities2.drawStringUnderlineCharAt(b, g,text,
 263                         mnemIndex,
 264                         textRect.x - 1, textRect.y + fmAccel.getAscent() - 1);
 265 
 266                 } else {
 267                     // *** paint the text normally
 268                     if (model.isArmed()|| (c instanceof JMenu && model.isSelected())) {
 269                         g.setColor(foreground);
 270                     } else {
 271                         g.setColor(b.getForeground());
 272                     }
 273                     SwingUtilities2.drawStringUnderlineCharAt(b, g,text,
 274                                                   mnemIndex,
 275                                                   textRect.x,
 276                                                   textRect.y + fm.getAscent());
 277                 }
 278             }
 279         }
 280 
 281         // Draw the Accelerator Text
 282         if(acceleratorText != null && !acceleratorText.equals("")) {
 283 
 284             //Get the maxAccWidth from the parent to calculate the offset.
 285             int accOffset = 0;
 286             Container parent = b.getParent();
 287             if (parent != null && parent instanceof JComponent) {
 288                 JComponent p = (JComponent) parent;
 289                 Integer maxValueInt = (Integer) p.getClientProperty(MotifGraphicsUtils.MAX_ACC_WIDTH);
 290                 int maxValue = maxValueInt != null ?
 291                     maxValueInt.intValue() : acceleratorRect.width;
 292 
 293                 //Calculate the offset, with which the accelerator texts will be drawn with.
 294                 accOffset = maxValue - acceleratorRect.width;
 295             }
 296 
 297             g.setFont( UIManager.getFont("MenuItem.acceleratorFont") );
 298             if(!model.isEnabled()) {
 299                 // *** paint the acceleratorText disabled
 300                 g.setColor(b.getBackground().brighter());
 301                 SwingUtilities2.drawString(c, g,acceleratorText,
 302                                               acceleratorRect.x - accOffset, acceleratorRect.y + fm.getAscent());
 303                 g.setColor(b.getBackground().darker());
 304                 SwingUtilities2.drawString(c, g,acceleratorText,
 305                                               acceleratorRect.x - accOffset - 1, acceleratorRect.y + fm.getAscent() - 1);
 306             } else {
 307                 // *** paint the acceleratorText normally
 308                 if (model.isArmed()|| (c instanceof JMenu && model.isSelected()))
 309                     {
 310                         g.setColor(foreground);
 311                     } else {
 312                         g.setColor(b.getForeground());
 313                     }
 314                 SwingUtilities2.drawString(c, g,acceleratorText,
 315                                               acceleratorRect.x - accOffset,
 316                                               acceleratorRect.y + fmAccel.getAscent());
 317             }
 318         }
 319 
 320         // Paint the Arrow
 321         if (arrowIcon != null) {
 322             if(model.isArmed() || (c instanceof JMenu && model.isSelected()))
 323                 g.setColor(foreground);
 324             if( !(b.getParent() instanceof JMenuBar) )
 325                 arrowIcon.paintIcon(c, g, arrowRect.x, arrowRect.y);
 326         }
 327 
 328         g.setColor(holdc);
 329         g.setFont(holdf);
 330     }
 331 
 332 
 333     /**
 334      * Compute and return the location of the icons origin, the
 335      * location of origin of the text baseline, and a possibly clipped
 336      * version of the compound labels string.  Locations are computed
 337      * relative to the viewR rectangle.
 338      */
 339 
 340     private static String layoutMenuItem(
 341         JComponent c,
 342         FontMetrics fm,
 343         String text,
 344         FontMetrics fmAccel,
 345         String acceleratorText,
 346         Icon icon,
 347         Icon checkIcon,
 348         Icon arrowIcon,
 349         int verticalAlignment,
 350         int horizontalAlignment,
 351         int verticalTextPosition,
 352         int horizontalTextPosition,
 353         Rectangle viewR,
 354         Rectangle iconR,
 355         Rectangle textR,
 356         Rectangle acceleratorR,
 357         Rectangle checkIconR,
 358         Rectangle arrowIconR,
 359         int textIconGap,
 360         int menuItemGap
 361         )
 362     {
 363 
 364         SwingUtilities.layoutCompoundLabel(c,
 365                                            fm,
 366                                            text,
 367                                            icon,
 368                                            verticalAlignment,
 369                                            horizontalAlignment,
 370                                            verticalTextPosition,
 371                                            horizontalTextPosition,
 372                                            viewR,
 373                                            iconR,
 374                                            textR,
 375                                            textIconGap);
 376 
 377         /* Initialize the acceelratorText bounds rectangle textR.  If a null
 378          * or and empty String was specified we substitute "" here
 379          * and use 0,0,0,0 for acceleratorTextR.
 380          */
 381         if( (acceleratorText == null) || acceleratorText.equals("") ) {
 382             acceleratorR.width = acceleratorR.height = 0;
 383             acceleratorText = "";
 384         }
 385         else {
 386             acceleratorR.width
 387                 = SwingUtilities2.stringWidth(c, fmAccel, acceleratorText);
 388             acceleratorR.height = fmAccel.getHeight();
 389         }
 390 
 391         /* Initialize the checkIcon bounds rectangle checkIconR.
 392          */
 393 
 394         if (checkIcon != null) {
 395             checkIconR.width = checkIcon.getIconWidth();
 396             checkIconR.height = checkIcon.getIconHeight();
 397         }
 398         else {
 399             checkIconR.width = checkIconR.height = 0;
 400         }
 401 
 402         /* Initialize the arrowIcon bounds rectangle arrowIconR.
 403          */
 404 
 405         if (arrowIcon != null) {
 406             arrowIconR.width = arrowIcon.getIconWidth();
 407             arrowIconR.height = arrowIcon.getIconHeight();
 408         }
 409         else {
 410             arrowIconR.width = arrowIconR.height = 0;
 411         }
 412 
 413 
 414         Rectangle labelR = iconR.union(textR);
 415         if( MotifGraphicsUtils.isLeftToRight(c) ) {
 416             textR.x += checkIconR.width + menuItemGap;
 417             iconR.x += checkIconR.width + menuItemGap;
 418 
 419             // Position the Accelerator text rect
 420             acceleratorR.x = viewR.x + viewR.width - arrowIconR.width
 421                              - menuItemGap - acceleratorR.width;
 422 
 423             // Position the Check and Arrow Icons
 424             checkIconR.x = viewR.x;
 425             arrowIconR.x = viewR.x + viewR.width - menuItemGap
 426                            - arrowIconR.width;
 427         } else {
 428             textR.x -= (checkIconR.width + menuItemGap);
 429             iconR.x -= (checkIconR.width + menuItemGap);
 430 
 431             // Position the Accelerator text rect
 432             acceleratorR.x = viewR.x + arrowIconR.width + menuItemGap;
 433 
 434             // Position the Check and Arrow Icons
 435             checkIconR.x = viewR.x + viewR.width - checkIconR.width;
 436             arrowIconR.x = viewR.x + menuItemGap;
 437         }
 438 
 439         // Align the accelertor text and the check and arrow icons vertically
 440         // with the center of the label rect.
 441         acceleratorR.y = labelR.y + (labelR.height/2) - (acceleratorR.height/2);
 442         arrowIconR.y = labelR.y + (labelR.height/2) - (arrowIconR.height/2);
 443         checkIconR.y = labelR.y + (labelR.height/2) - (checkIconR.height/2);
 444 
 445         /*
 446           System.out.println("Layout: v=" +viewR+"  c="+checkIconR+" i="+
 447           iconR+" t="+textR+" acc="+acceleratorR+" a="+arrowIconR);
 448           */
 449         return text;
 450     }
 451 
 452   private static void drawMenuBezel(Graphics g, Color background,
 453                                     int x, int y,
 454                                     int width, int height)
 455     {
 456       // shadowed button region
 457       g.setColor(background);
 458       g.fillRect(x,y,width,height);
 459 
 460       g.setColor(background.brighter().brighter());
 461       g.drawLine(x+1,       y+height-1,  x+width-1, y+height-1);
 462       g.drawLine(x+width-1, y+height-2,  x+width-1, y+1);
 463 
 464       g.setColor(background.darker().darker());
 465       g.drawLine(x,   y,   x+width-2, y);
 466       g.drawLine(x,   y+1, x,         y+height-2);
 467 
 468     }
 469 
 470     /*
 471      * Convenience function for determining ComponentOrientation.  Helps us
 472      * avoid having Munge directives throughout the code.
 473      */
 474     static boolean isLeftToRight( Component c ) {
 475         return c.getComponentOrientation().isLeftToRight();
 476     }
 477 }