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