Merge branch 'master' of git@git.labs.intellij.net:idea/community into tool-window
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ui / FocusTrackback.java
blob16e0466639e055dfe458ef92779a37e8c8c81312
1 /*
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;
33 import javax.swing.*;
34 import java.awt.*;
35 import java.lang.ref.WeakReference;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Map;
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;
76 register(parent);
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;
84 if (index == 0) {
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();
91 if (other != null) {
92 setFocusOwner(other.getFocusOwner());
97 else {
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() {
119 return false;
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();
137 stack.remove(this);
138 stack.add(this);
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);
157 return stack;
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()) {
176 dispose();
177 return;
181 if (project != null && !project.isDisposed()) {
182 final IdeFocusManager focusManager = IdeFocusManager.getInstance(project);
183 focusManager.requestFocus(new MyFocusCommand(), false).doWhenProcessed(new Runnable() {
184 public void run() {
185 dispose();
189 else {
190 // no ide focus manager, so no way -- do just later
191 //noinspection SSBasedInspection
192 SwingUtilities.invokeLater(new Runnable() {
193 public void run() {
194 _restoreFocus();
195 dispose();
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();
222 } else {
223 toFocus.requestFocus();
227 stack.remove(this);
228 dispose();
231 private static Component queryToFocus(final List<FocusTrackback> stack, final FocusTrackback trackback) {
232 final int index = stack.indexOf(trackback);
233 Component toFocus;
234 if (index > 0) {
235 final ComponentQuery query = stack.get(index - 1).myFocusedComponentQuery;
236 toFocus = query != null ? query.getComponent() : null;
238 else {
239 toFocus = trackback.getFocusOwner();
242 for (int i = index + 1; i < stack.size(); i++) {
243 if (!stack.get(i).isConsumed()) {
244 toFocus = null;
245 break;
248 return toFocus;
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())) {
257 stack.remove(each);
260 return stack;
263 private static List<FocusTrackback> getStackForRoot(final Window root) {
264 List<FocusTrackback> stack = ourRootWindowToParentsStack.get(root);
265 if (stack == null) {
266 stack = new ArrayList<FocusTrackback>();
267 ourRootWindowToParentsStack.put(root, stack);
269 return stack;
272 @Nullable
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) {
279 root = (Window)next;
281 final Window nextWindow = SwingUtilities.getWindowAncestor(next);
282 if (nextWindow == null) break;
283 root = nextWindow;
286 return root;
289 @Nullable
290 public Component getFocusOwner() {
291 return myFocusOwner;
294 @SuppressWarnings({"HardCodedStringLiteral"})
295 public String toString() {
296 return getClass().getName() + " requestor: " + myRequestorName + " parent=" + myParentWindow;
299 public void dispose() {
300 consume();
301 getStackForRoot(myRoot).remove(this);
302 mySheduledForRestore = false;
303 myParentWindow = null;
304 myRoot = null;
305 myFocusOwner = null;
308 private boolean isConsumed() {
309 if (myConsumed) return true;
311 if (myMustBeShown) {
312 return !isSheduledForRestore()
313 && myFocusedComponentQuery != null
314 && myFocusedComponentQuery.getComponent() != null
315 && !myFocusedComponentQuery.getComponent().isShowing();
317 else {
318 return myParentWindow == null || !myParentWindow.isShowing();
322 public void consume() {
323 myConsumed = true;
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();
365 @Nullable
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();
378 return null;
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() {
392 _restoreFocus();
393 return new ActionCallback.Done();
396 @Override
397 public boolean isExpired() {
398 return isConsumed();
401 public String toString() {
402 return "focus trackback";