1 /*
   2  * Copyright (c) 1997, 2014, 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 com.sun.java.swing.plaf.windows;
  27 
  28 import java.awt.Component;
  29 import java.awt.Graphics;
  30 import java.awt.Insets;
  31 import java.awt.KeyEventPostProcessor;
  32 import java.awt.KeyboardFocusManager;
  33 import java.awt.Window;
  34 import java.awt.event.KeyEvent;
  35 import javax.swing.*;
  36 import javax.swing.event.*;
  37 import javax.swing.plaf.*;
  38 import javax.swing.plaf.basic.*;
  39 
  40 import sun.swing.StringUIClientPropertyKey;
  41 
  42 import com.sun.java.swing.plaf.windows.TMSchema.Part;
  43 import com.sun.java.swing.plaf.windows.TMSchema.State;
  44 import com.sun.java.swing.plaf.windows.XPStyle.Skin;
  45 import static sun.swing.SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET;
  46 
  47 /**
  48  * Windows rendition of the component.
  49  * <p>
  50  * <strong>Warning:</strong>
  51  * Serialized objects of this class will not be compatible with
  52  * future Swing releases.  The current serialization support is appropriate
  53  * for short term storage or RMI between applications running the same
  54  * version of Swing.  A future release of Swing will provide support for
  55  * long term persistence.
  56  *
  57  * @author Igor Kushnirskiy
  58  */
  59 public class WindowsPopupMenuUI extends BasicPopupMenuUI {
  60 
  61     static MnemonicListener mnemonicListener = null;
  62     static final Object GUTTER_OFFSET_KEY =
  63         new StringUIClientPropertyKey("GUTTER_OFFSET_KEY");
  64 
  65     public static ComponentUI createUI(JComponent c) {
  66         return new WindowsPopupMenuUI();
  67     }
  68 
  69     public void installListeners() {
  70         super.installListeners();
  71         if (! UIManager.getBoolean("Button.showMnemonics") &&
  72             mnemonicListener == null) {
  73 
  74             mnemonicListener = new MnemonicListener();
  75             MenuSelectionManager.defaultManager().
  76                 addChangeListener(mnemonicListener);
  77         }
  78     }
  79 
  80     /**
  81      * Returns the <code>Popup</code> that will be responsible for
  82      * displaying the <code>JPopupMenu</code>.
  83      *
  84      * @param popupMenu JPopupMenu requesting Popup
  85      * @param x     Screen x location Popup is to be shown at
  86      * @param y     Screen y location Popup is to be shown at.
  87      * @return Popup that will show the JPopupMenu
  88      * @since 1.4
  89      */
  90     public Popup getPopup(JPopupMenu popupMenu, int x, int y) {
  91         PopupFactory popupFactory = PopupFactory.getSharedInstance();
  92         return popupFactory.getPopup(popupMenu.getInvoker(), popupMenu, x, y);
  93     }
  94 
  95     static class MnemonicListener implements ChangeListener {
  96         JRootPane repaintRoot = null;
  97 
  98         public void stateChanged(ChangeEvent ev) {
  99             MenuSelectionManager msm = (MenuSelectionManager)ev.getSource();
 100             MenuElement[] path = msm.getSelectedPath();
 101             if (path.length == 0) {
 102                 if(!WindowsLookAndFeel.isMnemonicHidden()) {
 103                     // menu was canceled -- hide mnemonics
 104                     WindowsLookAndFeel.setMnemonicHidden(true);
 105                     if (repaintRoot != null) {
 106                         Window win =
 107                             SwingUtilities.getWindowAncestor(repaintRoot);
 108                         WindowsGraphicsUtils.repaintMnemonicsInWindow(win);
 109                     }
 110                 }
 111             } else {
 112                 Component c = (Component)path[0];
 113                 if (c instanceof JPopupMenu) c = ((JPopupMenu)c).getInvoker();
 114                 repaintRoot = SwingUtilities.getRootPane(c);
 115             }
 116         }
 117     }
 118 
 119     /**
 120      * Returns offset for the text.
 121      * BasicMenuItemUI sets max text offset on the JPopupMenuUI.
 122      * @param c PopupMenu to return text offset for.
 123      * @return text offset for the component
 124      */
 125     static int getTextOffset(JComponent c) {
 126         int rv = -1;
 127         Object maxTextOffset =
 128             c.getClientProperty(BASICMENUITEMUI_MAX_TEXT_OFFSET);
 129         if (maxTextOffset instanceof Integer) {
 130             /*
 131              * this is in JMenuItem coordinates.
 132              * Let's assume all the JMenuItem have the same offset along X.
 133              */
 134             rv = (Integer) maxTextOffset;
 135             int menuItemOffset = 0;
 136             Component component = c.getComponent(0);
 137             if (component != null) {
 138                 menuItemOffset = component.getX();
 139             }
 140             rv += menuItemOffset;
 141         }
 142         return rv;
 143     }
 144 
 145     /**
 146      * Returns span before gutter.
 147      * used only on Vista.
 148      * @return span before gutter
 149      */
 150     static int getSpanBeforeGutter() {
 151         return 3;
 152     }
 153 
 154     /**
 155      * Returns span after gutter.
 156      * used only on Vista.
 157      * @return span after gutter
 158      */
 159     static int getSpanAfterGutter() {
 160         return 3;
 161     }
 162 
 163     /**
 164      * Returns gutter width.
 165      * used only on Vista.
 166      * @return width of the gutter
 167      */
 168     static int getGutterWidth() {
 169         int rv = 2;
 170         XPStyle xp = XPStyle.getXP();
 171         if (xp != null) {
 172             Skin skin = xp.getSkin(null, Part.MP_POPUPGUTTER);
 173             rv = skin.getWidth();
 174         }
 175         return rv;
 176     }
 177 
 178     /**
 179      * Checks if PopupMenu is leftToRight
 180      * The orientation is derived from the children of the component.
 181      * It is leftToRight if all the children are leftToRight
 182      *
 183      * @param c component to return orientation for
 184      * @return true if all the children are leftToRight
 185      */
 186     private static boolean isLeftToRight(JComponent c) {
 187         boolean leftToRight = true;
 188         for (int i = c.getComponentCount() - 1; i >=0 && leftToRight; i-- ) {
 189             leftToRight =
 190                 c.getComponent(i).getComponentOrientation().isLeftToRight();
 191         }
 192         return leftToRight;
 193     }
 194 
 195     @Override
 196     public void paint(Graphics g, JComponent c) {
 197         XPStyle xp = XPStyle.getXP();
 198         if (WindowsMenuItemUI.isVistaPainting(xp)) {
 199             Skin skin = xp.getSkin(c, Part.MP_POPUPBACKGROUND);
 200             skin.paintSkin(g, 0, 0, c.getWidth(),c.getHeight(), State.NORMAL);
 201             int textOffset = getTextOffset(c);
 202             if (textOffset >= 0
 203                     /* paint gutter only for leftToRight case */
 204                     && isLeftToRight(c)) {
 205                 skin = xp.getSkin(c, Part.MP_POPUPGUTTER);
 206                 int gutterWidth = getGutterWidth();
 207                 int gutterOffset =
 208                     textOffset - getSpanAfterGutter() - gutterWidth;
 209                 c.putClientProperty(GUTTER_OFFSET_KEY,
 210                     Integer.valueOf(gutterOffset));
 211                 Insets insets = c.getInsets();
 212                 skin.paintSkin(g, gutterOffset, insets.top,
 213                     gutterWidth, c.getHeight() - insets.bottom - insets.top,
 214                     State.NORMAL);
 215             } else {
 216                 if (c.getClientProperty(GUTTER_OFFSET_KEY) != null) {
 217                     c.putClientProperty(GUTTER_OFFSET_KEY, null);
 218                 }
 219             }
 220         } else {
 221             super.paint(g, c);
 222         }
 223     }
 224 }