EA-83059 - IAE: PopupComponent$DialogPopupWrapper.<init>
[idea/community.git] / platform / platform-impl / src / com / intellij / ui / popup / PopupComponent.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.ui.popup;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.ui.popup.JBPopup;
20 import com.intellij.openapi.ui.popup.util.PopupUtil;
21 import com.intellij.openapi.util.SystemInfo;
22 import com.intellij.openapi.util.registry.Registry;
23 import com.intellij.openapi.wm.IdeFocusManager;
24 import com.intellij.util.ReflectionUtil;
25 import com.intellij.util.ui.UIUtil;
26 import com.intellij.util.ui.accessibility.ScreenReader;
27 import com.sun.awt.AWTUtilities;
28
29 import javax.accessibility.AccessibleContext;
30 import javax.swing.*;
31 import java.awt.*;
32 import java.awt.event.FocusEvent;
33 import java.awt.event.WindowAdapter;
34 import java.awt.event.WindowEvent;
35 import java.lang.reflect.Field;
36 import java.lang.reflect.Method;
37
38 public interface PopupComponent {
39   Logger LOG = Logger.getInstance("#com.intellij.ui.popup.PopupComponent");
40
41   void hide(boolean dispose);
42
43   void show();
44
45   Window getWindow();
46
47   void setRequestFocus(boolean requestFocus);
48
49   boolean isPopupWindow(Window window);
50
51   interface Factory {
52     PopupComponent getPopup(Component owner, Component content, int x, int y, JBPopup jbPopup);
53
54     boolean isNativePopup();
55
56     class AwtDefault implements Factory {
57       public PopupComponent getPopup(Component owner, Component content, int x, int y, JBPopup jbPopup) {
58         final PopupFactory factory = PopupFactory.getSharedInstance();
59         final Popup popup = factory.getPopup(owner, content, x, y);
60         return new AwtPopupWrapper(popup, jbPopup);
61       }
62
63       public boolean isNativePopup() {
64         return true;
65       }
66     }
67
68     class AwtHeavyweight implements Factory {
69       public PopupComponent getPopup(Component owner, Component content, int x, int y, JBPopup jbPopup) {
70         if (OurHeavyWeightPopup.isEnabled()) {
71           return new AwtPopupWrapper(new OurHeavyWeightPopup(owner, content, x, y), jbPopup);
72         }
73         final PopupFactory factory = PopupFactory.getSharedInstance();
74
75         final int oldType = PopupUtil.getPopupType(factory);
76         PopupUtil.setPopupType(factory, 2);
77         final Popup popup = factory.getPopup(owner, content, x, y);
78         if (oldType >= 0) PopupUtil.setPopupType(factory, oldType);
79
80         return new AwtPopupWrapper(popup, jbPopup);
81       }
82
83       public boolean isNativePopup() {
84         return true;
85       }
86     }
87
88     class Dialog implements Factory {
89       public PopupComponent getPopup(Component owner, Component content, int x, int y, JBPopup jbPopup) {
90         return new DialogPopupWrapper(owner, content, x, y, jbPopup);
91       }
92
93       public boolean isNativePopup() {
94         return false;
95       }
96     }
97   }
98
99   class DialogPopupWrapper implements PopupComponent {
100     private final JDialog myDialog;
101     private boolean myRequestFocus = true;
102
103     public void setRequestFocus(boolean requestFocus) {
104       myRequestFocus = requestFocus;
105     }
106
107     @Override
108     public boolean isPopupWindow(Window window) {
109       return myDialog != null && myDialog == window;
110     }
111
112     public DialogPopupWrapper(Component owner, Component content, int x, int y, JBPopup jbPopup) {
113       if (!owner.isShowing()) {
114         throw new IllegalArgumentException("Popup owner must be showing, owner " + owner.getClass());
115       }
116
117       final Window wnd = UIUtil.getWindow(owner);
118       if (wnd instanceof Frame) {
119         myDialog = new JDialog((Frame)wnd);
120       } else if (wnd instanceof Dialog) {
121         myDialog = new JDialog((Dialog)wnd);
122       } else {
123         myDialog = new JDialog();
124       }
125
126       myDialog.getContentPane().setLayout(new BorderLayout());
127       myDialog.getContentPane().add(content, BorderLayout.CENTER);
128       myDialog.getRootPane().putClientProperty(JBPopup.KEY, jbPopup);
129       myDialog.getRootPane().setWindowDecorationStyle(JRootPane.NONE);
130       myDialog.setUndecorated(true);
131       myDialog.setBackground(UIUtil.getPanelBackground());
132       myDialog.pack();
133       myDialog.setLocation(x, y);
134       myDialog.addWindowListener(new WindowAdapter() {
135         @Override
136         public void windowClosed(WindowEvent e) {
137           super.windowClosed(e);
138           //A11YFix.invokeFocusGained(myDialog);
139         }
140       });
141     }
142
143     public Window getWindow() {
144       return myDialog;
145     }
146
147     public void hide(boolean dispose) {
148       myDialog.setVisible(false);
149       if (dispose) {
150         myDialog.dispose();
151         myDialog.getRootPane().putClientProperty(JBPopup.KEY, null);
152       }
153     }
154
155     public void show() {
156
157     UIUtil.suppressFocusStealing(getWindow());
158
159       if (!myRequestFocus) {
160         myDialog.setFocusableWindowState(false);
161       }
162
163       AwtPopupWrapper.fixFlickering(myDialog, false);
164       myDialog.setVisible(true);
165       AwtPopupWrapper.fixFlickering(myDialog, true);
166
167       SwingUtilities.invokeLater(() -> myDialog.setFocusableWindowState(true));
168     }
169   }
170
171   class AwtPopupWrapper implements PopupComponent {
172
173     private final Popup myPopup;
174     private JBPopup myJBPopup;
175
176     public AwtPopupWrapper(Popup popup, JBPopup jbPopup) {
177       myPopup = popup;
178       myJBPopup = jbPopup;
179
180       if (SystemInfo.isMac && UIUtil.isUnderAquaLookAndFeel()) {
181         final Component c = ReflectionUtil.getField(Popup.class, myPopup, Component.class, "component");
182         c.setBackground(UIUtil.getPanelBackground());
183       }
184       // TODO: should we call A11YFix.invokeFocusGained(getWindow()) on window closing?
185     }
186
187     @Override
188     public boolean isPopupWindow(Window window) {
189       final Window wnd = getWindow();
190       return wnd != null && wnd == window;
191     }
192
193     public void hide(boolean dispose) {
194       myPopup.hide();
195
196       Window wnd = getWindow();
197       if (wnd instanceof JWindow) {
198         JRootPane rootPane = ((JWindow)wnd).getRootPane();
199         if (rootPane != null) {
200           ReflectionUtil.resetField(rootPane, "clientProperties");
201           final Container cp = rootPane.getContentPane();
202           if (cp != null) {
203             cp.removeAll();
204           }
205         }
206       }
207     }
208
209     public void show() {
210       Window wnd = getWindow();
211       
212       fixFlickering(wnd, false);
213       myPopup.show();
214       fixFlickering(wnd, true);
215       
216       if (wnd instanceof JWindow) {
217         ((JWindow)wnd).getRootPane().putClientProperty(JBPopup.KEY, myJBPopup);
218       }
219     }
220
221     private static void fixFlickering(Window wnd, boolean opaque) {
222       try {
223         if (UIUtil.isUnderDarcula() && SystemInfo.isMac && Registry.is("darcula.fix.native.flickering") && wnd != null) {
224           AWTUtilities.setWindowOpaque(wnd, opaque);
225         }
226       } catch (Exception ignore) {}
227     }
228
229     public Window getWindow() {
230       final Component c = ReflectionUtil.getField(Popup.class, myPopup, Component.class, "component");
231       return c instanceof JWindow ? (JWindow)c : null;
232     }
233
234     public void setRequestFocus(boolean requestFocus) {
235     }
236   }
237 }
238
239 /**
240  * On Windows, AccessBridge loses a11y focus when a non-focusable popup window is closed.
241  * At the same time, IDEA focus remains in, for instance, the editor and doesn't change.
242  * As a workaround, {@link #invokeFocusGained} notifies AccessBridge that the current
243  * focus owner gains focus. This doesn't affect IDEA focus management. See IDEA-152169
244  */
245 class A11YFix {
246   private static Class cAccessBridge;
247   private static Field fAccessBridge;
248   private static Method mFocusGained;
249   private static boolean initialized;
250   private static final boolean ENABLED = SystemInfo.isWindows && ScreenReader.isEnabled(ScreenReader.ACCESS_BRIDGE);
251
252   public static void invokeFocusGained(Window closingWindow) {
253     if (!ENABLED || !ScreenReader.isActive()) return;
254
255     IdeFocusManager manager = IdeFocusManager.findInstanceByComponent(closingWindow);
256     if (manager != null) {
257       Component focusOwner = manager.getFocusOwner();
258       if (focusOwner != null) {
259         Window focusedWindow = UIUtil.getWindow(focusOwner);
260         // Check if the focus owner is not in the closing window and notify AB it gains focus.
261         // In case focus owner changes, AB will catch up with it on its own.
262         if (focusedWindow != closingWindow) {
263           Object bridge = getAccessBridge();
264           if (bridge != null) {
265             FocusEvent fe = new FocusEvent(focusOwner, FocusEvent.FOCUS_GAINED);
266             try {
267               mFocusGained.invoke(bridge, fe, focusOwner.getAccessibleContext());
268             }
269             catch (Throwable ignore) {
270             }
271           }
272         }
273       }
274     }
275   }
276
277   private static boolean checkInit() {
278     if (initialized) return fAccessBridge != null && mFocusGained != null;
279
280     try {
281       ClassLoader cl = ClassLoader.getSystemClassLoader();
282       cAccessBridge = cl.loadClass("com.sun.java.accessibility.AccessBridge");
283     }
284     catch (Throwable ignore) {
285     }
286     if (cAccessBridge != null) {
287       fAccessBridge = ReflectionUtil.getDeclaredField(cAccessBridge, "theAccessBridge");
288       if (fAccessBridge != null) {
289         fAccessBridge.setAccessible(true);
290         mFocusGained = ReflectionUtil.getDeclaredMethod(cAccessBridge, "focusGained", FocusEvent.class, AccessibleContext.class);
291         if (mFocusGained != null) {
292           mFocusGained.setAccessible(true);
293         }
294       }
295     }
296     initialized = true;
297     return fAccessBridge != null && mFocusGained != null;
298   }
299
300   private static Object getAccessBridge() {
301     if (!checkInit()) return null;
302     try {
303       return fAccessBridge.get(null);
304     }
305     catch (Throwable ignore) {
306     }
307     return null;
308   }
309 }
310