replaced <code></code> with more concise {@code}
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / commands / RequestFocusInToolWindowCmd.java
1 /*
2  * Copyright 2000-2015 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.openapi.wm.impl.commands;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.ActionCallback;
21 import com.intellij.openapi.util.Expirable;
22 import com.intellij.openapi.wm.FocusCommand;
23 import com.intellij.openapi.wm.FocusWatcher;
24 import com.intellij.openapi.wm.IdeFocusManager;
25 import com.intellij.openapi.wm.WindowManager;
26 import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
27 import com.intellij.openapi.wm.impl.FloatingDecorator;
28 import com.intellij.openapi.wm.impl.ToolWindowImpl;
29 import com.intellij.openapi.wm.impl.WindowManagerImpl;
30 import com.intellij.openapi.wm.impl.WindowWatcher;
31 import com.intellij.util.Alarm;
32 import org.jetbrains.annotations.NotNull;
33
34 import javax.swing.*;
35 import java.awt.*;
36
37 /**
38  * Requests focus for the specified tool window.
39  *
40  * @author Vladimir Kondratyev
41  */
42 public final class RequestFocusInToolWindowCmd extends FinalizableCommand {
43   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.commands.RequestFocusInToolWindowCmd");
44   private final ToolWindowImpl myToolWindow;
45   private final FocusWatcher myFocusWatcher;
46
47   private final Project myProject;
48   private final Expirable myTimestamp;
49
50   public RequestFocusInToolWindowCmd(IdeFocusManager focusManager, final ToolWindowImpl toolWindow, final FocusWatcher focusWatcher, final Runnable finishCallBack, Project project) {
51     super(finishCallBack);
52     myToolWindow = toolWindow;
53     myFocusWatcher = focusWatcher;
54     myProject = project;
55
56     myTimestamp = focusManager.getTimestamp(true);
57   }
58
59   @Override
60   public final void run() {
61     myToolWindow.getActivation().doWhenDone(() -> processRequestFocus());
62   }
63
64   private void processRequestFocus() {
65     try {
66
67       if (myTimestamp.isExpired()) {
68         return;
69       }
70
71       Component preferredFocusedComponent = myToolWindow.isUseLastFocusedOnActivation() ? myFocusWatcher.getFocusedComponent() : null;
72
73       if (preferredFocusedComponent == null && myToolWindow.getContentManager().getSelectedContent() != null) {
74         preferredFocusedComponent = myToolWindow.getContentManager().getSelectedContent().getPreferredFocusableComponent();
75         if (preferredFocusedComponent != null) {
76           preferredFocusedComponent = IdeFocusTraversalPolicy.getPreferredFocusedComponent((JComponent)preferredFocusedComponent);
77         }
78       }
79
80       if (preferredFocusedComponent == null) {
81         preferredFocusedComponent = myFocusWatcher.getNearestFocusableComponent();
82         if (preferredFocusedComponent instanceof JComponent) {
83           preferredFocusedComponent = IdeFocusTraversalPolicy.getPreferredFocusedComponent((JComponent)preferredFocusedComponent);
84         }
85       }
86
87       if (preferredFocusedComponent != null) {
88         // When we get remembered component this component can be already invisible
89         if (!preferredFocusedComponent.isShowing()) {
90           preferredFocusedComponent = null;
91         }
92       }
93
94       if (preferredFocusedComponent == null) {
95         final JComponent component = myToolWindow.getComponent();
96         preferredFocusedComponent = IdeFocusTraversalPolicy.getPreferredFocusedComponent(component);
97       }
98
99       // Try to focus component which is preferred one for the tool window
100       if (preferredFocusedComponent != null) {
101         requestFocus(preferredFocusedComponent).doWhenDone(() -> bringOwnerToFront());
102       }
103       else {
104         // If there is no preferred component then try to focus tool window itself
105         final JComponent componentToFocus = myToolWindow.getComponent();
106         requestFocus(componentToFocus).doWhenDone(() -> bringOwnerToFront());
107       }
108     }
109     finally {
110       finish();
111     }
112   }
113
114   private void bringOwnerToFront() {
115     final Window owner = SwingUtilities.getWindowAncestor(myToolWindow.getComponent());
116     //Toolwindow component shouldn't take focus back if new dialog or frame appears
117     //Example: Ctrl+D on file history brings a diff dialog to front and then hides it by main frame by calling
118     // toFront on toolwindow window
119     Window activeFrame = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
120     if (activeFrame != null && activeFrame != owner) return;
121     //if (owner == null) {
122     //  System.out.println("owner = " + owner);
123     //  return;
124     //}
125     // if owner is active window or it has active child window which isn't floating decorator then
126     // don't bring owner window to font. If we will make toFront every time then it's possible
127     // the following situation:
128     // 1. user perform refactoring
129     // 2. "Do not show preview" dialog is popping up.
130     // 3. At that time "preview" tool window is being activated and modal "don't show..." dialog
131     // isn't active.
132     if (owner != null && owner.getFocusOwner() == null) {
133       final Window activeWindow = getActiveWindow(owner.getOwnedWindows());
134       if (activeWindow == null || activeWindow instanceof FloatingDecorator) {
135         LOG.debug("owner.toFront()");
136         //Thread.dumpStack();
137         //System.out.println("------------------------------------------------------");
138         owner.toFront();
139       }
140     }
141   }
142
143
144   @NotNull
145   private ActionCallback requestFocus(@NotNull final Component c) {
146     final ActionCallback result = new ActionCallback();
147     final Alarm checkerAlarm = new Alarm(result);
148     Runnable checker = new Runnable() {
149       final long startTime = System.currentTimeMillis();
150       @Override
151       public void run() {
152         if (System.currentTimeMillis() - startTime > 10000) {
153           result.setRejected();
154           return;
155         }
156         if (c.isShowing()) {
157           final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
158           if (owner != null && owner == c) {
159             myManager.getFocusManager().requestFocus(new FocusCommand() {
160               @Override
161               @NotNull
162               public ActionCallback run() {
163                 return ActionCallback.DONE;
164               }
165             }, false).doWhenProcessed(() -> updateToolWindow(c)).notify(result);
166           }
167           else {
168             myManager.getFocusManager().requestFocus(new FocusCommand.ByComponent(c, myToolWindow.getComponent(), myProject, new Exception()),
169                                                      false)
170               .doWhenProcessed(() -> updateToolWindow(c)).notify(result);
171           }
172         }
173         else {
174           checkerAlarm.addRequest(this, 100);
175         }
176       }
177     };
178     checkerAlarm.addRequest(checker, 0);
179     return result;
180   }
181
182   private void updateToolWindow(Component c) {
183     if (c.isFocusOwner()) {
184       myFocusWatcher.setFocusedComponentImpl(c);
185       if (myToolWindow.isAvailable() && !myToolWindow.isActive()) {
186         myToolWindow.activate(null, true, false);
187       }
188     }
189
190     updateFocusedComponentForWatcher(c);
191   }
192
193   private static void updateFocusedComponentForWatcher(final Component c) {
194     final WindowWatcher watcher = ((WindowManagerImpl)WindowManager.getInstance()).getWindowWatcher();
195     final FocusWatcher focusWatcher = watcher.getFocusWatcherFor(c);
196     if (focusWatcher != null && c.isFocusOwner()) {
197       focusWatcher.setFocusedComponentImpl(c);
198     }
199   }
200
201   /**
202    * @return first active window from hierarchy with specified roots. Returns {@code null}
203    *         if there is no active window in the hierarchy.
204    */
205   private static Window getActiveWindow(final Window[] windows) {
206     for (Window window : windows) {
207       if (window.isShowing() && window.isActive()) {
208         return window;
209       }
210       window = getActiveWindow(window.getOwnedWindows());
211       if (window != null) {
212         return window;
213       }
214     }
215     return null;
216   }
217 }