/* * Copyright 2000-2009 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.wm.impl; import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.FocusWatcher; import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.util.containers.WeakHashMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.ComponentEvent; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.Iterator; import java.util.Map; /** * @author Anton Katilin * @author Vladimir Kondratyev */ public final class WindowWatcher implements PropertyChangeListener{ private static final Logger LOG=Logger.getInstance("#com.intellij.openapi.wm.impl.WindowWatcher"); private final Object myLock = new Object(); private final Map<Window, WindowInfo> myWindow2Info = new WeakHashMap<>(); /** * Currenly focused window (window which has focused component). Can be <code>null</code> if there is no focused * window at all. */ private Window myFocusedWindow; /** * Contains last focused window for each project. */ private final HashSet myFocusedWindows = new HashSet(); @NonNls protected static final String FOCUSED_WINDOW_PROPERTY = "focusedWindow"; WindowWatcher() {} /** * This method should get notifications abount changes of focused window. * Only <code>focusedWindow</code> property is acceptable. * @throws IllegalArgumentException if property name isn't <code>focusedWindow</code>. */ public final void propertyChange(final PropertyChangeEvent e){ if(LOG.isDebugEnabled()){ LOG.debug("enter: propertyChange("+e+")"); } if(!FOCUSED_WINDOW_PROPERTY.equals(e.getPropertyName())){ throw new IllegalArgumentException("unknown property name: "+e.getPropertyName()); } synchronized(myLock){ final Window window=(Window)e.getNewValue(); if(window==null || ApplicationManager.getApplication().isDisposed()){ return; } if(!myWindow2Info.containsKey(window)){ myWindow2Info.put(window,new WindowInfo(window, true)); } myFocusedWindow=window; final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(myFocusedWindow)); for (Iterator i = myFocusedWindows.iterator(); i.hasNext();) { final Window w = (Window)i.next(); final DataContext dataContext = DataManager.getInstance().getDataContext(w); if (project == CommonDataKeys.PROJECT.getData(dataContext)) { i.remove(); } } myFocusedWindows.add(myFocusedWindow); // Set new root frame final IdeFrameImpl frame; if(window instanceof IdeFrameImpl){ frame=(IdeFrameImpl)window; }else{ frame=(IdeFrameImpl)SwingUtilities.getAncestorOfClass(IdeFrameImpl.class,window); } if(frame!=null){ JOptionPane.setRootFrame(frame); } } if(LOG.isDebugEnabled()){ LOG.debug("exit: propertyChange()"); } } final void dispatchComponentEvent(final ComponentEvent e){ final int id=e.getID(); if(WindowEvent.WINDOW_CLOSED == id || (ComponentEvent.COMPONENT_HIDDEN == id && e.getSource() instanceof Window)){ dispatchHiddenOrClosed((Window)e.getSource()); } // Clear obsolete reference on root frame if(WindowEvent.WINDOW_CLOSED==id){ final Window window=(Window)e.getSource(); if(JOptionPane.getRootFrame()==window){ JOptionPane.setRootFrame(null); } } } private void dispatchHiddenOrClosed(final Window window){ if(LOG.isDebugEnabled()){ LOG.debug("enter: dispatchClosed("+window+")"); } synchronized(myLock){ final WindowInfo info=myWindow2Info.get(window); if(info!=null){ final FocusWatcher focusWatcher=info.myFocusWatcherRef.get(); if(focusWatcher!=null){ focusWatcher.deinstall(window); } myWindow2Info.remove(window); } } // Now, we have to recalculate focused window if currently focused // window is being closed. if(myFocusedWindow==window){ if(LOG.isDebugEnabled()){ LOG.debug("currently active window should be closed"); } myFocusedWindow=myFocusedWindow.getOwner(); if (LOG.isDebugEnabled()) { LOG.debug("new active window is "+myFocusedWindow); } } for(Iterator i=myFocusedWindows.iterator();i.hasNext();){ final Window activeWindow = (Window)i.next(); if (activeWindow == window) { final Window newActiveWindow = activeWindow.getOwner(); i.remove(); if (newActiveWindow != null) { myFocusedWindows.add(newActiveWindow); } break; } } // Remove invalid infos for garbage collected windows for(Iterator i=myWindow2Info.values().iterator();i.hasNext();){ final WindowInfo info=(WindowInfo)i.next(); if(info.myFocusWatcherRef.get()==null){ if (LOG.isDebugEnabled()) { LOG.debug("remove collected info"); } i.remove(); } } } public final Window getFocusedWindow(){ synchronized(myLock){ return myFocusedWindow; } } @Nullable public final Component getFocusedComponent(@Nullable final Project project) { synchronized(myLock){ final Window window=getFocusedWindowForProject(project); if(window==null){ return null; } return getFocusedComponent(window); } } public final Component getFocusedComponent(@NotNull final Window window){ synchronized(myLock){ final WindowInfo info=myWindow2Info.get(window); if(info==null){ // it means that we don't manage this window, so just return standard focus owner // return window.getFocusOwner(); // TODO[vova,anton] usage of getMostRecentFocusOwner is experimental. But it seems suitable here. return window.getMostRecentFocusOwner(); } final FocusWatcher focusWatcher=info.myFocusWatcherRef.get(); if(focusWatcher!=null){ final Component focusedComponent = focusWatcher.getFocusedComponent(); if(focusedComponent != null && focusedComponent.isShowing()){ return focusedComponent; } else{ return null; } }else{ // info isn't valid, i.e. window was garbage collected, so we need the remove invalid info // and return null myWindow2Info.remove(window); return null; } } } @Nullable public FocusWatcher getFocusWatcherFor(Component c) { final Window window = SwingUtilities.getWindowAncestor(c); final WindowInfo info = myWindow2Info.get(window); return info == null ? null : info.myFocusWatcherRef.get(); } /** * @param project may be null (for example, if no projects are opened) */ @Nullable public final Window suggestParentWindow(@Nullable final Project project){ synchronized(myLock){ Window window=getFocusedWindowForProject(project); if(window==null){ if (project != null) { return (Window)WindowManagerEx.getInstanceEx().findFrameFor(project); } else{ return null; } } LOG.assertTrue(window.isDisplayable()); LOG.assertTrue(window.isShowing()); while(window!=null){ // skip all windows until found forst dialog or frame if(!(window instanceof Dialog)&&!(window instanceof Frame)){ window=window.getOwner(); continue; } // skip not visible and disposed/not shown windows if(!window.isDisplayable()||!window.isShowing()){ window = window.getOwner(); continue; } // skip windows that have not associated WindowInfo final WindowInfo info=myWindow2Info.get(window); if(info==null){ window=window.getOwner(); continue; } if(info.mySuggestAsParent){ return window; }else{ window=window.getOwner(); } } return null; } } public final void doNotSuggestAsParent(final Window window) { if(LOG.isDebugEnabled()){ LOG.debug("enter: doNotSuggestAsParent("+window+")"); } synchronized(myLock){ final WindowInfo info=myWindow2Info.get(window); if(info==null){ myWindow2Info.put(window,new WindowInfo(window, false)); }else{ info.mySuggestAsParent=false; } } } /** * @return active window for specified <code>project</code>. There is only one window * for project can be at any point of time. */ @Nullable private Window getFocusedWindowForProject(@Nullable final Project project) { //todo[anton,vova]: it is possible that returned wnd is not contained in myFocusedWindows; investigate outer: for(Iterator i=myFocusedWindows.iterator();i.hasNext();){ Window window=(Window)i.next(); while(!window.isDisplayable()||!window.isShowing()){ // if window isn't visible then gets its first visible ancestor window=window.getOwner(); if(window==null){ continue outer; } } final DataContext dataContext = DataManager.getInstance().getDataContext(window); if (project == CommonDataKeys.PROJECT.getData(dataContext)) { return window; } } return null; } private static final class WindowInfo { public final WeakReference<FocusWatcher> myFocusWatcherRef; public boolean mySuggestAsParent; public WindowInfo(final Window window,final boolean suggestAsParent){ final FocusWatcher focusWatcher=new FocusWatcher(); focusWatcher.install(window); myFocusWatcherRef= new WeakReference<>(focusWatcher); mySuggestAsParent=suggestAsParent; } } }