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
;
49 private Component myFocusOwner
;
50 private Component myLocalFocusOwner
;
52 private static final Map
<Window
, List
<FocusTrackback
>> ourRootWindowToParentsStack
= new WeakHashMap
<Window
, List
<FocusTrackback
>>();
53 private static final Map
<Window
, WeakReference
<Component
>> ourRootWindowToFocusedMap
=
54 new WeakHashMap
<Window
, WeakReference
<Component
>>();
56 private String myRequestorName
;
57 private ComponentQuery myFocusedComponentQuery
;
58 private boolean myMustBeShown
;
60 private boolean myConsumed
;
61 private WeakReference myRequestor
;
62 private boolean mySheduledForRestore
;
63 private boolean myWillBeSheduledForRestore
;
65 public FocusTrackback(@NotNull Object requestor
, Component parent
, boolean mustBeShown
) {
66 this(requestor
, SwingUtilities
.getWindowAncestor(parent
), mustBeShown
);
69 public FocusTrackback(@NotNull Object requestor
, Window parent
, boolean mustBeShown
) {
70 myRequestor
= new WeakReference
<Object
>(requestor
);
71 myRequestorName
= requestor
.toString();
72 myParentWindow
= parent
;
73 myMustBeShown
= mustBeShown
;
76 final Application app
= ApplicationManager
.getApplication();
77 if (app
== null || app
.isUnitTestMode() || wrongOS()) return;
81 final List
<FocusTrackback
> stack
= getStackForRoot(myRoot
);
82 final int index
= stack
.indexOf(this);
84 //todo [kirillk] diagnostics for IDEADEV-28766
85 assert index
>= 0 : myRequestorName
;
87 final KeyboardFocusManager manager
= KeyboardFocusManager
.getCurrentKeyboardFocusManager();
88 setLocalFocusOwner(manager
.getPermanentFocusOwner());
91 setFocusOwner(manager
.getPermanentFocusOwner());
92 if (getFocusOwner() == null) {
93 final Window window
= manager
.getActiveWindow();
94 if (window
instanceof Provider
) {
95 final FocusTrackback other
= ((Provider
)window
).getFocusTrackback();
97 setFocusOwner(other
.getFocusOwner());
103 setFocusOwner(stack
.get(0).getFocusOwner());
106 if (stack
.size() == 1 && getFocusOwner() == null) {
107 setFocusOwner(getFocusFor(myRoot
));
109 else if (index
== 0 && getFocusOwner() != null) {
110 setFocusFor(myRoot
, getFocusOwner());
114 private void setLocalFocusOwner(Component component
) {
115 myLocalFocusOwner
= component
;
118 private static Component
getFocusFor(Window parent
) {
119 final WeakReference
<Component
> ref
= ourRootWindowToFocusedMap
.get(parent
);
120 return ref
!= null ? ref
.get() : null;
123 private static void setFocusFor(Window parent
, Component focus
) {
124 ourRootWindowToFocusedMap
.put(parent
, new WeakReference
<Component
>(focus
));
127 private static boolean wrongOS() {
131 public void registerFocusComponent(@NotNull final Component focusedComponent
) {
132 registerFocusComponent(new ComponentQuery() {
133 public Component
getComponent() {
134 return focusedComponent
;
139 public void registerFocusComponent(@NotNull ComponentQuery query
) {
140 myFocusedComponentQuery
= query
;
143 private void register(final Window parent
) {
144 myRoot
= findUtlimateParent(parent
);
145 List
<FocusTrackback
> stack
= getCleanStackForRoot();
150 private List
<FocusTrackback
> getCleanStackForRoot() {
151 return getCleanStackForRoot(myRoot
);
154 private static List
<FocusTrackback
> getCleanStackForRoot(final Window root
) {
155 List
<FocusTrackback
> stack
= getStackForRoot(root
);
157 final FocusTrackback
[] stackArray
= stack
.toArray(new FocusTrackback
[stack
.size()]);
158 for (FocusTrackback eachExisting
: stackArray
) {
159 if (eachExisting
!= null && eachExisting
.isConsumed()) {
160 eachExisting
.dispose();
162 else if (eachExisting
== null) {
163 stack
.remove(eachExisting
);
169 public void restoreFocus() {
170 final Application app
= ApplicationManager
.getApplication();
171 if (app
== null || wrongOS() || myConsumed
|| isSheduledForRestore()) return;
173 Project project
= null;
174 DataContext context
=
175 myParentWindow
== null ? DataManager
.getInstance().getDataContext() : DataManager
.getInstance().getDataContext(myParentWindow
);
176 if (context
!= null) {
177 project
= PlatformDataKeys
.PROJECT
.getData(context
);
180 mySheduledForRestore
= true;
181 final List
<FocusTrackback
> stack
= getCleanStackForRoot();
182 final int index
= stack
.indexOf(this);
183 for (int i
= index
- 1; i
>=0; i
--) {
184 if (stack
.get(i
).isSheduledForRestore()) {
190 if (project
!= null && !project
.isDisposed()) {
191 final IdeFocusManager focusManager
= IdeFocusManager
.getInstance(project
);
192 focusManager
.requestFocus(new MyFocusCommand(), false).doWhenProcessed(new Runnable() {
199 // no ide focus manager, so no way -- do just later
200 //noinspection SSBasedInspection
201 SwingUtilities
.invokeLater(new Runnable() {
210 private void _restoreFocus() {
211 final List
<FocusTrackback
> stack
= getCleanStack();
213 if (!stack
.contains(this)) return;
215 Component toFocus
= queryToFocus(stack
, this, true);
217 if (toFocus
!= null) {
218 final Component ownerBySwing
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
219 if (ownerBySwing
!= null) {
220 final Window ownerBySwingWindow
= SwingUtilities
.getWindowAncestor(ownerBySwing
);
221 if (ownerBySwingWindow
!= null && ownerBySwingWindow
== SwingUtilities
.getWindowAncestor(toFocus
)) {
222 toFocus
= ownerBySwing
;
226 if (myParentWindow
!= null) {
227 final Window to
= toFocus
instanceof Window ?
(Window
) toFocus
: SwingUtilities
.getWindowAncestor(toFocus
);
228 if (to
!= null && UIUtil
.findUltimateParent(to
) == UIUtil
.findUltimateParent(myParentWindow
)) { // IDEADEV-34537
229 toFocus
.requestFocus();
232 toFocus
.requestFocus();
240 private static Component
queryToFocus(final List
<FocusTrackback
> stack
, final FocusTrackback trackback
, boolean mustBeLastInStack
) {
241 final int index
= stack
.indexOf(trackback
);
242 Component toFocus
= null;
244 if (trackback
.myLocalFocusOwner
!= null) {
245 toFocus
= trackback
.myLocalFocusOwner
;
247 if (!toFocus
.isShowing()) {
252 if (toFocus
== null) {
254 final ComponentQuery query
= stack
.get(index
- 1).myFocusedComponentQuery
;
255 toFocus
= query
!= null ? query
.getComponent() : null;
258 toFocus
= trackback
.getFocusOwner();
262 if (mustBeLastInStack
) {
263 for (int i
= index
+ 1; i
< stack
.size(); i
++) {
264 if (!stack
.get(i
).isConsumed()) {
276 private List
<FocusTrackback
> getCleanStack() {
277 final List
<FocusTrackback
> stack
= getStackForRoot(myRoot
);
279 final FocusTrackback
[] all
= stack
.toArray(new FocusTrackback
[stack
.size()]);
280 for (FocusTrackback each
: all
) {
281 if (each
== null || (each
!= this && each
.isConsumed())) {
288 private static List
<FocusTrackback
> getStackForRoot(final Window root
) {
289 List
<FocusTrackback
> stack
= ourRootWindowToParentsStack
.get(root
);
291 stack
= new ArrayList
<FocusTrackback
>();
292 ourRootWindowToParentsStack
.put(root
, stack
);
298 private static Window
findUtlimateParent(final Window parent
) {
299 Window root
= parent
== null ? JOptionPane
.getRootFrame() : parent
;
300 while (root
!= null) {
301 final Container next
= root
.getParent();
302 if (next
== null) break;
303 if (next
instanceof Window
) {
306 final Window nextWindow
= SwingUtilities
.getWindowAncestor(next
);
307 if (nextWindow
== null) break;
315 public Component
getFocusOwner() {
319 @SuppressWarnings({"HardCodedStringLiteral"})
320 public String
toString() {
321 return getClass().getName() + " requestor: " + myRequestorName
+ " parent=" + myParentWindow
;
324 public void dispose() {
326 getStackForRoot(myRoot
).remove(this);
327 mySheduledForRestore
= false;
328 myParentWindow
= null;
331 myLocalFocusOwner
= null;
334 private boolean isConsumed() {
335 if (myConsumed
) return true;
338 return !isSheduledForRestore()
339 && myFocusedComponentQuery
!= null
340 && myFocusedComponentQuery
.getComponent() != null
341 && !myFocusedComponentQuery
.getComponent().isShowing();
344 return myParentWindow
== null || !myParentWindow
.isShowing();
348 public void consume() {
352 private void setFocusOwner(final Component focusOwner
) {
353 myFocusOwner
= focusOwner
;
356 public void setMustBeShown(final boolean mustBeShown
) {
357 myMustBeShown
= mustBeShown
;
360 public boolean isMustBeShown() {
361 return myMustBeShown
;
364 public static void release(@NotNull final JFrame frame
) {
365 final Window
[] all
= ourRootWindowToParentsStack
.keySet().toArray(new Window
[ourRootWindowToParentsStack
.size()]);
366 for (Window each
: all
) {
367 if (each
== null) continue;
369 if (each
== frame
|| SwingUtilities
.isDescendingFrom(each
, frame
)) {
370 ourRootWindowToParentsStack
.remove(each
);
375 public Object
getRequestor() {
376 return myRequestor
.get();
379 public void setWillBeSheduledForRestore() {
380 myWillBeSheduledForRestore
= true;
383 public boolean isSheduledForRestore() {
384 return mySheduledForRestore
;
387 public boolean isWillBeSheduledForRestore() {
388 return myWillBeSheduledForRestore
;
391 public interface Provider
{
392 FocusTrackback
getFocusTrackback();
395 public interface ComponentQuery
{
396 Component
getComponent();
400 public static List
<JBPopup
> getChildPopups(@NotNull final Component component
) {
401 List
<JBPopup
> result
= new ArrayList
<JBPopup
>();
403 final Window window
= SwingUtilities
.windowForComponent(component
);
404 if (window
== null) return result
;
406 final List
<FocusTrackback
> stack
= getCleanStackForRoot(findUtlimateParent(window
));
408 for (FocusTrackback each
: stack
) {
409 if (each
.isChildFor(component
) && each
.getRequestor() instanceof JBPopup
) {
410 result
.add((JBPopup
)each
.getRequestor());
417 private boolean isChildFor(final Component parent
) {
418 final Component toFocus
= queryToFocus(getCleanStack(), this, false);
419 if (toFocus
== null) return false;
420 if (parent
== toFocus
) return true;
422 return SwingUtilities
.isDescendingFrom(toFocus
, parent
);
426 private class MyFocusCommand
extends FocusCommand
{
427 public ActionCallback
run() {
429 return new ActionCallback
.Done();
433 public boolean isExpired() {
437 public String
toString() {
438 return "focus trackback";