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