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