2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com
.intellij
.ui
;
18 import com
.intellij
.ide
.DataManager
;
19 import com
.intellij
.openapi
.actionSystem
.DataContext
;
20 import com
.intellij
.openapi
.actionSystem
.PlatformDataKeys
;
21 import com
.intellij
.openapi
.application
.Application
;
22 import com
.intellij
.openapi
.application
.ApplicationManager
;
23 import com
.intellij
.openapi
.diagnostic
.Logger
;
24 import com
.intellij
.openapi
.project
.Project
;
25 import com
.intellij
.openapi
.ui
.popup
.JBPopup
;
26 import com
.intellij
.openapi
.util
.ActionCallback
;
27 import com
.intellij
.openapi
.wm
.FocusCommand
;
28 import com
.intellij
.openapi
.wm
.IdeFocusManager
;
29 import com
.intellij
.util
.ui
.UIUtil
;
30 import org
.jetbrains
.annotations
.NotNull
;
31 import org
.jetbrains
.annotations
.Nullable
;
35 import java
.lang
.ref
.WeakReference
;
36 import java
.util
.ArrayList
;
37 import java
.util
.List
;
39 import java
.util
.WeakHashMap
;
41 public class FocusTrackback
{
43 private static final Logger LOG
= Logger
.getInstance("#com.intellij.ui.FocusTrackback");
45 private Window myParentWindow
;
47 private Window myRoot
;
48 private Component myFocusOwner
;
50 private static final Map
<Window
, List
<FocusTrackback
>> ourRootWindowToParentsStack
= new WeakHashMap
<Window
, List
<FocusTrackback
>>();
51 private static final Map
<Window
, WeakReference
<Component
>> ourRootWindowToFocusedMap
=
52 new WeakHashMap
<Window
, WeakReference
<Component
>>();
54 private String myRequestorName
;
55 private ComponentQuery myFocusedComponentQuery
;
56 private boolean myMustBeShown
;
58 private boolean myConsumed
;
59 private WeakReference myRequestor
;
60 private boolean mySheduledForRestore
;
62 public FocusTrackback(@NotNull Object requestor
, Component parent
, boolean mustBeShown
) {
63 this(requestor
, SwingUtilities
.getWindowAncestor(parent
), mustBeShown
);
66 public FocusTrackback(@NotNull Object requestor
, Window parent
, boolean mustBeShown
) {
67 myRequestor
= new WeakReference
<Object
>(requestor
);
68 myRequestorName
= requestor
.toString();
69 myParentWindow
= parent
;
70 myMustBeShown
= mustBeShown
;
73 final Application app
= ApplicationManager
.getApplication();
74 if (app
== null || app
.isUnitTestMode() || wrongOS()) return;
78 final List
<FocusTrackback
> stack
= getStackForRoot(myRoot
);
79 final int index
= stack
.indexOf(this);
81 //todo [kirillk] diagnostics for IDEADEV-28766
82 assert index
>= 0 : myRequestorName
;
85 final KeyboardFocusManager manager
= KeyboardFocusManager
.getCurrentKeyboardFocusManager();
86 setFocusOwner(manager
.getPermanentFocusOwner());
87 if (getFocusOwner() == null) {
88 final Window window
= manager
.getActiveWindow();
89 if (window
instanceof Provider
) {
90 final FocusTrackback other
= ((Provider
)window
).getFocusTrackback();
92 setFocusOwner(other
.getFocusOwner());
98 setFocusOwner(stack
.get(0).getFocusOwner());
101 if (stack
.size() == 1 && getFocusOwner() == null) {
102 setFocusOwner(getFocusFor(myRoot
));
104 else if (index
== 0 && getFocusOwner() != null) {
105 setFocusFor(myRoot
, getFocusOwner());
109 private static Component
getFocusFor(Window parent
) {
110 final WeakReference
<Component
> ref
= ourRootWindowToFocusedMap
.get(parent
);
111 return ref
!= null ? ref
.get() : null;
114 private static void setFocusFor(Window parent
, Component focus
) {
115 ourRootWindowToFocusedMap
.put(parent
, new WeakReference
<Component
>(focus
));
118 private static boolean wrongOS() {
122 public void registerFocusComponent(@NotNull final Component focusedComponent
) {
123 registerFocusComponent(new ComponentQuery() {
124 public Component
getComponent() {
125 return focusedComponent
;
130 public void registerFocusComponent(@NotNull ComponentQuery query
) {
131 myFocusedComponentQuery
= query
;
134 private void register(final Window parent
) {
135 myRoot
= findUtlimateParent(parent
);
136 List
<FocusTrackback
> stack
= getCleanStackForRoot();
141 private List
<FocusTrackback
> getCleanStackForRoot() {
142 return getCleanStackForRoot(myRoot
);
145 private static List
<FocusTrackback
> getCleanStackForRoot(final Window root
) {
146 List
<FocusTrackback
> stack
= getStackForRoot(root
);
148 final FocusTrackback
[] stackArray
= stack
.toArray(new FocusTrackback
[stack
.size()]);
149 for (FocusTrackback eachExisting
: stackArray
) {
150 if (eachExisting
!= null && eachExisting
.isConsumed()) {
151 eachExisting
.dispose();
153 else if (eachExisting
== null) {
154 stack
.remove(eachExisting
);
160 public void restoreFocus() {
161 final Application app
= ApplicationManager
.getApplication();
162 if (app
== null || wrongOS() || myConsumed
|| isSheduledForRestore()) return;
164 Project project
= null;
165 DataContext context
=
166 myParentWindow
== null ? DataManager
.getInstance().getDataContext() : DataManager
.getInstance().getDataContext(myParentWindow
);
167 if (context
!= null) {
168 project
= PlatformDataKeys
.PROJECT
.getData(context
);
171 mySheduledForRestore
= true;
172 final List
<FocusTrackback
> stack
= getCleanStackForRoot();
173 final int index
= stack
.indexOf(this);
174 for (int i
= index
- 1; i
>=0; i
--) {
175 if (stack
.get(i
).isSheduledForRestore()) {
181 if (project
!= null && !project
.isDisposed()) {
182 final IdeFocusManager focusManager
= IdeFocusManager
.getInstance(project
);
183 focusManager
.requestFocus(new MyFocusCommand(), false).doWhenProcessed(new Runnable() {
190 // no ide focus manager, so no way -- do just later
191 //noinspection SSBasedInspection
192 SwingUtilities
.invokeLater(new Runnable() {
201 private void _restoreFocus() {
202 final List
<FocusTrackback
> stack
= getCleanStack();
204 if (!stack
.contains(this)) return;
206 Component toFocus
= queryToFocus(stack
, this);
208 if (toFocus
!= null) {
209 final Component ownerBySwing
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
210 if (ownerBySwing
!= null) {
211 final Window ownerBySwingWindow
= SwingUtilities
.getWindowAncestor(ownerBySwing
);
212 if (ownerBySwingWindow
!= null && ownerBySwingWindow
== SwingUtilities
.getWindowAncestor(toFocus
)) {
213 toFocus
= ownerBySwing
;
217 if (myParentWindow
!= null) {
218 final Window to
= toFocus
instanceof Window ?
(Window
) toFocus
: SwingUtilities
.getWindowAncestor(toFocus
);
219 if (to
!= null && UIUtil
.findUltimateParent(to
) == UIUtil
.findUltimateParent(myParentWindow
)) { // IDEADEV-34537
220 toFocus
.requestFocus();
223 toFocus
.requestFocus();
231 private static Component
queryToFocus(final List
<FocusTrackback
> stack
, final FocusTrackback trackback
) {
232 final int index
= stack
.indexOf(trackback
);
235 final ComponentQuery query
= stack
.get(index
- 1).myFocusedComponentQuery
;
236 toFocus
= query
!= null ? query
.getComponent() : null;
239 toFocus
= trackback
.getFocusOwner();
242 for (int i
= index
+ 1; i
< stack
.size(); i
++) {
243 if (!stack
.get(i
).isConsumed()) {
251 private List
<FocusTrackback
> getCleanStack() {
252 final List
<FocusTrackback
> stack
= getStackForRoot(myRoot
);
254 final FocusTrackback
[] all
= stack
.toArray(new FocusTrackback
[stack
.size()]);
255 for (FocusTrackback each
: all
) {
256 if (each
== null || (each
!= this && each
.isConsumed())) {
263 private static List
<FocusTrackback
> getStackForRoot(final Window root
) {
264 List
<FocusTrackback
> stack
= ourRootWindowToParentsStack
.get(root
);
266 stack
= new ArrayList
<FocusTrackback
>();
267 ourRootWindowToParentsStack
.put(root
, stack
);
273 private static Window
findUtlimateParent(final Window parent
) {
274 Window root
= parent
== null ? JOptionPane
.getRootFrame() : parent
;
275 while (root
!= null) {
276 final Container next
= root
.getParent();
277 if (next
== null) break;
278 if (next
instanceof Window
) {
281 final Window nextWindow
= SwingUtilities
.getWindowAncestor(next
);
282 if (nextWindow
== null) break;
290 public Component
getFocusOwner() {
294 @SuppressWarnings({"HardCodedStringLiteral"})
295 public String
toString() {
296 return getClass().getName() + " requestor: " + myRequestorName
+ " parent=" + myParentWindow
;
299 public void dispose() {
301 getStackForRoot(myRoot
).remove(this);
302 mySheduledForRestore
= false;
303 myParentWindow
= null;
308 private boolean isConsumed() {
309 if (myConsumed
) return true;
312 return !isSheduledForRestore()
313 && myFocusedComponentQuery
!= null
314 && myFocusedComponentQuery
.getComponent() != null
315 && !myFocusedComponentQuery
.getComponent().isShowing();
318 return myParentWindow
== null || !myParentWindow
.isShowing();
322 public void consume() {
326 private void setFocusOwner(final Component focusOwner
) {
327 myFocusOwner
= focusOwner
;
330 public void setMustBeShown(final boolean mustBeShown
) {
331 myMustBeShown
= mustBeShown
;
334 public boolean isMustBeShown() {
335 return myMustBeShown
;
338 public static void release(@NotNull final JFrame frame
) {
339 final Window
[] all
= ourRootWindowToParentsStack
.keySet().toArray(new Window
[ourRootWindowToParentsStack
.size()]);
340 for (Window each
: all
) {
341 if (each
== null) continue;
343 if (each
== frame
|| SwingUtilities
.isDescendingFrom(each
, frame
)) {
344 ourRootWindowToParentsStack
.remove(each
);
349 public Object
getRequestor() {
350 return myRequestor
.get();
353 public boolean isSheduledForRestore() {
354 return mySheduledForRestore
;
357 public interface Provider
{
358 FocusTrackback
getFocusTrackback();
361 public interface ComponentQuery
{
362 Component
getComponent();
366 public static JBPopup
getChildPopup(@NotNull final Component component
) {
367 final Window window
= SwingUtilities
.windowForComponent(component
);
368 if (window
== null) return null;
370 final List
<FocusTrackback
> stack
= getCleanStackForRoot(findUtlimateParent(window
));
372 for (FocusTrackback each
: stack
) {
373 if (each
.isChildFor(component
) && each
.getRequestor() instanceof JBPopup
) {
374 return (JBPopup
)each
.getRequestor();
381 private boolean isChildFor(final Component parent
) {
382 final Component toFocus
= queryToFocus(getCleanStack(), this);
383 if (toFocus
== null) return false;
384 if (parent
== toFocus
) return true;
386 return SwingUtilities
.isDescendingFrom(toFocus
, parent
);
390 private class MyFocusCommand
extends FocusCommand
{
391 public ActionCallback
run() {
393 return new ActionCallback
.Done();
397 public boolean isExpired() {
401 public String
toString() {
402 return "focus trackback";