       1 /*
       2  * Copyright (c) 2011, 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
      27 package sun.lwawt;
      28
      29 import java.awt.*;
      30 import java.awt.event.ItemEvent;
      31 import java.awt.event.ItemListener;
      32 import java.awt.peer.ChoicePeer;
      33
      34 import javax.accessibility.Accessible;
      35 import javax.swing.*;
      36
      37 /**
      38  * Lightweight implementation of {@link ChoicePeer}. Delegates most of the work
      39  * to the {@link JComboBox}.
      40  */
      41 final class LWChoicePeer extends LWComponentPeer<Choice, JComboBox<String>>
      42         implements ChoicePeer, ItemListener {
      43
      44     /**
      45      * According to Choice specification item events are sent in response to
      46      * user input, but not in response to calls to select(). But JComboBox are
      47      * sent item events in both cases. Should be used under delegateLock.
      48      */
      49     private boolean skipPostMessage;
      50
      51     LWChoicePeer(final Choice target,
      52                  final PlatformComponent platformComponent) {
      53         super(target, platformComponent);
      54     }
      55
      56     @Override
      57     JComboBox<String> createDelegate() {
      58         return new JComboBoxDelegate();
      59     }
      60
      61     @Override
      62     void initializeImpl() {
      63         super.initializeImpl();
      64         final Choice choice = getTarget();
      65         final JComboBox<String> combo = getDelegate();
      66         synchronized (getDelegateLock()) {
      67             final int count = choice.getItemCount();
      68             for (int i = 0; i < count; ++i) {
      69                 combo.addItem(choice.getItem(i));
      70             }
      71             select(choice.getSelectedIndex());
      72
      73             // NOTE: the listener must be added at the very end, otherwise it
      74             // fires events upon initialization of the combo box.
      75             combo.addItemListener(this);
      76         }
      77     }
      78
      79     @Override
      80     public void itemStateChanged(final ItemEvent e) {
      81         // AWT Choice sends SELECTED event only whereas JComboBox
      82         // sends both SELECTED and DESELECTED.
      83         if (e.getStateChange() == ItemEvent.SELECTED) {
      84             synchronized (getDelegateLock()) {
      85                 if (skipPostMessage) {
      86                     return;
      87                 }
      88                 getTarget().select(getDelegate().getSelectedIndex());
      89             }
      90             postEvent(new ItemEvent(getTarget(), ItemEvent.ITEM_STATE_CHANGED,
      91                                     e.getItem(), ItemEvent.SELECTED));
      92         }
      93     }
      94
      95     @Override
      96     public void add(final String item, final int index) {
      97         synchronized (getDelegateLock()) {
      98             getDelegate().insertItemAt(item, index);
      99         }
     100     }
     101
     102     @Override
     103     public void remove(final int index) {
     104         synchronized (getDelegateLock()) {
     105             // We shouldn't post event, if selected item was removed.
     106             skipPostMessage = true;
     107             getDelegate().removeItemAt(index);
     108             skipPostMessage = false;
     109         }
     110     }
     111
     112     @Override
     113     public void removeAll() {
     114         synchronized (getDelegateLock()) {
     115             getDelegate().removeAllItems();
     116         }
     117     }
     118
     119     @Override
     120     public void select(final int index) {
     121         synchronized (getDelegateLock()) {
     122             if (index != getDelegate().getSelectedIndex()) {
     123                 skipPostMessage = true;
     124                 getDelegate().setSelectedIndex(index);
     125                 skipPostMessage = false;
     126             }
     127         }
     128     }
     129
     130     @Override
     131     public boolean isFocusable() {
     132         return true;
     133     }
     134
     135     @SuppressWarnings("serial")// Safe: outer class is non-serializable.
     136     private final class JComboBoxDelegate extends JComboBox<String> {
     137
     138         // Empty non private constructor was added because access to this
     139         // class shouldn't be emulated by a synthetic accessor method.
     140         JComboBoxDelegate() {
     141             super();
     142         }
     143
     144         @Override
     145         public boolean hasFocus() {
     146             return getTarget().hasFocus();
     147         }
     148
     149         //Needed for proper popup menu location
     150         @Override
     151         public Point getLocationOnScreen() {
     152             return LWChoicePeer.this.getLocationOnScreen();
     153         }
     154
     155         /**
     156          * We should post ITEM_STATE_CHANGED event when the same element is
     157          * reselected.
     158          */
     159         @Override
     160         public void setSelectedItem(final String anObject) {
     161             final String oldSelection = selectedItemReminder;
     162             if (oldSelection != null && oldSelection.equals(anObject)) {
     163                 selectedItemChanged();
     164             }
     165             super.setSelectedItem(anObject);
     166         }
     167
     168         @Override
     169         public void firePopupMenuWillBecomeVisible() {
     170             super.firePopupMenuWillBecomeVisible();
     171             SwingUtilities.invokeLater(() -> {
     172                 JPopupMenu popupMenu = getPopupMenu();
     173                 // Need to override the invoker for proper grab handling
     174                 if (popupMenu != null
     175                         && popupMenu.isShowing()
     176                         && popupMenu.getInvoker() != getTarget()) {
     177                     // The popup is now visible with correct location
     178                     // Save it and restore after toggling visibility and changing invoker
     179                     Point loc = popupMenu.getLocationOnScreen();
     180                     SwingUtilities.convertPointFromScreen(loc, this);
     181                     popupMenu.setVisible(false);
     182                     popupMenu.show(getTarget(), loc.x, loc.y);
     183                 }
     184             });
     185         }
     186
     187         private JPopupMenu getPopupMenu() {
     188             for (int i = 0; i < getAccessibleContext().getAccessibleChildrenCount(); i++) {
     189                 Accessible child = getAccessibleContext().getAccessibleChild(i);
     190                 if (child instanceof JPopupMenu) {
     191                     return  (JPopupMenu) child;
     192                 }
     193             }
     194             return null;
     195         }
     196     }
     197 }