J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2002-2007 Sun Microsystems, Inc. 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. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any 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 java.util.logging.*; |
| 36 | |
| 37 | class XCheckboxPeer extends XComponentPeer implements CheckboxPeer { |
| 38 | |
| 39 | private static final Logger log = Logger.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(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(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(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(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 | } |
| 302 | |
| 303 | public void paint(Graphics g) { |
| 304 | if (g != null) { |
| 305 | //layout(); |
| 306 | Dimension size = getPeerSize(); |
| 307 | Font f = getPeerFont(); |
| 308 | |
| 309 | flush(); |
| 310 | g.setColor(getPeerBackground()); // erase the existing button |
| 311 | g.fillRect(0,0, size.width, size.height); |
| 312 | |
| 313 | if (label != null) { |
| 314 | g.setFont(f); |
| 315 | paintText(g, textRect, label); |
| 316 | } |
| 317 | |
| 318 | if (hasFocus()) { |
| 319 | paintFocus(g, |
| 320 | focusRect.x, |
| 321 | focusRect.y, |
| 322 | focusRect.width, |
| 323 | focusRect.height); |
| 324 | } |
| 325 | |
| 326 | // Paint the checkbox or radio button |
| 327 | if (checkBoxGroup == null) { |
| 328 | paintCheckbox(g, cbX, cbY, checkBoxSize, checkBoxSize); |
| 329 | } |
| 330 | else { |
| 331 | paintRadioButton(g, cbX, cbY, checkBoxSize, checkBoxSize); |
| 332 | } |
| 333 | |
| 334 | } |
| 335 | flush(); |
| 336 | } |
| 337 | |
| 338 | // You'll note this looks suspiciously like paintBorder |
| 339 | public void paintCheckbox(Graphics g, |
| 340 | int x, int y, int w, int h) { |
| 341 | boolean useBufferedImage = false; |
| 342 | BufferedImage buffer = null; |
| 343 | Graphics2D g2 = null; |
| 344 | int rx = x; |
| 345 | int ry = y; |
| 346 | if (!(g instanceof Graphics2D)) { |
| 347 | // Fix for 5045936. While printing, g is an instance of |
| 348 | // sun.print.ProxyPrintGraphics which extends Graphics. So |
| 349 | // we use a separate buffered image and its graphics is |
| 350 | // always Graphics2D instance |
| 351 | buffer = graphicsConfig.createCompatibleImage(w, h); |
| 352 | g2 = buffer.createGraphics(); |
| 353 | useBufferedImage = true; |
| 354 | rx = 0; |
| 355 | ry = 0; |
| 356 | } |
| 357 | else { |
| 358 | g2 = (Graphics2D)g; |
| 359 | } |
| 360 | try { |
| 361 | drawMotif3DRect(g2, rx, ry, w-1, h-1, armed | selected); |
| 362 | |
| 363 | // then paint the check |
| 364 | g2.setColor((armed | selected) ? selectColor : getPeerBackground()); |
| 365 | g2.fillRect(rx+1, ry+1, w-2, h-2); |
| 366 | |
| 367 | if (armed | selected) { |
| 368 | //Paint the check |
| 369 | |
| 370 | // FIXME: is this the right color? |
| 371 | g2.setColor(getPeerForeground()); |
| 372 | |
| 373 | AffineTransform af = g2.getTransform(); |
| 374 | g2.setTransform(AffineTransform.getTranslateInstance(rx,ry)); |
| 375 | g2.fill(myCheckMark); |
| 376 | g2.setTransform(af); |
| 377 | } |
| 378 | } finally { |
| 379 | if (useBufferedImage) { |
| 380 | g2.dispose(); |
| 381 | } |
| 382 | } |
| 383 | if (useBufferedImage) { |
| 384 | g.drawImage(buffer, x, y, null); |
| 385 | } |
| 386 | } |
| 387 | public void setFont(Font f) { |
| 388 | super.setFont(f); |
| 389 | target.repaint(); |
| 390 | } |
| 391 | |
| 392 | public void paintRadioButton(Graphics g, int x, int y, int w, int h) { |
| 393 | |
| 394 | g.setColor((armed | selected) ? darkShadow : lightShadow); |
| 395 | g.drawArc(x-1, y-1, w+2, h+2, 45, 180); |
| 396 | |
| 397 | g.setColor((armed | selected) ? lightShadow : darkShadow); |
| 398 | g.drawArc(x-1, y-1, w+2, h+2, 45, -180); |
| 399 | |
| 400 | if (armed | selected) { |
| 401 | g.setColor(selectColor); |
| 402 | g.fillArc(x+1, y+1, w-1, h-1, 0, 360); |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | protected void paintText(Graphics g, Rectangle textRect, String text) { |
| 407 | FontMetrics fm = g.getFontMetrics(); |
| 408 | |
| 409 | int mnemonicIndex = -1; |
| 410 | |
| 411 | if(isEnabled()) { |
| 412 | /*** paint the text normally */ |
| 413 | g.setColor(getPeerForeground()); |
| 414 | BasicGraphicsUtils.drawStringUnderlineCharAt(g,text,mnemonicIndex , textRect.x , textRect.y + fm.getAscent() ); |
| 415 | } |
| 416 | else { |
| 417 | /*** paint the text disabled ***/ |
| 418 | g.setColor(getPeerBackground().brighter()); |
| 419 | |
| 420 | BasicGraphicsUtils.drawStringUnderlineCharAt(g,text, mnemonicIndex, |
| 421 | textRect.x, textRect.y + fm.getAscent()); |
| 422 | g.setColor(getPeerBackground().darker()); |
| 423 | BasicGraphicsUtils.drawStringUnderlineCharAt(g,text, mnemonicIndex, |
| 424 | textRect.x - 1, textRect.y + fm.getAscent() - 1); |
| 425 | } |
| 426 | } |
| 427 | |
| 428 | // TODO: copied directly from XButtonPeer. Should probabaly be shared |
| 429 | protected void paintFocus(Graphics g, int x, int y, int w, int h) { |
| 430 | g.setColor(focusColor); |
| 431 | g.drawRect(x,y,w,h); |
| 432 | } |
| 433 | |
| 434 | public void setState(boolean state) { |
| 435 | if (selected != state) { |
| 436 | selected = state; |
| 437 | repaint(); |
| 438 | } |
| 439 | } |
| 440 | public void setCheckboxGroup(CheckboxGroup g) { |
| 441 | // If changed from grouped/ungrouped, need to repaint() |
| 442 | checkBoxGroup = g; |
| 443 | repaint(); |
| 444 | } |
| 445 | |
| 446 | // NOTE: This method is called by privileged threads. |
| 447 | // DO NOT INVOKE CLIENT CODE ON THIS THREAD! |
| 448 | // From MCheckboxPeer |
| 449 | void action(boolean state) { |
| 450 | final Checkbox cb = (Checkbox)target; |
| 451 | final boolean newState = state; |
| 452 | XToolkit.executeOnEventHandlerThread(cb, new Runnable() { |
| 453 | public void run() { |
| 454 | CheckboxGroup cbg = checkBoxGroup; |
| 455 | // Bugid 4039594. If this is the current Checkbox in |
| 456 | // a CheckboxGroup, then return to prevent deselection. |
| 457 | // Otherwise, it's logical state will be turned off, |
| 458 | // but it will appear on. |
| 459 | if ((cbg != null) && (cbg.getSelectedCheckbox() == cb) && |
| 460 | cb.getState()) { |
| 461 | //inUpCall = false; |
| 462 | cb.setState(true); |
| 463 | return; |
| 464 | } |
| 465 | // All clear - set the new state |
| 466 | cb.setState(newState); |
| 467 | notifyStateChanged(newState); |
| 468 | } |
| 469 | }); |
| 470 | } |
| 471 | |
| 472 | void notifyStateChanged(boolean state) { |
| 473 | Checkbox cb = (Checkbox) target; |
| 474 | ItemEvent e = new ItemEvent(cb, |
| 475 | ItemEvent.ITEM_STATE_CHANGED, |
| 476 | cb.getLabel(), |
| 477 | state ? ItemEvent.SELECTED : ItemEvent.DESELECTED); |
| 478 | postEvent(e); |
| 479 | } |
| 480 | } |