001: /*
002: * Copyright 2002-2007 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.awt.X11;
027:
028: import java.awt.*;
029: import java.awt.peer.*;
030: import java.awt.event.*;
031: import java.awt.image.BufferedImage;
032: import javax.swing.plaf.basic.BasicGraphicsUtils;
033: import java.awt.geom.AffineTransform;
034:
035: import java.util.logging.*;
036:
037: class XCheckboxPeer extends XComponentPeer implements CheckboxPeer {
038:
039: private static final Logger log = Logger
040: .getLogger("sun.awt.X11.XCheckboxPeer");
041:
042: private static final Insets focusInsets = new Insets(0, 0, 0, 0);
043: private static final Insets borderInsets = new Insets(2, 2, 2, 2);
044: private static final int checkBoxInsetFromText = 2;
045:
046: //The check mark is less common than a plain "depressed" button,
047: //so don't use the checkmark.
048: // The checkmark shape:
049: private static final double MASTER_SIZE = 128.0;
050: private static final Polygon MASTER_CHECKMARK = new Polygon(
051: new int[] { 1, 25, 56, 124, 124, 85, 64 }, // X-coords
052: new int[] { 59, 35, 67, 0, 12, 66, 123 }, // Y-coords
053: 7);
054:
055: private Shape myCheckMark;
056:
057: private Color focusColor = SystemColor.windowText;
058:
059: private boolean pressed;
060: private boolean armed;
061: private boolean selected;
062:
063: private Rectangle textRect;
064: private Rectangle focusRect;
065: private int checkBoxSize;
066: private int cbX;
067: private int cbY;
068:
069: String label;
070: CheckboxGroup checkBoxGroup;
071:
072: XCheckboxPeer(Checkbox target) {
073: super (target);
074: pressed = false;
075: armed = false;
076: selected = target.getState();
077: label = target.getLabel();
078: if (label == null) {
079: label = "";
080: }
081: checkBoxGroup = target.getCheckboxGroup();
082: updateMotifColors(getPeerBackground());
083: }
084:
085: public void preInit(XCreateWindowParams params) {
086: // Put this here so it is executed before layout() is called from
087: // setFont() in XComponent.postInit()
088: textRect = new Rectangle();
089: focusRect = new Rectangle();
090: super .preInit(params);
091: }
092:
093: public boolean isFocusable() {
094: return true;
095: }
096:
097: public void focusGained(FocusEvent e) {
098: // TODO: only need to paint the focus bit
099: super .focusGained(e);
100: repaint();
101: }
102:
103: public void focusLost(FocusEvent e) {
104: // TODO: only need to paint the focus bit?
105: super .focusLost(e);
106: repaint();
107: }
108:
109: void handleJavaKeyEvent(KeyEvent e) {
110: int i = e.getID();
111: switch (i) {
112: case KeyEvent.KEY_PRESSED:
113: keyPressed(e);
114: break;
115: case KeyEvent.KEY_RELEASED:
116: keyReleased(e);
117: break;
118: case KeyEvent.KEY_TYPED:
119: keyTyped(e);
120: break;
121: }
122: }
123:
124: public void keyTyped(KeyEvent e) {
125: }
126:
127: public void keyPressed(KeyEvent e) {
128: if (e.getKeyCode() == KeyEvent.VK_SPACE) {
129: //pressed=true;
130: //armed=true;
131: //selected=!selected;
132: action(!selected);
133: //repaint(); // Gets the repaint from action()
134: }
135:
136: }
137:
138: public void keyReleased(KeyEvent e) {
139: }
140:
141: public void setLabel(java.lang.String label) {
142: if (label == null) {
143: this .label = "";
144: } else {
145: this .label = label;
146: }
147: layout();
148: repaint();
149: }
150:
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:
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(Level.FINER)) {
179: log.finer("mousePressed() on " + target.getName()
180: + " : armed = " + armed + ", pressed = "
181: + pressed + ", selected = " + selected
182: + ", enabled = " + isEnabled());
183: }
184: if (!isEnabled()) {
185: // Disabled buttons ignore all input...
186: return;
187: }
188: if (!armed) {
189: armed = true;
190: }
191: pressed = true;
192: repaint();
193: }
194: }
195: }
196:
197: public void mouseReleased(MouseEvent e) {
198: if (log.isLoggable(Level.FINER)) {
199: log.finer("mouseReleased() on " + target.getName()
200: + ": armed = " + armed + ", pressed = " + pressed
201: + ", selected = " + selected + ", enabled = "
202: + isEnabled());
203: }
204: boolean sendEvent = false;
205: if (XToolkit.isLeftMouseButton(e)) {
206: // TODO: Multiclick Threshold? - see BasicButtonListener.java
207: if (armed) {
208: //selected = !selected;
209: // send action event
210: //action(e.getWhen(),e.getModifiers());
211: sendEvent = true;
212: }
213: pressed = false;
214: armed = false;
215: if (sendEvent) {
216: action(!selected); // Also gets repaint in action()
217: } else {
218: repaint();
219: }
220: }
221: }
222:
223: public void mouseEntered(MouseEvent e) {
224: if (log.isLoggable(Level.FINER)) {
225: log.finer("mouseEntered() on " + target.getName()
226: + ": armed = " + armed + ", pressed = " + pressed
227: + ", selected = " + selected + ", enabled = "
228: + isEnabled());
229: }
230: if (pressed) {
231: armed = true;
232: repaint();
233: }
234: }
235:
236: public void mouseExited(MouseEvent e) {
237: if (log.isLoggable(Level.FINER)) {
238: log.finer("mouseExited() on " + target.getName()
239: + ": armed = " + armed + ", pressed = " + pressed
240: + ", selected = " + selected + ", enabled = "
241: + isEnabled());
242: }
243: if (armed) {
244: armed = false;
245: repaint();
246: }
247: }
248:
249: public void mouseClicked(MouseEvent e) {
250: }
251:
252: public Dimension getMinimumSize() {
253: /*
254: * Spacing (number of pixels between check mark and label text) is
255: * currently set to 0, but in case it ever changes we have to add
256: * it. 8 is a heuristic number. Indicator size depends on font
257: * height, so we don't need to include it in checkbox's height
258: * calculation.
259: */
260: FontMetrics fm = getFontMetrics(getPeerFont());
261:
262: int wdth = fm.stringWidth(label) + getCheckboxSize(fm)
263: + (2 * checkBoxInsetFromText) + 8;
264: int hght = Math.max(fm.getHeight() + 8, 15);
265:
266: return new Dimension(wdth, hght);
267: }
268:
269: private int getCheckboxSize(FontMetrics fm) {
270: // the motif way of sizing is a bit inscutible, but this
271: // is a fair approximation
272: return (fm.getHeight() * 76 / 100) - 1;
273: }
274:
275: public void setBackground(Color c) {
276: updateMotifColors(c);
277: super .setBackground(c);
278: }
279:
280: /*
281: * Layout the checkbox/radio button and text label
282: */
283: public void layout() {
284: Dimension size = getPeerSize();
285: Font f = getPeerFont();
286: FontMetrics fm = getFontMetrics(f);
287: String text = label;
288:
289: checkBoxSize = getCheckboxSize(fm);
290:
291: // Note - Motif appears to use an left inset that is slightly
292: // scaled to the checkbox/font size.
293: cbX = borderInsets.left + checkBoxInsetFromText;
294: cbY = size.height / 2 - checkBoxSize / 2;
295: int minTextX = borderInsets.left + 2 * checkBoxInsetFromText
296: + checkBoxSize;
297: // FIXME: will need to account for alignment?
298: // FIXME: call layout() on alignment changes
299: //textRect.width = fm.stringWidth(text);
300: textRect.width = fm.stringWidth(text == null ? "" : text);
301: textRect.height = fm.getHeight();
302:
303: textRect.x = Math.max(minTextX, size.width / 2 - textRect.width
304: / 2);
305: textRect.y = (size.height - textRect.height) / 2;
306:
307: focusRect.x = focusInsets.left;
308: focusRect.y = focusInsets.top;
309: focusRect.width = size.width
310: - (focusInsets.left + focusInsets.right) - 1;
311: focusRect.height = size.height
312: - (focusInsets.top + focusInsets.bottom) - 1;
313:
314: double fsize = (double) checkBoxSize;
315: myCheckMark = AffineTransform.getScaleInstance(
316: fsize / MASTER_SIZE, fsize / MASTER_SIZE)
317: .createTransformedShape(MASTER_CHECKMARK);
318:
319: }
320:
321: public void paint(Graphics g) {
322: if (g != null) {
323: //layout();
324: Dimension size = getPeerSize();
325: Font f = getPeerFont();
326:
327: flush();
328: g.setColor(getPeerBackground()); // erase the existing button
329: g.fillRect(0, 0, size.width, size.height);
330:
331: if (label != null) {
332: g.setFont(f);
333: paintText(g, textRect, label);
334: }
335:
336: if (hasFocus()) {
337: paintFocus(g, focusRect.x, focusRect.y,
338: focusRect.width, focusRect.height);
339: }
340:
341: // Paint the checkbox or radio button
342: if (checkBoxGroup == null) {
343: paintCheckbox(g, cbX, cbY, checkBoxSize, checkBoxSize);
344: } else {
345: paintRadioButton(g, cbX, cbY, checkBoxSize,
346: checkBoxSize);
347: }
348:
349: }
350: flush();
351: }
352:
353: // You'll note this looks suspiciously like paintBorder
354: public void paintCheckbox(Graphics g, int x, int y, int w, int h) {
355: boolean useBufferedImage = false;
356: BufferedImage buffer = null;
357: Graphics2D g2 = null;
358: int rx = x;
359: int ry = y;
360: if (!(g instanceof Graphics2D)) {
361: // Fix for 5045936. While printing, g is an instance of
362: // sun.print.ProxyPrintGraphics which extends Graphics. So
363: // we use a separate buffered image and its graphics is
364: // always Graphics2D instance
365: buffer = graphicsConfig.createCompatibleImage(w, h);
366: g2 = buffer.createGraphics();
367: useBufferedImage = true;
368: rx = 0;
369: ry = 0;
370: } else {
371: g2 = (Graphics2D) g;
372: }
373: try {
374: drawMotif3DRect(g2, rx, ry, w - 1, h - 1, armed | selected);
375:
376: // then paint the check
377: g2.setColor((armed | selected) ? selectColor
378: : getPeerBackground());
379: g2.fillRect(rx + 1, ry + 1, w - 2, h - 2);
380:
381: if (armed | selected) {
382: //Paint the check
383:
384: // FIXME: is this the right color?
385: g2.setColor(getPeerForeground());
386:
387: AffineTransform af = g2.getTransform();
388: g2.setTransform(AffineTransform.getTranslateInstance(
389: rx, ry));
390: g2.fill(myCheckMark);
391: g2.setTransform(af);
392: }
393: } finally {
394: if (useBufferedImage) {
395: g2.dispose();
396: }
397: }
398: if (useBufferedImage) {
399: g.drawImage(buffer, x, y, null);
400: }
401: }
402:
403: public void setFont(Font f) {
404: super .setFont(f);
405: target.repaint();
406: }
407:
408: public void paintRadioButton(Graphics g, int x, int y, int w, int h) {
409:
410: g.setColor((armed | selected) ? darkShadow : lightShadow);
411: g.drawArc(x - 1, y - 1, w + 2, h + 2, 45, 180);
412:
413: g.setColor((armed | selected) ? lightShadow : darkShadow);
414: g.drawArc(x - 1, y - 1, w + 2, h + 2, 45, -180);
415:
416: if (armed | selected) {
417: g.setColor(selectColor);
418: g.fillArc(x + 1, y + 1, w - 1, h - 1, 0, 360);
419: }
420: }
421:
422: protected void paintText(Graphics g, Rectangle textRect, String text) {
423: FontMetrics fm = g.getFontMetrics();
424:
425: int mnemonicIndex = -1;
426:
427: if (isEnabled()) {
428: /*** paint the text normally */
429: g.setColor(getPeerForeground());
430: BasicGraphicsUtils.drawStringUnderlineCharAt(g, text,
431: mnemonicIndex, textRect.x, textRect.y
432: + fm.getAscent());
433: } else {
434: /*** paint the text disabled ***/
435: g.setColor(getPeerBackground().brighter());
436:
437: BasicGraphicsUtils.drawStringUnderlineCharAt(g, text,
438: mnemonicIndex, textRect.x, textRect.y
439: + fm.getAscent());
440: g.setColor(getPeerBackground().darker());
441: BasicGraphicsUtils.drawStringUnderlineCharAt(g, text,
442: mnemonicIndex, textRect.x - 1, textRect.y
443: + fm.getAscent() - 1);
444: }
445: }
446:
447: // TODO: copied directly from XButtonPeer. Should probabaly be shared
448: protected void paintFocus(Graphics g, int x, int y, int w, int h) {
449: g.setColor(focusColor);
450: g.drawRect(x, y, w, h);
451: }
452:
453: public void setState(boolean state) {
454: if (selected != state) {
455: selected = state;
456: repaint();
457: }
458: }
459:
460: public void setCheckboxGroup(CheckboxGroup g) {
461: // If changed from grouped/ungrouped, need to repaint()
462: checkBoxGroup = g;
463: repaint();
464: }
465:
466: // NOTE: This method is called by privileged threads.
467: // DO NOT INVOKE CLIENT CODE ON THIS THREAD!
468: // From MCheckboxPeer
469: void action(boolean state) {
470: final Checkbox cb = (Checkbox) target;
471: final boolean newState = state;
472: XToolkit.executeOnEventHandlerThread(cb, new Runnable() {
473: public void run() {
474: CheckboxGroup cbg = checkBoxGroup;
475: // Bugid 4039594. If this is the current Checkbox in
476: // a CheckboxGroup, then return to prevent deselection.
477: // Otherwise, it's logical state will be turned off,
478: // but it will appear on.
479: if ((cbg != null) && (cbg.getSelectedCheckbox() == cb)
480: && cb.getState()) {
481: //inUpCall = false;
482: cb.setState(true);
483: return;
484: }
485: // All clear - set the new state
486: cb.setState(newState);
487: notifyStateChanged(newState);
488: }
489: });
490: }
491:
492: void notifyStateChanged(boolean state) {
493: Checkbox cb = (Checkbox) target;
494: ItemEvent e = new ItemEvent(cb, ItemEvent.ITEM_STATE_CHANGED,
495: cb.getLabel(), state ? ItemEvent.SELECTED
496: : ItemEvent.DESELECTED);
497: postEvent(e);
498: }
499: }
|