1 /*
2  * Copyright (c) 2003, 2018, 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.Color;
29 import java.awt.Insets;
30 import java.awt.Point;
31 import java.awt.FontMetrics;
32 import java.awt.Dimension;
33 import java.awt.Rectangle;
34 import java.awt.Choice;
35 import java.awt.Toolkit;
36 import java.awt.Graphics;
37 import java.awt.Component;
38 import java.awt.AWTEvent;
39 import java.awt.Insets;
40 import java.awt.Font;
41 
42 import java.awt.peer.ChoicePeer;
43 
44 import java.awt.event.FocusEvent;
45 import java.awt.event.InvocationEvent;
46 import java.awt.event.MouseEvent;
47 import java.awt.event.MouseWheelEvent;
48 import java.awt.event.KeyEvent;
49 import java.awt.event.ItemEvent;
50 
51 import sun.util.logging.PlatformLogger;
52 
53 // FIXME: tab traversal should be disabled when mouse is captured (4816336)
54 
55 // FIXME: key and mouse events should not be delivered to listeners when the Choice is unfurled.  Must override handleNativeKey/MouseEvent (4816336)
56 
57 // FIXME: test programmatic add/remove/clear/etc
58 
59 // FIXME: account for unfurling at the edge of the screen
60 // Note: can't set x,y on layout(), 'cause moving the top-level to the
61 // edge of the screen won't call layout().  Just do it on paint, I guess
62 
63 // TODO: make painting more efficient (i.e. when down arrow is pressed, only two items should need to be repainted.
64 
65 public final class XChoicePeer extends XComponentPeer implements ChoicePeer, ToplevelStateListener {
66     private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XChoicePeer");
67 
68     private static final int MAX_UNFURLED_ITEMS = 10;  // Maximum number of
69     // items to be displayed
70     // at a time in an
71     // unfurled Choice
72     // Description of these constants in ListHelper
73     public static final int TEXT_SPACE = 1;
74     public static final int BORDER_WIDTH = 1;
75     public static final int ITEM_MARGIN = 1;
76     public static final int SCROLLBAR_WIDTH = 15;
77 
78 
79     // SHARE THESE!
80     private static final Insets focusInsets = new Insets(0,0,0,0);
81 
82 
83     static final int WIDGET_OFFSET = 18;
84 
85     // Stolen from Tiny
86     static final int            TEXT_XPAD = 8;
87     static final int            TEXT_YPAD = 6;
88 
89     // FIXME: Motif uses a different focus color for the item within
90     // the unfurled Choice list and for when the Choice itself is focused and
91     // popped up.
92     static final Color focusColor = Color.black;
93 
94     // TODO: there is a time value that the mouse is held down.  If short
95     // enough,  the Choice stays popped down.  If long enough, Choice
96     // is furled when the mouse is released
97 
98     private boolean unfurled = false;        // Choice list is popped down
99 
100     private boolean dragging = false;        // Mouse was pressed and is being
101                                              // dragged over the (unfurled)
102                                              // Choice
103 
104     private boolean mouseInSB = false;       // Mouse is interacting with the
105                                              // scrollbar
106 
107     private boolean firstPress = false;      // mouse was pressed on
108                                              // furled Choice so we
109                                              // not need to furl the
110                                              // Choice when MOUSE_RELEASED occurred
111 
112     // 6425067. Mouse was pressed on furled choice and dropdown list appeared over Choice itself
113     // and then there were no mouse movements until MOUSE_RELEASE.
114     // This scenario leads to ItemStateChanged as the choice logic uses
115     // MouseReleased event to send ItemStateChanged. To prevent it we should
116     // use a combination of firstPress and wasDragged variables.
117     // The only difference in dragging and wasDragged is: last one will not
118     // set to false on mouse ungrab. It become false after MouseRelased() finishes.
119     private boolean wasDragged = false;
120     private ListHelper helper;
121     private UnfurledChoice unfurledChoice;
122 
123     // TODO: Choice remembers where it was scrolled to when unfurled - it's not
124     // always to the currently selected item.
125 
126     // Indicates whether or not to paint selected item in the choice.
127     // Default is to paint
128     private boolean drawSelectedItem = true;
129 
130     // If set, indicates components under which choice popup should be showed.
131     // The choice's popup width and location should be adjust to appear
132     // under both choice and alignUnder component.
133     private Component alignUnder;
134 
135     // If cursor is outside of an unfurled Choice when the mouse is
136     // released, Choice item should NOT be updated.  Remember the proper index.
137     private int dragStartIdx = -1;
138 
139     // Holds the listener (XFileDialogPeer) which the processing events from the choice
140     // See 6240074 for more information
141     private XChoicePeerListener choiceListener;
142 
XChoicePeer(Choice target)143     XChoicePeer(Choice target) {
144         super(target);
145     }
146 
preInit(XCreateWindowParams params)147     void preInit(XCreateWindowParams params) {
148         super.preInit(params);
149         Choice target = (Choice)this.target;
150         int numItems = target.getItemCount();
151         unfurledChoice = new UnfurledChoice(target);
152         getToplevelXWindow().addToplevelStateListener(this);
153         helper = new ListHelper(unfurledChoice,
154                                 getGUIcolors(),
155                                 numItems,
156                                 false,
157                                 true,
158                                 false,
159                                 target.getFont(),
160                                 MAX_UNFURLED_ITEMS,
161                                 TEXT_SPACE,
162                                 ITEM_MARGIN,
163                                 BORDER_WIDTH,
164                                 SCROLLBAR_WIDTH);
165     }
166 
postInit(XCreateWindowParams params)167     void postInit(XCreateWindowParams params) {
168         super.postInit(params);
169         Choice target = (Choice)this.target;
170         int numItems = target.getItemCount();
171 
172         // Add all items
173         for (int i = 0; i < numItems; i++) {
174             helper.add(target.getItem(i));
175         }
176         if (!helper.isEmpty()) {
177             helper.select(target.getSelectedIndex());
178             helper.setFocusedIndex(target.getSelectedIndex());
179         }
180         helper.updateColors(getGUIcolors());
181         updateMotifColors(getPeerBackground());
182     }
183 
isFocusable()184     public boolean isFocusable() { return true; }
185 
186     // 6399679. check if super.setBounds() actually changes the size of the
187     // component and then compare current Choice size with a new one. If
188     // they differs then hide dropdown menu
setBounds(int x, int y, int width, int height, int op)189     public void setBounds(int x, int y, int width, int height, int op) {
190         int oldX = this.x;
191         int oldY = this.y;
192         int oldWidth = this.width;
193         int oldHeight = this.height;
194         super.setBounds(x, y, width, height, op);
195         if (unfurled && (oldX != this.x || oldY != this.y || oldWidth != this.width || oldHeight != this.height) ) {
196             hidePopdownMenu();
197         }
198     }
199 
focusGained(FocusEvent e)200     public void focusGained(FocusEvent e) {
201         // TODO: only need to paint the focus bit
202         super.focusGained(e);
203         repaint();
204     }
205 
206     /*
207      * Fix for 6246503 : Disabling a choice after selection locks keyboard, mouse and makes the system unusable, Xtoolkit
208      * if setEnabled(false) invoked we should close opened choice in
209      * order to prevent keyboard/mouse lock.
210      */
setEnabled(boolean value)211     public void setEnabled(boolean value) {
212         super.setEnabled(value);
213         helper.updateColors(getGUIcolors());
214         if (!value && unfurled){
215             hidePopdownMenu();
216         }
217     }
218 
focusLost(FocusEvent e)219     public void focusLost(FocusEvent e) {
220         // TODO: only need to paint the focus bit?
221         super.focusLost(e);
222         repaint();
223     }
224 
ungrabInputImpl()225     void ungrabInputImpl() {
226         if (unfurled) {
227             unfurled = false;
228             dragging = false;
229             mouseInSB = false;
230             unfurledChoice.setVisible(false);
231         }
232 
233         super.ungrabInputImpl();
234     }
235 
handleJavaKeyEvent(KeyEvent e)236     void handleJavaKeyEvent(KeyEvent e) {
237         if (e.getID() == KeyEvent.KEY_PRESSED) {
238             keyPressed(e);
239         }
240     }
241 
keyPressed(KeyEvent e)242     public void keyPressed(KeyEvent e) {
243         switch(e.getKeyCode()) {
244             // UP & DOWN are same if furled or unfurled
245           case KeyEvent.VK_DOWN:
246           case KeyEvent.VK_KP_DOWN: {
247               if (helper.getItemCount() > 1) {
248                   helper.down();
249                   int newIdx = helper.getSelectedIndex();
250 
251                   if (((Choice)target).getSelectedIndex() != newIdx) {
252                         ((Choice)target).select(newIdx);
253                         postEvent(new ItemEvent((Choice)target,
254                                                 ItemEvent.ITEM_STATE_CHANGED,
255                                                 ((Choice)target).getItem(newIdx),
256                                                 ItemEvent.SELECTED));
257                         repaint();
258                   }
259               }
260               break;
261           }
262           case KeyEvent.VK_UP:
263           case KeyEvent.VK_KP_UP: {
264               if (helper.getItemCount() > 1) {
265                   helper.up();
266                   int newIdx = helper.getSelectedIndex();
267 
268                   if (((Choice)target).getSelectedIndex() != newIdx) {
269                         ((Choice)target).select(newIdx);
270                         postEvent(new ItemEvent((Choice)target,
271                                                 ItemEvent.ITEM_STATE_CHANGED,
272                                                 ((Choice)target).getItem(newIdx),
273                                                 ItemEvent.SELECTED));
274                         repaint();
275                   }
276               }
277               break;
278           }
279           case KeyEvent.VK_PAGE_DOWN:
280               if (unfurled && !dragging) {
281                   int oldIdx = helper.getSelectedIndex();
282                   helper.pageDown();
283                   int newIdx = helper.getSelectedIndex();
284                   if (oldIdx != newIdx) {
285                       ((Choice)target).select(newIdx);
286                       postEvent(new ItemEvent((Choice)target,
287                                               ItemEvent.ITEM_STATE_CHANGED,
288                                               ((Choice)target).getItem(newIdx),
289                                               ItemEvent.SELECTED));
290                       repaint();
291                   }
292               }
293               break;
294           case KeyEvent.VK_PAGE_UP:
295               if (unfurled && !dragging) {
296                   int oldIdx = helper.getSelectedIndex();
297                   helper.pageUp();
298                   int newIdx = helper.getSelectedIndex();
299                   if (oldIdx != newIdx) {
300                       ((Choice)target).select(newIdx);
301                       postEvent(new ItemEvent((Choice)target,
302                                               ItemEvent.ITEM_STATE_CHANGED,
303                                               ((Choice)target).getItem(newIdx),
304                                               ItemEvent.SELECTED));
305                       repaint();
306                   }
307               }
308               break;
309           case KeyEvent.VK_ESCAPE:
310           case KeyEvent.VK_ENTER:
311               if (unfurled) {
312                   if (dragging){
313                       if (e.getKeyCode() == KeyEvent.VK_ESCAPE){
314                           //This also happens on
315                           // - MouseButton2,3, etc. press
316                           // - ENTER press
317                           helper.select(dragStartIdx);
318                       } else { //KeyEvent.VK_ENTER:
319                           int newIdx = helper.getSelectedIndex();
320                           if (newIdx != (((Choice)target).getSelectedIndex())) {
321                             ((Choice)target).select(newIdx);
322                             postEvent(new ItemEvent((Choice)target,
323                                                     ItemEvent.ITEM_STATE_CHANGED,
324                                                     ((Choice)target).getItem(newIdx),
325                                                     ItemEvent.SELECTED));
326                           }
327                       }
328                   }
329                   hidePopdownMenu();
330                   dragging = false;
331                   wasDragged = false;
332                   mouseInSB = false;
333 
334                   // See 6240074 for more information
335                   if (choiceListener != null){
336                       choiceListener.unfurledChoiceClosing();
337                   }
338               }
339               break;
340           default:
341               if (unfurled) {
342                   Toolkit.getDefaultToolkit().beep();
343               }
344               break;
345         }
346     }
347 
handlesWheelScrolling()348     public boolean handlesWheelScrolling() { return true; }
349 
handleJavaMouseWheelEvent(MouseWheelEvent e)350     void handleJavaMouseWheelEvent(MouseWheelEvent e) {
351         if (unfurled && helper.isVSBVisible()) {
352             if (ListHelper.doWheelScroll(helper.getVSB(), null, e)) {
353                 repaint();
354             }
355         }
356     }
357 
handleJavaMouseEvent(MouseEvent e)358     void handleJavaMouseEvent(MouseEvent e) {
359         super.handleJavaMouseEvent(e);
360         int i = e.getID();
361         switch (i) {
362           case MouseEvent.MOUSE_PRESSED:
363               mousePressed(e);
364               break;
365           case MouseEvent.MOUSE_RELEASED:
366               mouseReleased(e);
367               break;
368           case MouseEvent.MOUSE_DRAGGED:
369               mouseDragged(e);
370               break;
371         }
372     }
373 
mousePressed(MouseEvent e)374     public void mousePressed(MouseEvent e) {
375         /*
376          * fix for 5003166: a Choice on XAWT shouldn't react to any
377          * mouse button presses except left. This involves presses on
378          * Choice but not on opened part of choice.
379          */
380         if (e.getButton() == MouseEvent.BUTTON1){
381             dragStartIdx = helper.getSelectedIndex();
382             if (unfurled) {
383                 //fix 6259328: PIT: Choice scrolls when dragging the parent frame while drop-down is active, XToolkit
384                 if (! (isMouseEventInChoice(e) ||
385                        unfurledChoice.isMouseEventInside(e)))
386                 {
387                     hidePopdownMenu();
388                 }
389                 // Press on unfurled Choice.  Highlight the item under the cursor,
390                 // but don't send item event or set the text on the button yet
391                 unfurledChoice.trackMouse(e);
392             }
393             else {
394                 // Choice is up - unfurl it
395                 grabInput();
396                 unfurledChoice.toFront();
397                 firstPress = true;
398                 wasDragged = false;
399                 unfurled = true;
400             }
401         }
402     }
403 
404     /*
405      * helper method for mouseReleased routine
406      */
hidePopdownMenu()407     void hidePopdownMenu(){
408         ungrabInput();
409         unfurledChoice.setVisible(false);
410         unfurled = false;
411     }
412 
mouseReleased(MouseEvent e)413     public void mouseReleased(MouseEvent e) {
414         if (unfurled) {
415             if (mouseInSB) {
416                 unfurledChoice.trackMouse(e);
417             }
418             else {
419                 // We pressed and dragged onto the Choice, or, this is the
420                 // second release after clicking to make the Choice "stick"
421                 // unfurled.
422                 // This release should ungrab/furl, and set the new item if
423                 // release was over the unfurled Choice.
424 
425                 // Fix for 6239944 : Choice shouldn't close its
426                 // pop-down menu if user presses Mouse on Choice's Scrollbar
427                 // some additional cases like releasing mouse outside
428                 // of Choice are considered too
429                 boolean isMouseEventInside = unfurledChoice.isMouseEventInside( e );
430                 boolean isMouseInListArea = unfurledChoice.isMouseInListArea( e );
431 
432                 // Fixed 6318746: REG: File Selection is failing
433                 // We shouldn't restore the selected item
434                 // if the mouse was dragged outside the drop-down choice area
435                 if (!helper.isEmpty() && !isMouseInListArea && dragging) {
436                     // Set the selected item back how it was.
437                     ((Choice)target).select(dragStartIdx);
438                 }
439 
440                 // Choice must be closed if user releases mouse on
441                 // pop-down menu on the second click
442                 if ( !firstPress && isMouseInListArea) {
443                     hidePopdownMenu();
444                 }
445                 // Choice must be closed if user releases mouse
446                 // outside of Choice's pop-down menu  on the second click
447                 if ( !firstPress && !isMouseEventInside) {
448                     hidePopdownMenu();
449                 }
450                 //if user drags Mouse on pop-down menu, Scrollbar or
451                 // outside the Choice
452                 if ( firstPress && dragging) {
453                     hidePopdownMenu();
454                 }
455                 /* this could happen when user has opened a Choice and
456                  * released mouse button. Then he drags mouse on the
457                  * Scrollbar and releases mouse again.
458                  */
459                 if ( !firstPress && !isMouseInListArea &&
460                      isMouseEventInside && dragging)
461                 {
462                     hidePopdownMenu();
463                 }
464 
465                 if (!helper.isEmpty()) {
466                     // Only update the Choice if the mouse button is released
467                     // over the list of items.
468                     if (unfurledChoice.isMouseInListArea(e)) {
469                         int newIdx = helper.getSelectedIndex();
470                         if (newIdx >= 0) {
471                             int currentItem = ((Choice)target).getSelectedIndex();
472                             // Update the selected item in the target now that
473                             // the mouse selection is complete.
474                             if (newIdx != dragStartIdx) {
475                                 ((Choice)target).select(newIdx);
476                                 // NOTE: We get a repaint when Choice.select()
477                                 // calls our peer.select().
478                             }
479                             if (wasDragged && e.getButton() != MouseEvent.BUTTON1){
480                                 ((Choice)target).select(dragStartIdx);
481                             }
482 
483                             /*fix for 6239941 : Choice triggers ItemEvent when selecting an item with right mouse button, Xtoolkit
484                             * We should generate ItemEvent if only
485                             * LeftMouseButton used */
486                             if (e.getButton() == MouseEvent.BUTTON1 &&
487                                 (!firstPress || wasDragged ) &&
488                                 (newIdx != currentItem))
489                             {
490                                 ((Choice)target).select(newIdx);
491                                 postEvent(new ItemEvent((Choice)target,
492                                                         ItemEvent.ITEM_STATE_CHANGED,
493                                                         ((Choice)target).getItem(newIdx),
494                                                         ItemEvent.SELECTED));
495                             }
496 
497                             // see 6240074 for more information
498                             if (choiceListener != null) {
499                                 choiceListener.unfurledChoiceClosing();
500                             }
501                         }
502                     }
503                 }
504                 // See 6243382 for more information
505                 unfurledChoice.trackMouse(e);
506             }
507         }
508 
509         dragging = false;
510         wasDragged = false;
511         firstPress = false;
512         dragStartIdx = -1;
513     }
514     @SuppressWarnings("deprecation")
mouseDragged(MouseEvent e)515     public void mouseDragged(MouseEvent e) {
516         /*
517          * fix for 5003166. On Motif user are unable to drag
518          * mouse inside opened Choice if he drags the mouse with
519          * different from LEFT mouse button ( e.g. RIGHT or MIDDLE).
520          * This fix make impossible to drag mouse inside opened choice
521          * with other mouse buttons rather then LEFT one.
522          */
523         if ( e.getModifiers() == MouseEvent.BUTTON1_MASK ){
524             dragging = true;
525             wasDragged = true;
526             unfurledChoice.trackMouse(e);
527         }
528     }
529 
530     // Stolen from TinyChoicePeer
531     @SuppressWarnings("deprecation")
getMinimumSize()532     public Dimension getMinimumSize() {
533         // TODO: move this impl into ListHelper?
534         FontMetrics fm = getFontMetrics(target.getFont());
535         Choice c = (Choice)target;
536         int w = 0;
537         for (int i = c.countItems() ; i-- > 0 ;) {
538             w = Math.max(fm.stringWidth(c.getItem(i)), w);
539         }
540         return new Dimension(w + TEXT_XPAD + WIDGET_OFFSET,
541                              fm.getMaxAscent() + fm.getMaxDescent() + TEXT_YPAD);
542     }
543 
544     /*
545      * Layout the...
546      */
layout()547     public void layout() {
548         /*
549           Dimension size = target.getSize();
550           Font f = target.getFont();
551           FontMetrics fm = target.getFontMetrics(f);
552           String text = ((Choice)target).getLabel();
553 
554           textRect.height = fm.getHeight();
555 
556           checkBoxSize = getChoiceSize(fm);
557 
558           // Note - Motif appears to use an left inset that is slightly
559           // scaled to the checkbox/font size.
560           cbX = borderInsets.left + checkBoxInsetFromText;
561           cbY = size.height / 2 - checkBoxSize / 2;
562           int minTextX = borderInsets.left + 2 * checkBoxInsetFromText + checkBoxSize;
563           // FIXME: will need to account for alignment?
564           // FIXME: call layout() on alignment changes
565           //textRect.width = fm.stringWidth(text);
566           textRect.width = fm.stringWidth(text == null ? "" : text);
567           textRect.x = Math.max(minTextX, size.width / 2 - textRect.width / 2);
568           textRect.y = size.height / 2 - textRect.height / 2 + borderInsets.top;
569 
570           focusRect.x = focusInsets.left;
571           focusRect.y = focusInsets.top;
572           focusRect.width = size.width-(focusInsets.left+focusInsets.right)-1;
573           focusRect.height = size.height-(focusInsets.top+focusInsets.bottom)-1;
574 
575           myCheckMark = AffineTransform.getScaleInstance((double)target.getFont().getSize() / MASTER_SIZE, (double)target.getFont().getSize() / MASTER_SIZE).createTransformedShape(MASTER_CHECKMARK);
576         */
577 
578     }
579 
580     /**
581      * Paint the choice
582      */
583     @Override
paintPeer(final Graphics g)584     void paintPeer(final Graphics g) {
585         flush();
586         Dimension size = getPeerSize();
587         // TODO: when mouse is down over button, widget should be drawn depressed
588         g.setColor(getPeerBackground());
589         g.fillRect(0, 0, width, height);
590 
591         drawMotif3DRect(g, 1, 1, width-2, height-2, false);
592         drawMotif3DRect(g, width - WIDGET_OFFSET, (height / 2) - 3, 12, 6, false);
593 
594         if (!helper.isEmpty() && helper.getSelectedIndex() != -1) {
595             g.setFont(getPeerFont());
596             FontMetrics fm = g.getFontMetrics();
597             String lbl = helper.getItem(helper.getSelectedIndex());
598             if (lbl != null && drawSelectedItem) {
599                 g.setClip(1, 1, width - WIDGET_OFFSET - 2, height);
600                 if (isEnabled()) {
601                     g.setColor(getPeerForeground());
602                     g.drawString(lbl, 5, (height + fm.getMaxAscent()-fm.getMaxDescent())/2);
603                 }
604                 else {
605                     g.setColor(getPeerBackground().brighter());
606                     g.drawString(lbl, 5, (height + fm.getMaxAscent()-fm.getMaxDescent())/2);
607                     g.setColor(getPeerBackground().darker());
608                     g.drawString(lbl, 4, ((height + fm.getMaxAscent()-fm.getMaxDescent())/2)-1);
609                 }
610                 g.setClip(0, 0, width, height);
611             }
612         }
613         if (hasFocus()) {
614             paintFocus(g,focusInsets.left,focusInsets.top,size.width-(focusInsets.left+focusInsets.right)-1,size.height-(focusInsets.top+focusInsets.bottom)-1);
615         }
616         if (unfurled) {
617             unfurledChoice.repaint();
618         }
619         flush();
620     }
621 
paintFocus(Graphics g, int x, int y, int w, int h)622     protected void paintFocus(Graphics g,
623                               int x, int y, int w, int h) {
624         g.setColor(focusColor);
625         g.drawRect(x,y,w,h);
626     }
627 
628 
629 
630     /*
631      * ChoicePeer methods stolen from TinyChoicePeer
632      */
633 
select(int index)634     public void select(int index) {
635         helper.select(index);
636         helper.setFocusedIndex(index);
637         repaint();
638     }
639 
add(String item, int index)640     public void add(String item, int index) {
641         helper.add(item, index);
642         repaint();
643     }
644 
remove(int index)645     public void remove(int index) {
646         boolean selected = (index == helper.getSelectedIndex());
647         boolean visibled = (index >= helper.firstDisplayedIndex() && index <= helper.lastDisplayedIndex());
648         helper.remove(index);
649         if (selected) {
650             if (helper.isEmpty()) {
651                 helper.select(-1);
652             }
653             else {
654                 helper.select(0);
655             }
656         }
657         /*
658          * Fix for 6248016
659          * After removing the item of the choice we need to reshape unfurled choice
660          * in order to keep actual bounds of the choice
661          */
662 
663         /*
664          * condition added only for performance
665          */
666         if (!unfurled) {
667             // Fix 6292186: PIT: Choice is not refreshed properly when the last item gets removed, XToolkit
668             // We should take into account that there is no 'select' invoking (hence 'repaint')
669             // if the choice is empty (see Choice.java method removeNoInvalidate())
670             // The condition isn't 'visibled' since it would be cause of the twice repainting
671             if (helper.isEmpty()) {
672                 repaint();
673             }
674             return;
675         }
676 
677         /*
678          * condition added only for performance
679          * the count of the visible items changed
680          */
681         if (visibled){
682             Rectangle r = unfurledChoice.placeOnScreen();
683             unfurledChoice.reshape(r.x, r.y, r.width, r.height);
684             return;
685         }
686 
687         /*
688          * condition added only for performance
689          * the structure of visible items changed
690          * if removable item is non visible and non selected then there is no repaint
691          */
692         if (visibled || selected){
693             repaint();
694         }
695     }
696 
removeAll()697     public void removeAll() {
698         helper.removeAll();
699         helper.select(-1);
700         /*
701          * Fix for 6248016
702          * After removing the item of the choice we need to reshape unfurled choice
703          * in order to keep actual bounds of the choice
704          */
705         Rectangle r = unfurledChoice.placeOnScreen();
706         unfurledChoice.reshape(r.x, r.y, r.width, r.height);
707         repaint();
708     }
709 
setFont(Font font)710     public void setFont(Font font) {
711         super.setFont(font);
712         helper.setFont(this.font);
713     }
714 
setForeground(Color c)715     public void setForeground(Color c) {
716         super.setForeground(c);
717         helper.updateColors(getGUIcolors());
718     }
719 
setBackground(Color c)720     public void setBackground(Color c) {
721         super.setBackground(c);
722         unfurledChoice.setBackground(c);
723         helper.updateColors(getGUIcolors());
724         updateMotifColors(c);
725     }
726 
setDrawSelectedItem(boolean value)727     public void setDrawSelectedItem(boolean value) {
728         drawSelectedItem = value;
729     }
730 
setAlignUnder(Component comp)731     public void setAlignUnder(Component comp) {
732         alignUnder = comp;
733     }
734 
735     // see 6240074 for more information
addXChoicePeerListener(XChoicePeerListener l)736     public void addXChoicePeerListener(XChoicePeerListener l){
737         choiceListener = l;
738     }
739 
740     // see 6240074 for more information
removeXChoicePeerListener()741     public void removeXChoicePeerListener(){
742         choiceListener = null;
743     }
744 
isUnfurled()745     public boolean isUnfurled(){
746         return unfurled;
747     }
748 
749     /* fix for 6261352. We should detect if current parent Window (containing a Choice) become iconified and hide pop-down menu with grab release.
750      * In this case we should hide pop-down menu.
751      */
752     //calls from XWindowPeer. Could accept X-styled state events
stateChangedICCCM(int oldState, int newState)753     public void stateChangedICCCM(int oldState, int newState) {
754         if (unfurled && oldState != newState){
755                 hidePopdownMenu();
756         }
757     }
758 
759     //calls from XFramePeer. Could accept Frame's states.
stateChangedJava(int oldState, int newState)760     public void stateChangedJava(int oldState, int newState) {
761         if (unfurled && oldState != newState){
762             hidePopdownMenu();
763         }
764     }
765 
766     @Override
initGraphicsConfiguration()767     protected void initGraphicsConfiguration() {
768         super.initGraphicsConfiguration();
769         // The popup have the same graphic config, so update it at the same time
770         if (unfurledChoice != null) {
771             unfurledChoice.initGraphicsConfiguration();
772             unfurledChoice.doValidateSurface();
773         }
774     }
775 
776     /**************************************************************************/
777     /* Common functionality between List & Choice
778        /**************************************************************************/
779 
780     /**
781      * Inner class for the unfurled Choice list
782      * Much, much more docs
783      */
784     final class UnfurledChoice extends XWindow /*implements XScrollbarClient*/ {
785 
786         // First try - use Choice as the target
787 
UnfurledChoice(Component target)788         public UnfurledChoice(Component target) {
789             super(target);
790         }
791 
792         // Override so we can do our own create()
preInit(XCreateWindowParams params)793         public void preInit(XCreateWindowParams params) {
794             // A parent of this window is the target, at this point: wrong.
795             // Remove parent window; in the following preInit() call we'll calculate as a default
796             // a correct root window which is the proper parent for override redirect.
797             params.delete(PARENT_WINDOW);
798             super.preInit(params);
799             // Reset bounds(we'll set them later), set overrideRedirect
800             params.remove(BOUNDS);
801             params.add(OVERRIDE_REDIRECT, Boolean.TRUE);
802         }
803 
804         // Generally, bounds should be:
805         //  x = target.x
806         //  y = target.y + target.height
807         //  w = Max(target.width, getLongestItemWidth) + possible vertScrollbar
808         //  h = Min(MAX_UNFURLED_ITEMS, target.getItemCount()) * itemHeight
placeOnScreen()809         Rectangle placeOnScreen() {
810             int numItemsDisplayed;
811             // Motif paints an empty Choice the same size as a single item
812             if (helper.isEmpty()) {
813                 numItemsDisplayed = 1;
814             }
815             else {
816                 int numItems = helper.getItemCount();
817                 numItemsDisplayed = Math.min(MAX_UNFURLED_ITEMS, numItems);
818             }
819             Point global = XChoicePeer.this.toGlobal(0,0);
820             Rectangle screenBounds = graphicsConfig.getBounds();
821 
822             if (alignUnder != null) {
823                 Rectangle choiceRec = XChoicePeer.this.getBounds();
824                 choiceRec.setLocation(0, 0);
825                 choiceRec = XChoicePeer.this.toGlobal(choiceRec);
826                 Rectangle alignUnderRec = new Rectangle(alignUnder.getLocationOnScreen(), alignUnder.getSize()); // TODO: Security?
827                 Rectangle result = choiceRec.union(alignUnderRec);
828                 // we've got the left and width, calculate top and height
829                 width = result.width;
830                 x = result.x;
831                 y = result.y + result.height;
832                 height = 2*BORDER_WIDTH +
833                     numItemsDisplayed*(helper.getItemHeight()+2*ITEM_MARGIN);
834             } else {
835                 x = global.x;
836                 y = global.y + XChoicePeer.this.height;
837                 width = Math.max(XChoicePeer.this.width,
838                                  helper.getMaxItemWidth() + 2 * (BORDER_WIDTH + ITEM_MARGIN + TEXT_SPACE) + (helper.isVSBVisible() ? SCROLLBAR_WIDTH : 0));
839                 height = 2*BORDER_WIDTH +
840                     numItemsDisplayed*(helper.getItemHeight()+2*ITEM_MARGIN);
841             }
842             // Don't run off the edge of the screenBounds
843             if (x < screenBounds.x) {
844                 x = screenBounds.x;
845             }
846             else if (x + width > screenBounds.x + screenBounds.width) {
847                 x = screenBounds.x + screenBounds.width - width;
848             }
849 
850             if (y + height > screenBounds.y + screenBounds.height) {
851                 y = global.y - height;
852             }
853             if (y < screenBounds.y) {
854                 y = screenBounds.y;
855             }
856             return new Rectangle(x, y, width, height);
857         }
858 
toFront()859         public void toFront() {
860             // see 6240074 for more information
861             if (choiceListener != null)
862                 choiceListener.unfurledChoiceOpening(helper);
863 
864             Rectangle r = placeOnScreen();
865             reshape(r.x, r.y, r.width, r.height);
866             super.toFront();
867             setVisible(true);
868         }
869 
870         /*
871          * Track a MouseEvent (either a drag or a press) and paint a new
872          * selected item, if necessary.
873          */
874         // FIXME: first unfurl after move is not at edge of the screen  onto second monitor doesn't
875         // track mouse correctly.  Problem is w/ UnfurledChoice coords
trackMouse(MouseEvent e)876         public void trackMouse(MouseEvent e) {
877             // Event coords are relative to the button, so translate a bit
878             Point local = toLocalCoords(e);
879 
880             // If x,y is over unfurled Choice,
881             // highlight item under cursor
882 
883             switch (e.getID()) {
884               case MouseEvent.MOUSE_PRESSED:
885                   // FIXME: If the Choice is unfurled and the mouse is pressed
886                   // outside of the Choice, the mouse should ungrab on the
887                   // the press, not the release
888                   if (helper.isInVertSB(getBounds(), local.x, local.y)) {
889                       mouseInSB = true;
890                       helper.handleVSBEvent(e, getBounds(), local.x, local.y);
891                   }
892                   else {
893                       trackSelection(local.x, local.y);
894                   }
895                   break;
896               case MouseEvent.MOUSE_RELEASED:
897                   if (mouseInSB) {
898                       mouseInSB = false;
899                       helper.handleVSBEvent(e, getBounds(), local.x, local.y);
900                   }else{
901                       // See 6243382 for more information
902                       helper.trackMouseReleasedScroll();
903                   }
904                   /*
905                     else {
906                     trackSelection(local.x, local.y);
907                     }
908                   */
909                   break;
910               case MouseEvent.MOUSE_DRAGGED:
911                   if (mouseInSB) {
912                       helper.handleVSBEvent(e, getBounds(), local.x, local.y);
913                   }
914                   else {
915                       // See 6243382 for more information
916                       helper.trackMouseDraggedScroll(local.x, local.y, width, height);
917                       trackSelection(local.x, local.y);
918                   }
919                   break;
920             }
921         }
922 
trackSelection(int transX, int transY)923         private void trackSelection(int transX, int transY) {
924             if (!helper.isEmpty()) {
925                 if (transX > 0 && transX < width &&
926                     transY > 0 && transY < height) {
927                     int newIdx = helper.y2index(transY);
928                     if (log.isLoggable(PlatformLogger.Level.FINE)) {
929                         log.fine("transX=" + transX + ", transY=" + transY
930                                  + ",width=" + width + ", height=" + height
931                                  + ", newIdx=" + newIdx + " on " + target);
932                     }
933                     if ((newIdx >=0) && (newIdx < helper.getItemCount())
934                         && (newIdx != helper.getSelectedIndex()))
935                     {
936                         helper.select(newIdx);
937                         unfurledChoice.repaint();
938                     }
939                 }
940             }
941             // FIXME: If dragged off top or bottom, scroll if there's a vsb
942             // (ICK - we'll need a timer or our own event or something)
943         }
944 
945         /*
946          * fillRect with current Background color on the whole dropdown list.
947          */
paintBackground()948         public void paintBackground() {
949             final Graphics g = getGraphics();
950             if (g != null) {
951                 try {
952                     g.setColor(getPeerBackground());
953                     g.fillRect(0, 0, width, height);
954                 } finally {
955                     g.dispose();
956                 }
957             }
958         }
959         /*
960          * 6405689. In some cases we should erase background to eliminate painting
961          * artefacts.
962          */
963         @Override
repaint()964         public void repaint() {
965             if (!isVisible()) {
966                 return;
967             }
968             if (helper.checkVsbVisibilityChangedAndReset()){
969                 paintBackground();
970             }
971             super.repaint();
972         }
973         @Override
paintPeer(Graphics g)974         public void paintPeer(Graphics g) {
975             //System.out.println("UC.paint()");
976             Choice choice = (Choice)target;
977             Color[] colors = XChoicePeer.this.getGUIcolors();
978             draw3DRect(g, getSystemColors(), 0, 0, width - 1, height - 1, true);
979             draw3DRect(g, getSystemColors(), 1, 1, width - 3, height - 3, true);
980 
981             helper.paintAllItems(g,
982                                  colors,
983                                  getBounds());
984         }
985 
setVisible(boolean vis)986         public void setVisible(boolean vis) {
987             xSetVisible(vis);
988 
989             if (!vis && alignUnder != null) {
990                 alignUnder.requestFocusInWindow();
991             }
992         }
993 
994         /**
995          * Return a MouseEvent's Point in coordinates relative to the
996          * UnfurledChoice.
997          */
toLocalCoords(MouseEvent e)998         private Point toLocalCoords(MouseEvent e) {
999             // Event coords are relative to the button, so translate a bit
1000             Point global = e.getLocationOnScreen();
1001 
1002             global.x -= x;
1003             global.y -= y;
1004             return global;
1005         }
1006 
1007         /* Returns true if the MouseEvent coords (which are based on the Choice)
1008          * are inside of the UnfurledChoice.
1009          */
isMouseEventInside(MouseEvent e)1010         private boolean isMouseEventInside(MouseEvent e) {
1011             Point local = toLocalCoords(e);
1012             if (local.x > 0 && local.x < width &&
1013                 local.y > 0 && local.y < height) {
1014                 return true;
1015             }
1016             return false;
1017         }
1018 
1019         /**
1020          * Tests if the mouse cursor is in the Unfurled Choice, yet not
1021          * in the vertical scrollbar
1022          */
isMouseInListArea(MouseEvent e)1023         private boolean isMouseInListArea(MouseEvent e) {
1024             if (isMouseEventInside(e)) {
1025                 Point local = toLocalCoords(e);
1026                 Rectangle bounds = getBounds();
1027                 if (!helper.isInVertSB(bounds, local.x, local.y)) {
1028                     return true;
1029                 }
1030             }
1031             return false;
1032         }
1033 
1034         /*
1035          * Overridden from XWindow() because we don't want to send
1036          * ComponentEvents
1037          */
handleConfigureNotifyEvent(XEvent xev)1038         public void handleConfigureNotifyEvent(XEvent xev) {}
handleMapNotifyEvent(XEvent xev)1039         public void handleMapNotifyEvent(XEvent xev) {}
handleUnmapNotifyEvent(XEvent xev)1040         public void handleUnmapNotifyEvent(XEvent xev) {}
1041     } //UnfurledChoice
1042 
dispose()1043     public void dispose() {
1044         if (unfurledChoice != null) {
1045             unfurledChoice.destroy();
1046         }
1047         super.dispose();
1048     }
1049 
1050     /*
1051      * fix for 6239938 : Choice drop-down does not disappear when it loses
1052      * focus, on XToolkit
1053      * We are able to handle all _Key_ events received by Choice when
1054      * it is in opened state without sending it to EventQueue.
1055      * If Choice is in closed state we should behave like before: send
1056      * all events to EventQueue.
1057      * To be compatible with Motif we should handle all KeyEvents in
1058      * Choice if it is opened. KeyEvents should be sent into Java if Choice is not opened.
1059      */
prePostEvent(final AWTEvent e)1060     boolean prePostEvent(final AWTEvent e) {
1061         if (unfurled){
1062             // fix for 6253211: PIT: MouseWheel events not triggered for Choice drop down in XAWT
1063             if (e instanceof MouseWheelEvent){
1064                 return super.prePostEvent(e);
1065             }
1066             //fix 6252982: PIT: Keyboard FocusTraversal not working when choice's drop-down is visible, on XToolkit
1067             if (e instanceof KeyEvent){
1068                 // notify XWindow that this event had been already handled and no need to post it again
1069                 InvocationEvent ev = new InvocationEvent(target, new Runnable() {
1070                     public void run() {
1071                         if(target.isFocusable() &&
1072                                 getParentTopLevel().isFocusableWindow() )
1073                         {
1074                             handleJavaKeyEvent((KeyEvent)e);
1075                         }
1076                     }
1077                 });
1078                 postEvent(ev);
1079 
1080                 return true;
1081             } else {
1082                 if (e instanceof MouseEvent){
1083                     // Fix for 6240046 : REG:Choice's Drop-down does not disappear when clicking somewhere, after popup menu is disposed
1084                     // if user presses Right Mouse Button on opened (unfurled)
1085                     // Choice then we mustn't open a popup menu. We could filter
1086                     // Mouse Events and handle them in XChoicePeer if Choice
1087                     // currently in opened state.
1088                     MouseEvent me = (MouseEvent)e;
1089                     int eventId = e.getID();
1090                     // fix 6251983: PIT: MouseDragged events not triggered
1091                     // fix 6251988: PIT: Choice consumes MouseReleased, MouseClicked events when clicking it with left button,
1092                     if ((unfurledChoice.isMouseEventInside(me) ||
1093                          (!firstPress && eventId == MouseEvent.MOUSE_DRAGGED)))
1094                     {
1095                         return handleMouseEventByChoice(me);
1096                     }
1097                     // MouseMoved events should be fired in Choice's comp if it's not opened
1098                     // Shouldn't generate Moved Events. CR : 6251995
1099                     if (eventId == MouseEvent.MOUSE_MOVED){
1100                         return handleMouseEventByChoice(me);
1101                     }
1102                     //fix for 6272965: PIT: Choice triggers MousePressed when pressing mouse outside comp while drop-down is active, XTkt
1103                     if (  !firstPress && !( isMouseEventInChoice(me) ||
1104                              unfurledChoice.isMouseEventInside(me)) &&
1105                              ( eventId == MouseEvent.MOUSE_PRESSED ||
1106                                eventId == MouseEvent.MOUSE_RELEASED ||
1107                                eventId == MouseEvent.MOUSE_CLICKED )
1108                           )
1109                     {
1110                         return handleMouseEventByChoice(me);
1111                     }
1112                 }
1113             }//else KeyEvent
1114         }//if unfurled
1115         return super.prePostEvent(e);
1116     }
1117 
1118     //convenient method
1119     //do not generate this kind of Events
handleMouseEventByChoice(final MouseEvent me)1120     public boolean handleMouseEventByChoice(final MouseEvent me){
1121         InvocationEvent ev = new InvocationEvent(target, new Runnable() {
1122             public void run() {
1123                 handleJavaMouseEvent(me);
1124             }
1125         });
1126         postEvent(ev);
1127 
1128         return true;
1129     }
1130 
1131     /* Returns true if the MouseEvent coords
1132      * are inside of the Choice itself (it doesnt's depends on
1133      * if this choice opened or not).
1134      */
isMouseEventInChoice(MouseEvent e)1135     private boolean isMouseEventInChoice(MouseEvent e) {
1136         int x = e.getX();
1137         int y = e.getY();
1138         Rectangle choiceRect = getBounds();
1139 
1140         if (x < 0 || x > choiceRect.width ||
1141             y < 0 || y > choiceRect.height)
1142         {
1143             return false;
1144         }
1145         return true;
1146     }
1147 }
1148