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 }