blob: c8937d7b0df80215b47f9653f8b094ff996a9dff [file] [log] [blame]
/*
* 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<Window, WindowInfo>();
/**
* 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>(focusWatcher);
mySuggestAsParent=suggestAsParent;
}
}
}