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    * 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   public static void paintMenuItem(Graphics g, JComponent c,
 147                                    Icon checkIcon, Icon arrowIcon,
 148                                    Color background, Color foreground,
 149                                    int defaultTextIconGap)
 150     {
 151 
 152         JMenuItem b = (JMenuItem) c;
 153         ButtonModel model = b.getModel();
 154 
 155         Dimension size = b.getSize();
 156         Insets i = c.getInsets();
 157 
 158         Rectangle viewRect = new Rectangle(size);
 159 
 160         viewRect.x += i.left;
 161         viewRect.y += i.top;
 162         viewRect.width -= (i.right + viewRect.x);
 163         viewRect.height -= (i.bottom + viewRect.y);
 164 
 165         Rectangle iconRect = new Rectangle();
 166         Rectangle textRect = new Rectangle();
 167         Rectangle acceleratorRect = new Rectangle();
 168         Rectangle checkRect = new Rectangle();
 169         Rectangle arrowRect = new Rectangle();
 170 
 171         Font holdf = g.getFont();
 172         Font f = c.getFont();
 173         g.setFont(f);
 174         FontMetrics fm = SwingUtilities2.getFontMetrics(c, g, f);
 175         FontMetrics fmAccel = SwingUtilities2.getFontMetrics(
 176             c, g, UIManager.getFont("MenuItem.acceleratorFont"));
 177 
 178         if (c.isOpaque()) {
 179             if (model.isArmed()|| (c instanceof JMenu && model.isSelected())) {
 180                 g.setColor(background);
 181             } else {
 182                 g.setColor(c.getBackground());
 183             }
 184             g.fillRect(0,0, size.width, size.height);
 185         }
 186 
 187         // get Accelerator text
 188         KeyStroke accelerator =  b.getAccelerator();
 189         String acceleratorText = "";
 190         if (accelerator != null) {
 191             int modifiers = accelerator.getModifiers();
 192             if (modifiers > 0) {
 193                 acceleratorText = KeyEvent.getKeyModifiersText(modifiers);
 194                 acceleratorText += "+";
 195             }
 196             acceleratorText += KeyEvent.getKeyText(accelerator.getKeyCode());
 197         }
 198 
 199         // layout the text and icon
 200         String text = layoutMenuItem(c, fm, b.getText(), fmAccel,
 201                                      acceleratorText, b.getIcon(),
 202                                      checkIcon, arrowIcon,
 203                                      b.getVerticalAlignment(),
 204                                      b.getHorizontalAlignment(),
 205                                      b.getVerticalTextPosition(),
 206                                      b.getHorizontalTextPosition(),
 207                                      viewRect, iconRect,
 208                                      textRect, acceleratorRect,
 209                                      checkRect, arrowRect,
 210                                      b.getText() == null
 211                                      ? 0 : defaultTextIconGap,
 212                                      defaultTextIconGap
 213                                      );
 214 
 215         // Paint the Check
 216         Color holdc = g.getColor();
 217         if (checkIcon != null) {
 218             if(model.isArmed() || (c instanceof JMenu && model.isSelected()))
 219                 g.setColor(foreground);
 220             checkIcon.paintIcon(c, g, checkRect.x, checkRect.y);
 221             g.setColor(holdc);
 222         }
 223 
 224         // Paint the Icon
 225         if(b.getIcon() != null) {
 226             Icon icon;
 227             if(!model.isEnabled()) {
 228                 icon = b.getDisabledIcon();
 229             } else if(model.isPressed() && model.isArmed()) {
 230                 icon = b.getPressedIcon();
 231                 if(icon == null) {
 232                     // Use default icon
 233                     icon = b.getIcon();
 234                 }
 235             } else {
 236                 icon = b.getIcon();
 237             }
 238 
 239             if (icon!=null) {
 240                 icon.paintIcon(c, g, iconRect.x, iconRect.y);
 241             }
 242         }
 243 
 244         // Draw the Text
 245         if(text != null && !text.equals("")) {
 246             // Once BasicHTML becomes public, use BasicHTML.propertyKey
 247             // instead of the hardcoded string below!
 248             View v = (View) c.getClientProperty("html");
 249             if (v != null) {
 250                 v.paint(g, textRect);
 251             } else {
 252                 int mnemIndex = b.getDisplayedMnemonicIndex();
 253 
 254                 if(!model.isEnabled()) {
 255                     // *** paint the text disabled
 256                     g.setColor(b.getBackground().brighter());
 257                     SwingUtilities2.drawStringUnderlineCharAt(b, g,text,
 258                         mnemIndex,
 259                         textRect.x, textRect.y + fmAccel.getAscent());
 260                     g.setColor(b.getBackground().darker());
 261                     SwingUtilities2.drawStringUnderlineCharAt(b, g,text,
 262                         mnemIndex,
 263                         textRect.x - 1, textRect.y + fmAccel.getAscent() - 1);
 264 
 265                 } else {
 266                     // *** paint the text normally
 267                     if (model.isArmed()|| (c instanceof JMenu && model.isSelected())) {
 268                         g.setColor(foreground);
 269                     } else {
 270                         g.setColor(b.getForeground());
 271                     }
 272                     SwingUtilities2.drawStringUnderlineCharAt(b, g,text,
 273                                                   mnemIndex,
 274                                                   textRect.x,
 275                                                   textRect.y + fm.getAscent());
 276                 }
 277             }
 278         }
 279 
 280         // Draw the Accelerator Text
 281         if(acceleratorText != null && !acceleratorText.equals("")) {
 282 
 283             //Get the maxAccWidth from the parent to calculate the offset.
 284             int accOffset = 0;
 285             Container parent = b.getParent();
 286             if (parent != null && parent instanceof JComponent) {
 287                 JComponent p = (JComponent) parent;
 288                 Integer maxValueInt = (Integer) p.getClientProperty(MotifGraphicsUtils.MAX_ACC_WIDTH);
 289                 int maxValue = maxValueInt != null ?
 290                     maxValueInt.intValue() : acceleratorRect.width;
 291 
 292                 //Calculate the offset, with which the accelerator texts will be drawn with.
 293                 accOffset = maxValue - acceleratorRect.width;
 294             }
 295 
 296             g.setFont( UIManager.getFont("MenuItem.acceleratorFont") );
 297             if(!model.isEnabled()) {
 298                 // *** paint the acceleratorText disabled
 299                 g.setColor(b.getBackground().brighter());
 300                 SwingUtilities2.drawString(c, g,acceleratorText,
 301                                               acceleratorRect.x - accOffset, acceleratorRect.y + fm.getAscent());
 302                 g.setColor(b.getBackground().darker());
 303                 SwingUtilities2.drawString(c, g,acceleratorText,
 304                                               acceleratorRect.x - accOffset - 1, acceleratorRect.y + fm.getAscent() - 1);
 305             } else {
 306                 // *** paint the acceleratorText normally
 307                 if (model.isArmed()|| (c instanceof JMenu && model.isSelected()))
 308                     {
 309                         g.setColor(foreground);
 310                     } else {
 311                         g.setColor(b.getForeground());
 312                     }
 313                 SwingUtilities2.drawString(c, g,acceleratorText,
 314                                               acceleratorRect.x - accOffset,
 315                                               acceleratorRect.y + fmAccel.getAscent());
 316             }
 317         }
 318 
 319         // Paint the Arrow
 320         if (arrowIcon != null) {
 321             if(model.isArmed() || (c instanceof JMenu && model.isSelected()))
 322                 g.setColor(foreground);
 323             if( !(b.getParent() instanceof JMenuBar) )
 324                 arrowIcon.paintIcon(c, g, arrowRect.x, arrowRect.y);
 325         }
 326 
 327         g.setColor(holdc);
 328         g.setFont(holdf);
 329     }
 330 
 331 
 332     /**
 333      * Compute and return the location of the icons origin, the
 334      * location of origin of the text baseline, and a possibly clipped
 335      * version of the compound labels string.  Locations are computed
 336      * relative to the viewR rectangle.
 337      */
 338 
 339     private static String layoutMenuItem(
 340         JComponent c,
 341         FontMetrics fm,
 342         String text,
 343         FontMetrics fmAccel,
 344         String acceleratorText,
 345         Icon icon,
 346         Icon checkIcon,
 347         Icon arrowIcon,
 348         int verticalAlignment,
 349         int horizontalAlignment,
 350         int verticalTextPosition,
 351         int horizontalTextPosition,
 352         Rectangle viewR,
 353         Rectangle iconR,
 354         Rectangle textR,
 355         Rectangle acceleratorR,
 356         Rectangle checkIconR,
 357         Rectangle arrowIconR,
 358         int textIconGap,
 359         int menuItemGap
 360         )
 361     {
 362 
 363         SwingUtilities.layoutCompoundLabel(c,
 364                                            fm,
 365                                            text,
 366                                            icon,
 367                                            verticalAlignment,
 368                                            horizontalAlignment,
 369                                            verticalTextPosition,
 370                                            horizontalTextPosition,
 371                                            viewR,
 372                                            iconR,
 373                                            textR,
 374                                            textIconGap);
 375 
 376         /* Initialize the acceelratorText bounds rectangle textR.  If a null
 377          * or and empty String was specified we substitute "" here
 378          * and use 0,0,0,0 for acceleratorTextR.
 379          */
 380         if( (acceleratorText == null) || acceleratorText.equals("") ) {
 381             acceleratorR.width = acceleratorR.height = 0;
 382             acceleratorText = "";
 383         }
 384         else {
 385             acceleratorR.width
 386                 = SwingUtilities2.stringWidth(c, fmAccel, acceleratorText);
 387             acceleratorR.height = fmAccel.getHeight();
 388         }
 389 
 390         /* Initialize the checkIcon bounds rectangle checkIconR.
 391          */
 392 
 393         if (checkIcon != null) {
 394             checkIconR.width = checkIcon.getIconWidth();
 395             checkIconR.height = checkIcon.getIconHeight();
 396         }
 397         else {
 398             checkIconR.width = checkIconR.height = 0;
 399         }
 400 
 401         /* Initialize the arrowIcon bounds rectangle arrowIconR.
 402          */
 403 
 404         if (arrowIcon != null) {
 405             arrowIconR.width = arrowIcon.getIconWidth();
 406             arrowIconR.height = arrowIcon.getIconHeight();
 407         }
 408         else {
 409             arrowIconR.width = arrowIconR.height = 0;
 410         }
 411 
 412 
 413         Rectangle labelR = iconR.union(textR);
 414         if( MotifGraphicsUtils.isLeftToRight(c) ) {
 415             textR.x += checkIconR.width + menuItemGap;
 416             iconR.x += checkIconR.width + menuItemGap;
 417 
 418             // Position the Accelerator text rect
 419             acceleratorR.x = viewR.x + viewR.width - arrowIconR.width
 420                              - menuItemGap - acceleratorR.width;
 421 
 422             // Position the Check and Arrow Icons
 423             checkIconR.x = viewR.x;
 424             arrowIconR.x = viewR.x + viewR.width - menuItemGap
 425                            - arrowIconR.width;
 426         } else {
 427             textR.x -= (checkIconR.width + menuItemGap);
 428             iconR.x -= (checkIconR.width + menuItemGap);
 429 
 430             // Position the Accelerator text rect
 431             acceleratorR.x = viewR.x + arrowIconR.width + menuItemGap;
 432 
 433             // Position the Check and Arrow Icons
 434             checkIconR.x = viewR.x + viewR.width - checkIconR.width;
 435             arrowIconR.x = viewR.x + menuItemGap;
 436         }
 437 
 438         // Align the accelertor text and the check and arrow icons vertically
 439         // with the center of the label rect.
 440         acceleratorR.y = labelR.y + (labelR.height/2) - (acceleratorR.height/2);
 441         arrowIconR.y = labelR.y + (labelR.height/2) - (arrowIconR.height/2);
 442         checkIconR.y = labelR.y + (labelR.height/2) - (checkIconR.height/2);
 443 
 444         /*
 445           System.out.println("Layout: v=" +viewR+"  c="+checkIconR+" i="+
 446           iconR+" t="+textR+" acc="+acceleratorR+" a="+arrowIconR);
 447           */
 448         return text;
 449     }
 450 
 451   private static void drawMenuBezel(Graphics g, Color background,
 452                                     int x, int y,
 453                                     int width, int height)
 454     {
 455       // shadowed button region
 456       g.setColor(background);
 457       g.fillRect(x,y,width,height);
 458 
 459       g.setColor(background.brighter().brighter());
 460       g.drawLine(x+1,       y+height-1,  x+width-1, y+height-1);
 461       g.drawLine(x+width-1, y+height-2,  x+width-1, y+1);
 462 
 463       g.setColor(background.darker().darker());
 464       g.drawLine(x,   y,   x+width-2, y);
 465       g.drawLine(x,   y+1, x,         y+height-2);
 466 
 467     }
 468 
 469     /*
 470      * Convenience function for determining ComponentOrientation.  Helps us
 471      * avoid having Munge directives throughout the code.
 472      */
 473     static boolean isLeftToRight( Component c ) {
 474         return c.getComponentOrientation().isLeftToRight();
 475     }
 476 }