replaced <code></code> with more concise {@code}
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / WindowWatcher.java
1 /*
2  * Copyright 2000-2009 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;
17
18 import com.intellij.ide.DataManager;
19 import com.intellij.openapi.actionSystem.CommonDataKeys;
20 import com.intellij.openapi.actionSystem.DataContext;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.wm.FocusWatcher;
25 import com.intellij.openapi.wm.ex.WindowManagerEx;
26 import com.intellij.util.containers.WeakHashMap;
27 import org.jetbrains.annotations.NonNls;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import javax.swing.*;
32 import java.awt.*;
33 import java.awt.event.ComponentEvent;
34 import java.awt.event.WindowEvent;
35 import java.beans.PropertyChangeEvent;
36 import java.beans.PropertyChangeListener;
37 import java.lang.ref.WeakReference;
38 import java.util.HashSet;
39 import java.util.Iterator;
40 import java.util.Map;
41
42 /**
43  * @author Anton Katilin
44  * @author Vladimir Kondratyev
45  */
46 public final class WindowWatcher implements PropertyChangeListener{
47   private static final Logger LOG=Logger.getInstance("#com.intellij.openapi.wm.impl.WindowWatcher");
48   private final Object myLock = new Object();
49   private final Map<Window, WindowInfo> myWindow2Info = new WeakHashMap<>();
50   /**
51    * Currenly focused window (window which has focused component). Can be {@code null} if there is no focused
52    * window at all.
53    */
54   private Window myFocusedWindow;
55   /**
56    * Contains last focused window for each project.
57    */
58   private final HashSet myFocusedWindows = new HashSet();
59   @NonNls protected static final String FOCUSED_WINDOW_PROPERTY = "focusedWindow";
60
61   WindowWatcher() {}
62
63   /**
64    * This method should get notifications abount changes of focused window.
65    * Only {@code focusedWindow} property is acceptable.
66    * @throws IllegalArgumentException if property name isn't {@code focusedWindow}.
67    */
68   public final void propertyChange(final PropertyChangeEvent e){
69     if(LOG.isDebugEnabled()){
70       LOG.debug("enter: propertyChange("+e+")");
71     }
72     if(!FOCUSED_WINDOW_PROPERTY.equals(e.getPropertyName())){
73       throw new IllegalArgumentException("unknown property name: "+e.getPropertyName());
74     }
75     synchronized(myLock){
76       final Window window=(Window)e.getNewValue();
77       if(window==null || ApplicationManager.getApplication().isDisposed()){
78         return;
79       }
80       if(!myWindow2Info.containsKey(window)){
81         myWindow2Info.put(window,new WindowInfo(window, true));
82       }
83       myFocusedWindow=window;
84       final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(myFocusedWindow));
85       for (Iterator i = myFocusedWindows.iterator(); i.hasNext();) {
86         final Window w = (Window)i.next();
87         final DataContext dataContext = DataManager.getInstance().getDataContext(w);
88         if (project == CommonDataKeys.PROJECT.getData(dataContext)) {
89           i.remove();
90         }
91       }
92       myFocusedWindows.add(myFocusedWindow);
93       // Set new root frame
94       final IdeFrameImpl frame;
95       if(window instanceof IdeFrameImpl){
96         frame=(IdeFrameImpl)window;
97       }else{
98         frame=(IdeFrameImpl)SwingUtilities.getAncestorOfClass(IdeFrameImpl.class,window);
99       }
100       if(frame!=null){
101         JOptionPane.setRootFrame(frame);
102       }
103     }
104     if(LOG.isDebugEnabled()){
105       LOG.debug("exit: propertyChange()");
106     }
107   }
108
109   final void dispatchComponentEvent(final ComponentEvent e){
110     final int id=e.getID();
111     if(WindowEvent.WINDOW_CLOSED == id ||
112        (ComponentEvent.COMPONENT_HIDDEN == id && e.getSource() instanceof Window)){
113       dispatchHiddenOrClosed((Window)e.getSource());
114     }
115     // Clear obsolete reference on root frame
116     if(WindowEvent.WINDOW_CLOSED==id){
117       final Window window=(Window)e.getSource();
118       if(JOptionPane.getRootFrame()==window){
119         JOptionPane.setRootFrame(null);
120       }
121     }
122   }
123
124   private void dispatchHiddenOrClosed(final Window window){
125     if(LOG.isDebugEnabled()){
126       LOG.debug("enter: dispatchClosed("+window+")");
127     }
128     synchronized(myLock){
129       final WindowInfo info=myWindow2Info.get(window);
130       if(info!=null){
131         final FocusWatcher focusWatcher=info.myFocusWatcherRef.get();
132         if(focusWatcher!=null){
133           focusWatcher.deinstall(window);
134         }
135         myWindow2Info.remove(window);
136       }
137     }
138     // Now, we have to recalculate focused window if currently focused
139     // window is being closed.
140     if(myFocusedWindow==window){
141       if(LOG.isDebugEnabled()){
142         LOG.debug("currently active window should be closed");
143       }
144       myFocusedWindow=myFocusedWindow.getOwner();
145       if (LOG.isDebugEnabled()) {
146         LOG.debug("new active window is "+myFocusedWindow);
147       }
148     }
149     for(Iterator i=myFocusedWindows.iterator();i.hasNext();){
150       final Window activeWindow = (Window)i.next();
151       if (activeWindow == window) {
152         final Window newActiveWindow = activeWindow.getOwner();
153         i.remove();
154         if (newActiveWindow != null) {
155           myFocusedWindows.add(newActiveWindow);
156         }
157         break;
158       }
159     }
160     // Remove invalid infos for garbage collected windows
161     for(Iterator i=myWindow2Info.values().iterator();i.hasNext();){
162       final WindowInfo info=(WindowInfo)i.next();
163       if(info.myFocusWatcherRef.get()==null){
164         if (LOG.isDebugEnabled()) {
165           LOG.debug("remove collected info");
166         }
167         i.remove();
168       }
169     }
170   }
171
172   public final Window getFocusedWindow(){
173     synchronized(myLock){
174       return myFocusedWindow;
175     }
176   }
177
178   @Nullable
179   public final Component getFocusedComponent(@Nullable final Project project) {
180     synchronized(myLock){
181       final Window window=getFocusedWindowForProject(project);
182       if(window==null){
183         return null;
184       }
185       return getFocusedComponent(window);
186     }
187   }
188
189
190   public final Component getFocusedComponent(@NotNull final Window window){
191     synchronized(myLock){
192       final WindowInfo info=myWindow2Info.get(window);
193       if(info==null){ // it means that we don't manage this window, so just return standard focus owner
194         // return window.getFocusOwner();
195         // TODO[vova,anton] usage of getMostRecentFocusOwner is experimental. But it seems suitable here.
196         return window.getMostRecentFocusOwner();
197       }
198       final FocusWatcher focusWatcher=info.myFocusWatcherRef.get();
199       if(focusWatcher!=null){
200         final Component focusedComponent = focusWatcher.getFocusedComponent();
201         if(focusedComponent != null && focusedComponent.isShowing()){
202           return focusedComponent;
203         }
204         else{
205           return null;
206         }
207       }else{
208          // info isn't valid, i.e. window was garbage collected, so we need the remove invalid info
209         // and return null
210         myWindow2Info.remove(window);
211         return null;
212       }
213     }
214   }
215
216   @Nullable
217   public FocusWatcher getFocusWatcherFor(Component c) {
218     final Window window = SwingUtilities.getWindowAncestor(c);
219     final WindowInfo info = myWindow2Info.get(window);
220     return info == null ? null : info.myFocusWatcherRef.get();
221   }
222
223   /**
224    * @param project may be null (for example, if no projects are opened)
225    */
226   @Nullable
227   public final Window suggestParentWindow(@Nullable final Project project){
228     synchronized(myLock){
229       Window window=getFocusedWindowForProject(project);
230       if(window==null){
231         if (project != null) {
232           return (Window)WindowManagerEx.getInstanceEx().findFrameFor(project);
233         }
234         else{
235           return null;
236         }
237       }
238
239       LOG.assertTrue(window.isDisplayable());
240       LOG.assertTrue(window.isShowing());
241
242       while(window!=null){
243         // skip all windows until found forst dialog or frame
244         if(!(window instanceof Dialog)&&!(window instanceof Frame)){
245           window=window.getOwner();
246           continue;
247         }
248         // skip not visible and disposed/not shown windows
249         if(!window.isDisplayable()||!window.isShowing()){
250           window = window.getOwner();
251           continue;
252         }
253         // skip windows that have not associated WindowInfo
254         final WindowInfo info=myWindow2Info.get(window);
255         if(info==null){
256           window=window.getOwner();
257           continue;
258         }
259         if(info.mySuggestAsParent){
260           return window;
261         }else{
262           window=window.getOwner();
263         }
264       }
265       return null;
266     }
267   }
268
269   public final void doNotSuggestAsParent(final Window window) {
270     if(LOG.isDebugEnabled()){
271       LOG.debug("enter: doNotSuggestAsParent("+window+")");
272     }
273     synchronized(myLock){
274       final WindowInfo info=myWindow2Info.get(window);
275       if(info==null){
276         myWindow2Info.put(window,new WindowInfo(window, false));
277       }else{
278         info.mySuggestAsParent=false;
279       }
280     }
281   }
282
283   /**
284    * @return active window for specified {@code project}. There is only one window
285    * for project can be at any point of time.
286    */
287   @Nullable
288   private Window getFocusedWindowForProject(@Nullable final Project project) {
289     //todo[anton,vova]: it is possible that returned wnd is not contained in myFocusedWindows; investigate
290     outer: for(Iterator i=myFocusedWindows.iterator();i.hasNext();){
291       Window window=(Window)i.next();
292       while(!window.isDisplayable()||!window.isShowing()){ // if window isn't visible then gets its first visible ancestor
293         window=window.getOwner();
294         if(window==null){
295           continue outer;
296         }
297       }
298       final DataContext dataContext = DataManager.getInstance().getDataContext(window);
299       if (project == CommonDataKeys.PROJECT.getData(dataContext)) {
300         return window;
301       }
302     }
303     return null;
304   }
305
306   private static final class WindowInfo {
307     public final WeakReference<FocusWatcher> myFocusWatcherRef;
308     public boolean mySuggestAsParent;
309
310     public WindowInfo(final Window window,final boolean suggestAsParent){
311       final FocusWatcher focusWatcher=new FocusWatcher();
312       focusWatcher.install(window);
313       myFocusWatcherRef= new WeakReference<>(focusWatcher);
314       mySuggestAsParent=suggestAsParent;
315     }
316
317   }
318
319
320 }