1 /*
   2  * Copyright (c) 2002, 2007, 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 
  26 package sun.awt.X11;
  27 
  28 import java.awt.*;
  29 import java.awt.peer.*;
  30 import java.awt.event.*;
  31 import java.awt.image.BufferedImage;
  32 import javax.swing.plaf.basic.BasicGraphicsUtils;
  33 import java.awt.geom.AffineTransform;
  34 
  35 import sun.util.logging.PlatformLogger;
  36 
  37 class XCheckboxPeer extends XComponentPeer implements CheckboxPeer {
  38 
  39     private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XCheckboxPeer");
  40 
  41     private static final Insets focusInsets = new Insets(0,0,0,0);
  42     private static final Insets borderInsets = new Insets(2,2,2,2);
  43     private static final int checkBoxInsetFromText = 2;
  44 
  45     //The check mark is less common than a plain "depressed" button,
  46     //so don't use the checkmark.
  47     // The checkmark shape:
  48     private static final double MASTER_SIZE = 128.0;
  49     private static final Polygon MASTER_CHECKMARK = new Polygon(
  50         new int[] {1, 25,56,124,124,85, 64},  // X-coords
  51         new int[] {59,35,67,  0, 12,66,123},  // Y-coords
  52       7);
  53 
  54     private Shape myCheckMark;
  55 
  56     private Color focusColor = SystemColor.windowText;
  57 
  58     private boolean pressed;
  59     private boolean armed;
  60     private boolean selected;
  61 
  62     private Rectangle textRect;
  63     private Rectangle focusRect;
  64     private int checkBoxSize;
  65     private int cbX;
  66     private int cbY;
  67 
  68     String label;
  69     CheckboxGroup checkBoxGroup;
  70 
  71     XCheckboxPeer(Checkbox target) {
  72         super(target);
  73         pressed = false;
  74         armed = false;
  75         selected = target.getState();
  76         label = target.getLabel();
  77         if ( label == null ) {
  78             label = "";
  79         }
  80         checkBoxGroup = target.getCheckboxGroup();
  81         updateMotifColors(getPeerBackground());
  82     }
  83 
  84     public void preInit(XCreateWindowParams params) {
  85         // Put this here so it is executed before layout() is called from
  86         // setFont() in XComponent.postInit()
  87         textRect = new Rectangle();
  88         focusRect = new Rectangle();
  89         super.preInit(params);
  90     }
  91 
  92     public boolean isFocusable() { return true; }
  93 
  94     public void focusGained(FocusEvent e) {
  95         // TODO: only need to paint the focus bit
  96         super.focusGained(e);
  97         repaint();
  98     }
  99 
 100     public void focusLost(FocusEvent e) {
 101         // TODO: only need to paint the focus bit?
 102         super.focusLost(e);
 103         repaint();
 104     }
 105 
 106 
 107     void handleJavaKeyEvent(KeyEvent e) {
 108         int i = e.getID();
 109         switch (i) {
 110           case KeyEvent.KEY_PRESSED:
 111               keyPressed(e);
 112               break;
 113           case KeyEvent.KEY_RELEASED:
 114               keyReleased(e);
 115               break;
 116           case KeyEvent.KEY_TYPED:
 117               keyTyped(e);
 118               break;
 119         }
 120     }
 121 
 122     public void keyTyped(KeyEvent e) {}
 123 
 124     public void keyPressed(KeyEvent e) {
 125         if (e.getKeyCode() == KeyEvent.VK_SPACE)
 126         {
 127             //pressed=true;
 128             //armed=true;
 129             //selected=!selected;
 130             action(!selected);
 131             //repaint();  // Gets the repaint from action()
 132         }
 133 
 134     }
 135 
 136     public void keyReleased(KeyEvent e) {}
 137 
 138     public void  setLabel(java.lang.String label) {
 139         if ( label == null ) {
 140             this.label = "";
 141         } else {
 142             this.label = label;
 143         }
 144         layout();
 145         repaint();
 146     }
 147 
 148     void handleJavaMouseEvent(MouseEvent e) {
 149         super.handleJavaMouseEvent(e);
 150         int i = e.getID();
 151         switch (i) {
 152           case MouseEvent.MOUSE_PRESSED:
 153               mousePressed(e);
 154               break;
 155           case MouseEvent.MOUSE_RELEASED:
 156               mouseReleased(e);
 157               break;
 158           case MouseEvent.MOUSE_ENTERED:
 159               mouseEntered(e);
 160               break;
 161           case MouseEvent.MOUSE_EXITED:
 162               mouseExited(e);
 163               break;
 164           case MouseEvent.MOUSE_CLICKED:
 165               mouseClicked(e);
 166               break;
 167         }
 168     }
 169 
 170     public void mousePressed(MouseEvent e) {
 171         if (XToolkit.isLeftMouseButton(e)) {
 172             Checkbox cb = (Checkbox) e.getSource();
 173 
 174             if (cb.contains(e.getX(), e.getY())) {
 175                 if (log.isLoggable(PlatformLogger.Level.FINER)) {
 176                     log.finer("mousePressed() on " + target.getName() + " : armed = " + armed + ", pressed = " + pressed
 177                               + ", selected = " + selected + ", enabled = " + isEnabled());
 178                 }
 179                 if (!isEnabled()) {
 180                     // Disabled buttons ignore all input...
 181                     return;
 182                 }
 183                 if (!armed) {
 184                     armed = true;
 185                 }
 186                 pressed = true;
 187                 repaint();
 188             }
 189         }
 190     }
 191 
 192     public void mouseReleased(MouseEvent e) {
 193         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 194             log.finer("mouseReleased() on " + target.getName() + ": armed = " + armed + ", pressed = " + pressed
 195                       + ", selected = " + selected + ", enabled = " + isEnabled());
 196         }
 197         boolean sendEvent = false;
 198         if (XToolkit.isLeftMouseButton(e)) {
 199             // TODO: Multiclick Threshold? - see BasicButtonListener.java
 200             if (armed) {
 201                 //selected = !selected;
 202                 // send action event
 203                 //action(e.getWhen(),e.getModifiers());
 204                 sendEvent = true;
 205             }
 206             pressed = false;
 207             armed = false;
 208             if (sendEvent) {
 209                 action(!selected);  // Also gets repaint in action()
 210             }
 211             else {
 212                 repaint();
 213             }
 214         }
 215     }
 216 
 217     public void mouseEntered(MouseEvent e) {
 218         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 219             log.finer("mouseEntered() on " + target.getName() + ": armed = " + armed + ", pressed = " + pressed
 220                       + ", selected = " + selected + ", enabled = " + isEnabled());
 221         }
 222         if (pressed) {
 223             armed = true;
 224             repaint();
 225         }
 226     }
 227 
 228     public void mouseExited(MouseEvent e) {
 229         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 230             log.finer("mouseExited() on " + target.getName() + ": armed = " + armed + ", pressed = " + pressed
 231                       + ", selected = " + selected + ", enabled = " + isEnabled());
 232         }
 233         if (armed) {
 234             armed = false;
 235             repaint();
 236         }
 237     }
 238 
 239     public void mouseClicked(MouseEvent e) {}
 240 
 241     public Dimension getMinimumSize() {
 242         /*
 243          * Spacing (number of pixels between check mark and label text) is
 244          * currently set to 0, but in case it ever changes we have to add
 245          * it. 8 is a heuristic number. Indicator size depends on font
 246          * height, so we don't need to include it in checkbox's height
 247          * calculation.
 248          */
 249         FontMetrics fm = getFontMetrics(getPeerFont());
 250 
 251         int wdth = fm.stringWidth(label) + getCheckboxSize(fm) + (2 * checkBoxInsetFromText) + 8;
 252         int hght = Math.max(fm.getHeight() + 8, 15);
 253 
 254         return new Dimension(wdth, hght);
 255     }
 256 
 257     private int getCheckboxSize(FontMetrics fm) {
 258         // the motif way of sizing is a bit inscutible, but this
 259         // is a fair approximation
 260         return (fm.getHeight() * 76 / 100) - 1;
 261     }
 262 
 263     public void setBackground(Color c) {
 264         updateMotifColors(c);
 265         super.setBackground(c);
 266     }
 267 
 268     /*
 269      * Layout the checkbox/radio button and text label
 270      */
 271     public void layout() {
 272         Dimension size = getPeerSize();
 273         Font f = getPeerFont();
 274         FontMetrics fm = getFontMetrics(f);
 275         String text = label;
 276 
 277         checkBoxSize = getCheckboxSize(fm);
 278 
 279         // Note - Motif appears to use an left inset that is slightly
 280         // scaled to the checkbox/font size.
 281         cbX = borderInsets.left + checkBoxInsetFromText;
 282         cbY = size.height / 2 - checkBoxSize / 2;
 283         int minTextX = borderInsets.left + 2 * checkBoxInsetFromText + checkBoxSize;
 284         // FIXME: will need to account for alignment?
 285         // FIXME: call layout() on alignment changes
 286         //textRect.width = fm.stringWidth(text);
 287         textRect.width = fm.stringWidth(text == null ? "" : text);
 288         textRect.height = fm.getHeight();
 289 
 290         textRect.x = Math.max(minTextX, size.width / 2 - textRect.width / 2);
 291         textRect.y = (size.height - textRect.height) / 2;
 292 
 293         focusRect.x = focusInsets.left;
 294         focusRect.y = focusInsets.top;
 295         focusRect.width = size.width-(focusInsets.left+focusInsets.right)-1;
 296         focusRect.height = size.height-(focusInsets.top+focusInsets.bottom)-1;
 297 
 298         double fsize = (double) checkBoxSize;
 299         myCheckMark = AffineTransform.getScaleInstance(fsize / MASTER_SIZE, fsize / MASTER_SIZE).createTransformedShape(MASTER_CHECKMARK);
 300     }
 301     @Override
 302     void paintPeer(final Graphics g) {
 303         //layout();
 304         Dimension size = getPeerSize();
 305         Font f = getPeerFont();
 306         flush();
 307         g.setColor(getPeerBackground());   // erase the existing button
 308         g.fillRect(0,0, size.width, size.height);
 309         if (label != null) {
 310             g.setFont(f);
 311             paintText(g, textRect, label);
 312         }
 313 
 314         if (hasFocus()) {
 315             paintFocus(g,
 316                        focusRect.x,
 317                        focusRect.y,
 318                        focusRect.width,
 319                        focusRect.height);
 320         }
 321         // Paint the checkbox or radio button
 322         if (checkBoxGroup == null) {
 323             paintCheckbox(g, cbX, cbY, checkBoxSize, checkBoxSize);
 324         }
 325         else {
 326             paintRadioButton(g, cbX, cbY, checkBoxSize, checkBoxSize);
 327         }
 328         flush();
 329     }
 330 
 331     // You'll note this looks suspiciously like paintBorder
 332     public void paintCheckbox(Graphics g,
 333                               int x, int y, int w, int h) {
 334         boolean useBufferedImage = false;
 335         BufferedImage buffer = null;
 336         Graphics2D g2 = null;
 337         int rx = x;
 338         int ry = y;
 339         if (!(g instanceof Graphics2D)) {
 340             // Fix for 5045936. While printing, g is an instance of
 341             //   sun.print.ProxyPrintGraphics which extends Graphics. So
 342             //   we use a separate buffered image and its graphics is
 343             //   always Graphics2D instance
 344             buffer = graphicsConfig.createCompatibleImage(w, h);
 345             g2 = buffer.createGraphics();
 346             useBufferedImage = true;
 347             rx = 0;
 348             ry = 0;
 349         }
 350         else {
 351             g2 = (Graphics2D)g;
 352         }
 353         try {
 354             drawMotif3DRect(g2, rx, ry, w-1, h-1, armed | selected);
 355 
 356             // then paint the check
 357             g2.setColor((armed | selected) ? selectColor : getPeerBackground());
 358             g2.fillRect(rx+1, ry+1, w-2, h-2);
 359 
 360             if (armed | selected) {
 361                 //Paint the check
 362 
 363                 // FIXME: is this the right color?
 364                 g2.setColor(getPeerForeground());
 365 
 366                 AffineTransform af = g2.getTransform();
 367                 g2.setTransform(AffineTransform.getTranslateInstance(rx,ry));
 368                 g2.fill(myCheckMark);
 369                 g2.setTransform(af);
 370             }
 371         } finally {
 372             if (useBufferedImage) {
 373                 g2.dispose();
 374             }
 375         }
 376         if (useBufferedImage) {
 377             g.drawImage(buffer, x, y, null);
 378         }
 379     }
 380     public void setFont(Font f) {
 381         super.setFont(f);
 382         target.repaint();
 383     }
 384 
 385     public void paintRadioButton(Graphics g, int x, int y, int w, int h) {
 386 
 387         g.setColor((armed | selected) ? darkShadow : lightShadow);
 388         g.drawArc(x-1, y-1, w+2, h+2, 45, 180);
 389 
 390         g.setColor((armed | selected) ? lightShadow : darkShadow);
 391         g.drawArc(x-1, y-1, w+2, h+2, 45, -180);
 392 
 393         if (armed | selected) {
 394             g.setColor(selectColor);
 395             g.fillArc(x+1, y+1, w-1, h-1, 0, 360);
 396         }
 397     }
 398 
 399     protected void paintText(Graphics g, Rectangle textRect, String text) {
 400         FontMetrics fm = g.getFontMetrics();
 401 
 402         int mnemonicIndex = -1;
 403 
 404         if(isEnabled()) {
 405             /*** paint the text normally */
 406             g.setColor(getPeerForeground());
 407             BasicGraphicsUtils.drawStringUnderlineCharAt(g,text,mnemonicIndex , textRect.x , textRect.y + fm.getAscent() );
 408         }
 409         else {
 410             /*** paint the text disabled ***/
 411             g.setColor(getPeerBackground().brighter());
 412 
 413             BasicGraphicsUtils.drawStringUnderlineCharAt(g,text, mnemonicIndex,
 414                                                          textRect.x, textRect.y + fm.getAscent());
 415             g.setColor(getPeerBackground().darker());
 416             BasicGraphicsUtils.drawStringUnderlineCharAt(g,text, mnemonicIndex,
 417                                                          textRect.x - 1, textRect.y + fm.getAscent() - 1);
 418         }
 419     }
 420 
 421     // TODO: copied directly from XButtonPeer.  Should probabaly be shared
 422     protected void paintFocus(Graphics g, int x, int y, int w, int h) {
 423         g.setColor(focusColor);
 424         g.drawRect(x,y,w,h);
 425     }
 426 
 427     public void setState(boolean state) {
 428         if (selected != state) {
 429             selected = state;
 430             repaint();
 431         }
 432     }
 433     public void setCheckboxGroup(CheckboxGroup g) {
 434         // If changed from grouped/ungrouped, need to repaint()
 435         checkBoxGroup = g;
 436         repaint();
 437     }
 438 
 439     // NOTE: This method is called by privileged threads.
 440     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
 441     // From MCheckboxPeer
 442     void action(boolean state) {
 443         final Checkbox cb = (Checkbox)target;
 444         final boolean newState = state;
 445         XToolkit.executeOnEventHandlerThread(cb, new Runnable() {
 446                 public void run() {
 447                     CheckboxGroup cbg = checkBoxGroup;
 448                     // Bugid 4039594. If this is the current Checkbox in
 449                     // a CheckboxGroup, then return to prevent deselection.
 450                     // Otherwise, it's logical state will be turned off,
 451                     // but it will appear on.
 452                     if ((cbg != null) && (cbg.getSelectedCheckbox() == cb) &&
 453                         cb.getState()) {
 454                         //inUpCall = false;
 455                         cb.setState(true);
 456                         return;
 457                     }
 458                     // All clear - set the new state
 459                     cb.setState(newState);
 460                     notifyStateChanged(newState);
 461                 }
 462             });
 463     }
 464 
 465     void notifyStateChanged(boolean state) {
 466         Checkbox cb = (Checkbox) target;
 467         ItemEvent e = new ItemEvent(cb,
 468                                     ItemEvent.ITEM_STATE_CHANGED,
 469                                     cb.getLabel(),
 470                                     state ? ItemEvent.SELECTED : ItemEvent.DESELECTED);
 471         postEvent(e);
 472     }
 473 }