1/*
2 * Copyright (c) 1999, 2016, 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
26package javax.swing.plaf.basic;
27
28import java.awt.event.ActionEvent;
29import java.awt.KeyboardFocusManager;
30import java.awt.Component;
31import java.awt.Point;
32import java.awt.Rectangle;
33import java.beans.PropertyChangeEvent;
34import java.beans.PropertyChangeListener;
35import javax.swing.*;
36import javax.swing.plaf.*;
37import sun.swing.DefaultLookup;
38import sun.swing.UIAction;
39
40/**
41 * Basic implementation of RootPaneUI, there is one shared between all
42 * JRootPane instances.
43 *
44 * @author Scott Violet
45 * @since 1.3
46 */
47public class BasicRootPaneUI extends RootPaneUI implements
48                  PropertyChangeListener {
49    private static RootPaneUI rootPaneUI = new BasicRootPaneUI();
50
51    /**
52     * Returns a new instance of {@code BasicRootPaneUI}.
53     *
54     * @param c a component
55     * @return a new instance of {@code BasicRootPaneUI}
56     */
57    public static ComponentUI createUI(JComponent c) {
58        return rootPaneUI;
59    }
60
61    public void installUI(JComponent c) {
62        installDefaults((JRootPane)c);
63        installComponents((JRootPane)c);
64        installListeners((JRootPane)c);
65        installKeyboardActions((JRootPane)c);
66    }
67
68
69    public void uninstallUI(JComponent c) {
70        uninstallDefaults((JRootPane)c);
71        uninstallComponents((JRootPane)c);
72        uninstallListeners((JRootPane)c);
73        uninstallKeyboardActions((JRootPane)c);
74    }
75
76    /**
77     * Installs default properties.
78     *
79     * @param c an instance of {@code JRootPane}
80     */
81    protected void installDefaults(JRootPane c){
82        LookAndFeel.installProperty(c, "opaque", Boolean.FALSE);
83    }
84
85    /**
86     * Installs components.
87     *
88     * @param root an instance of {@code JRootPane}
89     */
90    protected void installComponents(JRootPane root) {
91    }
92
93    /**
94     * Registers listeners.
95     *
96     * @param root an instance of {@code JRootPane}
97     */
98    protected void installListeners(JRootPane root) {
99        root.addPropertyChangeListener(this);
100    }
101
102    /**
103     * Registers keyboard actions.
104     *
105     * @param root an instance of {@code JRootPane}
106     */
107    protected void installKeyboardActions(JRootPane root) {
108        InputMap km = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, root);
109        SwingUtilities.replaceUIInputMap(root,
110                JComponent.WHEN_IN_FOCUSED_WINDOW, km);
111        km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
112                root);
113        SwingUtilities.replaceUIInputMap(root,
114                JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);
115
116        LazyActionMap.installLazyActionMap(root, BasicRootPaneUI.class,
117                "RootPane.actionMap");
118        updateDefaultButtonBindings(root);
119    }
120
121    /**
122     * Uninstalls default properties.
123     *
124     * @param root an instance of {@code JRootPane}
125     */
126    protected void uninstallDefaults(JRootPane root) {
127    }
128
129    /**
130     * Unregisters components.
131     *
132     * @param root an instance of {@code JRootPane}
133     */
134    protected void uninstallComponents(JRootPane root) {
135    }
136
137    /**
138     * Unregisters listeners.
139     *
140     * @param root an instance of {@code JRootPane}
141     */
142    protected void uninstallListeners(JRootPane root) {
143        root.removePropertyChangeListener(this);
144    }
145
146    /**
147     * Unregisters keyboard actions.
148     *
149     * @param root an instance of {@code JRootPane}
150     */
151    protected void uninstallKeyboardActions(JRootPane root) {
152        SwingUtilities.replaceUIInputMap(root, JComponent.
153                WHEN_IN_FOCUSED_WINDOW, null);
154        SwingUtilities.replaceUIActionMap(root, null);
155    }
156
157    InputMap getInputMap(int condition, JComponent c) {
158        if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
159            return (InputMap)DefaultLookup.get(c, this,
160                                       "RootPane.ancestorInputMap");
161        }
162
163        if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
164            return createInputMap(condition, c);
165        }
166        return null;
167    }
168
169    ComponentInputMap createInputMap(int condition, JComponent c) {
170        return new RootPaneInputMap(c);
171    }
172
173    static void loadActionMap(LazyActionMap map) {
174        map.put(new Actions(Actions.PRESS));
175        map.put(new Actions(Actions.RELEASE));
176        map.put(new Actions(Actions.POST_POPUP));
177    }
178
179    /**
180     * Invoked when the default button property has changed. This reloads
181     * the bindings from the defaults table with name
182     * <code>RootPane.defaultButtonWindowKeyBindings</code>.
183     */
184    void updateDefaultButtonBindings(JRootPane root) {
185        InputMap km = SwingUtilities.getUIInputMap(root, JComponent.
186                                               WHEN_IN_FOCUSED_WINDOW);
187        while (km != null && !(km instanceof RootPaneInputMap)) {
188            km = km.getParent();
189        }
190        if (km != null) {
191            km.clear();
192            if (root.getDefaultButton() != null) {
193                Object[] bindings = (Object[])DefaultLookup.get(root, this,
194                           "RootPane.defaultButtonWindowKeyBindings");
195                if (bindings != null) {
196                    LookAndFeel.loadKeyBindings(km, bindings);
197                }
198            }
199        }
200    }
201
202    /**
203     * Invoked when a property changes on the root pane. If the event
204     * indicates the <code>defaultButton</code> has changed, this will
205     * reinstall the keyboard actions.
206     */
207    public void propertyChange(PropertyChangeEvent e) {
208        if(e.getPropertyName().equals("defaultButton")) {
209            JRootPane rootpane = (JRootPane)e.getSource();
210            updateDefaultButtonBindings(rootpane);
211            if (rootpane.getClientProperty("temporaryDefaultButton") == null) {
212                rootpane.putClientProperty("initialDefaultButton", e.getNewValue());
213            }
214        }
215    }
216
217
218    static class Actions extends UIAction {
219        public static final String PRESS = "press";
220        public static final String RELEASE = "release";
221        public static final String POST_POPUP = "postPopup";
222
223        Actions(String name) {
224            super(name);
225        }
226
227        public void actionPerformed(ActionEvent evt) {
228            JRootPane root = (JRootPane)evt.getSource();
229            JButton owner = root.getDefaultButton();
230            String key = getName();
231
232            if (key == POST_POPUP) { // Action to post popup
233                Component c = KeyboardFocusManager
234                        .getCurrentKeyboardFocusManager()
235                         .getFocusOwner();
236
237                if(c instanceof JComponent) {
238                    JComponent src = (JComponent) c;
239                    JPopupMenu jpm = src.getComponentPopupMenu();
240                    if(jpm != null) {
241                        Point pt = src.getPopupLocation(null);
242                        if(pt == null) {
243                            Rectangle vis = src.getVisibleRect();
244                            pt = new Point(vis.x+vis.width/2,
245                                           vis.y+vis.height/2);
246                        }
247                        jpm.show(c, pt.x, pt.y);
248                    }
249                }
250            }
251            else if (owner != null
252                     && SwingUtilities.getRootPane(owner) == root) {
253                if (key == PRESS) {
254                    owner.doClick(20);
255                }
256            }
257        }
258
259        @Override
260        public boolean accept(Object sender) {
261            String key = getName();
262            if(key == POST_POPUP) {
263                MenuElement[] elems = MenuSelectionManager
264                        .defaultManager()
265                        .getSelectedPath();
266                if(elems != null && elems.length != 0) {
267                    return false;
268                    // We shall not interfere with already opened menu
269                }
270
271                Component c = KeyboardFocusManager
272                       .getCurrentKeyboardFocusManager()
273                        .getFocusOwner();
274                if(c instanceof JComponent) {
275                    JComponent src = (JComponent) c;
276                    return src.getComponentPopupMenu() != null;
277                }
278
279                return false;
280            }
281
282            if (sender instanceof JRootPane) {
283                JButton owner = ((JRootPane)sender).getDefaultButton();
284                return (owner != null && owner.getModel().isEnabled() && owner.isShowing());
285            }
286            return true;
287        }
288    }
289
290    @SuppressWarnings("serial") // JDK-implementation class
291    private static class RootPaneInputMap extends ComponentInputMapUIResource {
292        public RootPaneInputMap(JComponent c) {
293            super(c);
294        }
295    }
296}
297