sticky documentation popup [take 1]
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / WindowWatcher.java
blobe275bacc07d58882078243970d933a8587bfd284
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.openapi.wm.impl;
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.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.HashMap;
27 import org.jetbrains.annotations.NonNls;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
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;
41 /**
42 * @author Anton Katilin
43 * @author Vladimir Kondratyev
45 public final class WindowWatcher implements PropertyChangeListener{
46 private static final Logger LOG=Logger.getInstance("#com.intellij.openapi.wm.impl.WindowWatcher");
47 private final Object myLock;
48 private final HashMap<Window, WindowInfo> myWindow2Info;
49 /**
50 * Currenly focused window (window which has focused component). Can be <code>null</code> if there is no focused
51 * window at all.
53 private Window myFocusedWindow;
54 /**
55 * Contains last focused window for each project.
57 private final HashSet myFocusedWindows;
58 @NonNls protected static final String FOCUSED_WINDOW_PROPERTY = "focusedWindow";
60 WindowWatcher(){
61 myLock=new Object();
62 myWindow2Info=new HashMap<Window, WindowInfo>();
63 myFocusedWindows=new HashSet();
66 /**
67 * This method should get notifications abount changes of focused window.
68 * Only <code>focusedWindow</code> property is acceptable.
69 * @throws IllegalArgumentException if property name isn't <code>focusedWindow</code>.
71 public final void propertyChange(final PropertyChangeEvent e){
72 if(LOG.isDebugEnabled()){
73 LOG.debug("enter: propertyChange("+e+")");
75 if(!FOCUSED_WINDOW_PROPERTY.equals(e.getPropertyName())){
76 throw new IllegalArgumentException("unknown property name: "+e.getPropertyName());
78 synchronized(myLock){
79 final Window window=(Window)e.getNewValue();
80 if(window==null || ApplicationManager.getApplication().isDisposed()){
81 return;
83 if(!myWindow2Info.containsKey(window)){
84 myWindow2Info.put(window,new WindowInfo(window, true));
86 myFocusedWindow=window;
87 final Project project = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(myFocusedWindow));
88 for (Iterator i = myFocusedWindows.iterator(); i.hasNext();) {
89 final Window w = (Window)i.next();
90 final DataContext dataContext = DataManager.getInstance().getDataContext(w);
91 if (project == PlatformDataKeys.PROJECT.getData(dataContext)) {
92 i.remove();
95 myFocusedWindows.add(myFocusedWindow);
96 // Set new root frame
97 final IdeFrameImpl frame;
98 if(window instanceof IdeFrameImpl){
99 frame=(IdeFrameImpl)window;
100 }else{
101 frame=(IdeFrameImpl)SwingUtilities.getAncestorOfClass(IdeFrameImpl.class,window);
103 if(frame!=null){
104 JOptionPane.setRootFrame(frame);
107 if(LOG.isDebugEnabled()){
108 LOG.debug("exit: propertyChange()");
112 final void dispatchComponentEvent(final ComponentEvent e){
113 final int id=e.getID();
114 if(WindowEvent.WINDOW_CLOSED==id||(ComponentEvent.COMPONENT_HIDDEN==id&&e.getSource() instanceof Window)){
115 dispatchHiddenOrClosed((Window)e.getSource());
117 // Clear obsolete reference on root frame
118 if(WindowEvent.WINDOW_CLOSED==id){
119 final Window window=(Window)e.getSource();
120 if(JOptionPane.getRootFrame()==window){
121 JOptionPane.setRootFrame(null);
126 private void dispatchHiddenOrClosed(final Window window){
127 if(LOG.isDebugEnabled()){
128 LOG.debug("enter: dispatchClosed("+window+")");
130 synchronized(myLock){
131 final WindowInfo info=myWindow2Info.get(window);
132 if(info!=null){
133 final FocusWatcher focusWatcher=info.myFocusWatcherRef.get();
134 if(focusWatcher!=null){
135 focusWatcher.deinstall(window);
137 myWindow2Info.remove(window);
140 // Now, we have to recalculate focused window if currently focused
141 // window is being closed.
142 if(myFocusedWindow==window){
143 if(LOG.isDebugEnabled()){
144 LOG.debug("currently active window should be closed");
146 myFocusedWindow=myFocusedWindow.getOwner();
147 if (LOG.isDebugEnabled()) {
148 LOG.debug("new active window is "+myFocusedWindow);
151 for(Iterator i=myFocusedWindows.iterator();i.hasNext();){
152 final Window activeWindow = (Window)i.next();
153 if (activeWindow == window) {
154 final Window newActiveWindow = activeWindow.getOwner();
155 i.remove();
156 if (newActiveWindow != null) {
157 myFocusedWindows.add(newActiveWindow);
159 break;
162 // Remove invalid infos for garbage collected windows
163 for(Iterator i=myWindow2Info.values().iterator();i.hasNext();){
164 final WindowInfo info=(WindowInfo)i.next();
165 if(info.myFocusWatcherRef.get()==null){
166 if (LOG.isDebugEnabled()) {
167 LOG.debug("remove collected info");
169 i.remove();
174 public final Window getFocusedWindow(){
175 synchronized(myLock){
176 return myFocusedWindow;
180 public final Component getFocusedComponent(final Project project){
181 synchronized(myLock){
182 final Window window=getFocusedWindowForProject(project);
183 if(window==null){
184 return null;
186 return getFocusedComponent(window);
191 public final Component getFocusedComponent(@NotNull final Window window){
192 synchronized(myLock){
193 final WindowInfo info=myWindow2Info.get(window);
194 if(info==null){ // it means that we don't manage this window, so just return standard focus owner
195 // return window.getFocusOwner();
196 // TODO[vova,anton] usage of getMostRecentFocusOwner is experimental. But it seems suitable here.
197 return window.getMostRecentFocusOwner();
199 final FocusWatcher focusWatcher=info.myFocusWatcherRef.get();
200 if(focusWatcher!=null){
201 final Component focusedComponent = focusWatcher.getFocusedComponent();
202 if(focusedComponent != null && focusedComponent.isShowing()){
203 return focusedComponent;
205 else{
206 return null;
208 }else{
209 // info isn't valid, i.e. window was garbage collected, so we need the remove invalid info
210 // and return null
211 myWindow2Info.remove(window);
212 return null;
217 @Nullable
218 public FocusWatcher getFocusWatcherFor(Component c) {
219 final Window window = SwingUtilities.getWindowAncestor(c);
220 final WindowInfo info = myWindow2Info.get(window);
221 return info == null ? null : info.myFocusWatcherRef.get();
225 * @param project may be null (for example, if no projects are opened)
227 public final Window suggestParentWindow(final Project project){
228 synchronized(myLock){
229 Window window=getFocusedWindowForProject(project);
230 if(window==null){
231 if (project != null) {
232 return WindowManagerEx.getInstanceEx().getFrame(project);
234 else{
235 return null;
239 LOG.assertTrue(window.isDisplayable());
240 LOG.assertTrue(window.isShowing());
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;
248 // skip not visible and disposed/not shown windows
249 if(!window.isDisplayable()||!window.isShowing()){
250 window = window.getOwner();
251 continue;
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;
259 if(info.mySuggestAsParent){
260 return window;
261 }else{
262 window=window.getOwner();
265 return null;
269 public final void doNotSuggestAsParent(final Window window){
270 if(LOG.isDebugEnabled()){
271 LOG.debug("enter: doNotSuggestAsParent("+window+")");
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;
284 * @return active window for specified <code>project</code>. There is only one window
285 * for project can be at any point of time.
287 private Window getFocusedWindowForProject(final Project project){
288 //todo[anton,vova]: it is possible that returned wnd is not contained in myFocusedWindows; investigate
289 outer: for(Iterator i=myFocusedWindows.iterator();i.hasNext();){
290 Window window=(Window)i.next();
291 while(!window.isDisplayable()||!window.isShowing()){ // if window isn't visible then gets its first visible ancestor
292 window=window.getOwner();
293 if(window==null){
294 continue outer;
297 final DataContext dataContext = DataManager.getInstance().getDataContext(window);
298 if (project == PlatformDataKeys.PROJECT.getData(dataContext)) {
299 return window;
302 return null;
305 private static final class WindowInfo {
306 public final WeakReference<FocusWatcher> myFocusWatcherRef;
307 public boolean mySuggestAsParent;
309 public WindowInfo(final Window window,final boolean suggestAsParent){
310 final FocusWatcher focusWatcher=new FocusWatcher();
311 focusWatcher.install(window);
312 myFocusWatcherRef=new WeakReference<FocusWatcher>(focusWatcher);
313 mySuggestAsParent=suggestAsParent;