blob: 7e23854309940098b059167e44f0a41ff285e774 [file] [log] [blame]
/*
* Copyright 2000-2013 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.ui;
import com.intellij.ide.DataManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.actionSystem.impl.MouseGestureManager;
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerAdapter;
import com.intellij.openapi.project.ProjectManagerListener;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.DimensionService;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.wm.*;
import com.intellij.openapi.wm.ex.LayoutFocusTraversalPolicyExt;
import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.intellij.openapi.wm.impl.IdeFrameImpl;
import com.intellij.openapi.wm.impl.IdeGlassPaneImpl;
import com.intellij.openapi.wm.impl.IdeMenuBar;
import com.intellij.ui.AppUIUtil;
import com.intellij.ui.BalloonLayout;
import com.intellij.ui.FocusTrackback;
import com.intellij.util.ImageLoader;
import com.intellij.util.containers.HashMap;
import com.intellij.util.ui.UIUtil;
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.*;
import java.io.File;
import java.util.Map;
public class FrameWrapper implements Disposable, DataProvider {
private String myDimensionKey = null;
private JComponent myComponent = null;
private JComponent myPreferedFocus = null;
private String myTitle = "";
private Image myImage = ImageLoader.loadFromResource(ApplicationInfoImpl.getShadowInstance().getIconUrl());
private boolean myCloseOnEsc = false;
private Window myFrame;
private final Map<String, Object> myDatas = new HashMap<String, Object>();
private Project myProject;
private final ProjectManagerListener myProjectListener = new MyProjectManagerListener();
private FocusTrackback myFocusTrackback;
private FocusWatcher myFocusWatcher;
private ActionCallback myFocusedCallback;
private boolean myDisposed;
protected StatusBar myStatusBar;
private boolean myShown;
private boolean myIsDialog;
private boolean myImageWasChanged;
//Skip restoration of MAXIMIZED_BOTH_PROPERTY
private static final boolean WORKAROUND_FOR_JDK_8007219 = SystemInfo.isMac && SystemInfo.isOracleJvm;
public FrameWrapper(Project project) {
this(project, null);
}
public FrameWrapper(Project project, @Nullable @NonNls String dimensionServiceKey) {
this(project, dimensionServiceKey, false);
}
public FrameWrapper(Project project, @Nullable @NonNls String dimensionServiceKey, boolean isDialog) {
myProject = project;
myDimensionKey = dimensionServiceKey;
myIsDialog = isDialog;
}
public void setDimensionKey(String dimensionKey) {
myDimensionKey = dimensionKey;
}
public void setData(String dataId, Object data) {
myDatas.put(dataId, data);
}
public void setProject(@NotNull final Project project) {
myProject = project;
setData(CommonDataKeys.PROJECT.getName(), project);
ProjectManager.getInstance().addProjectManagerListener(project, myProjectListener);
Disposer.register(this, new Disposable() {
@Override
public void dispose() {
ProjectManager.getInstance().removeProjectManagerListener(project, myProjectListener);
}
});
}
public void show() {
show(true);
}
public void show(boolean restoreBounds) {
myFocusedCallback = new ActionCallback();
if (myProject != null) {
IdeFocusManager.getInstance(myProject).typeAheadUntil(myFocusedCallback);
}
final Window frame = getFrame();
if (myStatusBar != null) {
myStatusBar.install((IdeFrame)frame);
}
myFocusTrackback = new FocusTrackback(this, IdeFocusManager.findInstance().getFocusOwner(), true);
if (frame instanceof JFrame) {
((JFrame)frame).setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
} else {
((JDialog)frame).setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}
final WindowAdapter focusListener = new WindowAdapter() {
public void windowOpened(WindowEvent e) {
IdeFocusManager fm = IdeFocusManager.getInstance(myProject);
JComponent toFocus = myPreferedFocus;
if (toFocus == null) {
toFocus = fm.getFocusTargetFor(myComponent);
}
if (toFocus != null) {
fm.requestFocus(toFocus, true).notify(myFocusedCallback);
} else {
myFocusedCallback.setRejected();
}
}
};
frame.addWindowListener(focusListener);
Disposer.register(this, new Disposable() {
@Override
public void dispose() {
frame.removeWindowListener(focusListener);
}
});
if (myCloseOnEsc) addCloseOnEsc((RootPaneContainer)frame);
((RootPaneContainer)frame).getContentPane().add(myComponent, BorderLayout.CENTER);
if (frame instanceof JFrame) {
((JFrame)frame).setTitle(myTitle);
} else {
((JDialog)frame).setTitle(myTitle);
}
if (myImageWasChanged) {
frame.setIconImage(myImage);
}
else {
AppUIUtil.updateWindowIcon(myFrame);
}
if (restoreBounds) {
loadFrameState();
}
myFocusWatcher = new FocusWatcher() {
protected void focusLostImpl(final FocusEvent e) {
myFocusTrackback.consume();
}
};
myFocusWatcher.install(myComponent);
myShown = true;
frame.setVisible(true);
if (UIUtil.isUnderAlloyLookAndFeel() && frame instanceof JFrame) {
//please ask [kb] before remove it
((JFrame)frame).setMaximizedBounds(null);
}
}
public void close() {
Disposer.dispose(this);
}
public void dispose() {
if (isDisposed()) return;
Window frame = getFrame();
final JRootPane rootPane = ((RootPaneContainer)frame).getRootPane();
if (rootPane != null) {
DialogWrapper.unregisterKeyboardActions(rootPane);
}
frame.setVisible(false);
frame.dispose();
if (frame instanceof JFrame) {
FocusTrackback.release((JFrame)frame);
}
if (myStatusBar != null) {
Disposer.dispose(myStatusBar);
myStatusBar = null;
}
myDisposed = true;
}
public boolean isDisposed() {
return myDisposed;
}
private void addCloseOnEsc(final RootPaneContainer frame) {
frame.getRootPane().registerKeyboardAction(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager();
MenuElement[] selectedPath = menuSelectionManager.getSelectedPath();
if (selectedPath.length > 0) { // hide popup menu if any
menuSelectionManager.clearSelectedPath();
}
else {
close();
}
}
},
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW
);
}
public Window getFrame() {
assert !myDisposed : "Already disposed!";
if (myFrame == null) {
final IdeFrame parent = WindowManager.getInstance().getIdeFrame(myProject);
myFrame = myIsDialog ? createJDialog(parent) : createJFrame(parent);
}
return myFrame;
}
protected JFrame createJFrame(IdeFrame parent) {
return new MyJFrame(parent) {
@Override
public IdeRootPaneNorthExtension getNorthExtension(String key) {
return FrameWrapper.this.getNorthExtension(key);
}
};
}
protected JDialog createJDialog(IdeFrame parent) {
return new MyJDialog(parent);
}
protected IdeRootPaneNorthExtension getNorthExtension(String key) {
return null;
}
@Override
public Object getData(@NonNls String dataId) {
return null;
}
public void setComponent(JComponent component) {
myComponent = component;
}
public void setPreferredFocusedComponent(JComponent preferedFocus) {
myPreferedFocus = preferedFocus;
}
public void closeOnEsc() {
myCloseOnEsc = true;
}
public void setImage(Image image) {
myImageWasChanged = true;
myImage = image;
}
protected void loadFrameState() {
final Window frame = getFrame();
final Point location;
final Dimension size;
final int extendedState;
DimensionService dimensionService = DimensionService.getInstance();
if (myDimensionKey == null || dimensionService == null) {
location = null;
size = null;
extendedState = -1;
}
else {
location = dimensionService.getLocation(myDimensionKey);
size = dimensionService.getSize(myDimensionKey);
extendedState = dimensionService.getExtendedState(myDimensionKey);
}
if (size != null && location != null) {
frame.setLocation(location);
frame.setSize(size);
((RootPaneContainer)frame).getRootPane().revalidate();
}
else {
final IdeFrame ideFrame = WindowManagerEx.getInstanceEx().getIdeFrame(myProject);
if (ideFrame != null) {
frame.pack();
frame.setBounds(ideFrame.suggestChildFrameBounds());
}
}
if (!WORKAROUND_FOR_JDK_8007219 && extendedState == Frame.MAXIMIZED_BOTH && frame instanceof JFrame) {
((JFrame)frame).setExtendedState(extendedState);
}
}
private static void saveFrameState(String dimensionKey, Component frame) {
DimensionService dimensionService = DimensionService.getInstance();
if (dimensionKey == null || dimensionService == null) return;
dimensionService.setLocation(dimensionKey, frame.getLocation());
dimensionService.setSize(dimensionKey, frame.getSize());
if (frame instanceof JFrame) {
dimensionService.setExtendedState(dimensionKey, ((JFrame)frame).getExtendedState());
}
}
public void setTitle(String title) {
myTitle = title;
}
public void addDisposable(Disposable disposable) {
Disposer.register(this, disposable);
}
protected void setStatusBar(StatusBar statusBar) {
if (myStatusBar != null) {
Disposer.dispose(myStatusBar);
}
myStatusBar = statusBar;
}
private class MyJFrame extends JFrame implements DataProvider, IdeFrame.Child {
private boolean myDisposing;
private final IdeFrame myParent;
private String myFrameTitle;
private String myFileTitle;
private File myFile;
private MyJFrame(IdeFrame parent) throws HeadlessException {
myParent = parent;
setGlassPane(new IdeGlassPaneImpl(getRootPane()));
if (SystemInfo.isMac) {
setJMenuBar(new IdeMenuBar(ActionManagerEx.getInstanceEx(), DataManager.getInstance()));
}
MouseGestureManager.getInstance().add(this);
setFocusTraversalPolicy(new LayoutFocusTraversalPolicyExt());
}
@Override
public JComponent getComponent() {
return getRootPane();
}
@Override
public StatusBar getStatusBar() {
return myStatusBar != null ? myStatusBar : myParent.getStatusBar();
}
@Override
public Rectangle suggestChildFrameBounds() {
return myParent.suggestChildFrameBounds();
}
@Override
public Project getProject() {
return myParent.getProject();
}
@Override
public void setFrameTitle(String title) {
myFrameTitle = title;
updateTitle();
}
@Override
public void setFileTitle(String fileTitle, File ioFile) {
myFileTitle = fileTitle;
myFile = ioFile;
updateTitle();
}
@Override
public IdeRootPaneNorthExtension getNorthExtension(String key) {
return null;
}
@Override
public BalloonLayout getBalloonLayout() {
return null;
}
private void updateTitle() {
IdeFrameImpl.updateTitle(this, myFrameTitle, myFileTitle, myFile);
}
@Override
public IdeFrame getParentFrame() {
return myParent;
}
public void dispose() {
if (myDisposing) return;
myDisposing = true;
MouseGestureManager.getInstance().remove(this);
if (myShown) {
saveFrameState(myDimensionKey, this);
}
Disposer.dispose(FrameWrapper.this);
myDatas.clear();
myProject = null;
myPreferedFocus = null;
if (myFocusTrackback != null) {
myFocusTrackback.restoreFocus();
}
if (myComponent != null && myFocusWatcher != null) {
myFocusWatcher.deinstall(myComponent);
}
myFocusWatcher = null;
myFocusedCallback = null;
super.dispose();
}
public Object getData(String dataId) {
if (IdeFrame.KEY.getName().equals(dataId)) {
return this;
}
Object data = FrameWrapper.this.getData(dataId);
return data != null ? data : myDatas.get(dataId);
}
@Override
public void paint(Graphics g) {
UIUtil.applyRenderingHints(g);
super.paint(g);
}
}
private class MyJDialog extends JDialog implements DataProvider, IdeFrame.Child {
private boolean myDisposing;
private final IdeFrame myParent;
private MyJDialog(IdeFrame parent) throws HeadlessException {
super((JFrame)parent);
myParent = parent;
setGlassPane(new IdeGlassPaneImpl(getRootPane()));
getRootPane().putClientProperty("Window.style", "small");
setBackground(UIUtil.getPanelBackground());
MouseGestureManager.getInstance().add(this);
setFocusTraversalPolicy(new LayoutFocusTraversalPolicyExt());
}
@Override
public JComponent getComponent() {
return getRootPane();
}
@Override
public StatusBar getStatusBar() {
return null;
}
@Nullable
@Override
public BalloonLayout getBalloonLayout() {
return null;
}
@Override
public Rectangle suggestChildFrameBounds() {
return myParent.suggestChildFrameBounds();
}
@Override
public Project getProject() {
return myParent.getProject();
}
@Override
public void setFrameTitle(String title) {
setTitle(title);
}
@Override
public void setFileTitle(String fileTitle, File ioFile) {
setTitle(fileTitle);
}
@Override
public IdeRootPaneNorthExtension getNorthExtension(String key) {
return null;
}
@Override
public IdeFrame getParentFrame() {
return myParent;
}
public void dispose() {
if (myDisposing) return;
myDisposing = true;
MouseGestureManager.getInstance().remove(this);
if (myShown) {
saveFrameState(myDimensionKey, this);
}
Disposer.dispose(FrameWrapper.this);
myDatas.clear();
myProject = null;
myPreferedFocus = null;
if (myFocusTrackback != null) {
myFocusTrackback.restoreFocus();
}
if (myComponent != null && myFocusWatcher != null) {
myFocusWatcher.deinstall(myComponent);
}
myFocusWatcher = null;
myFocusedCallback = null;
super.dispose();
}
public Object getData(String dataId) {
if (IdeFrame.KEY.getName().equals(dataId)) {
return this;
}
Object data = FrameWrapper.this.getData(dataId);
return data != null ? data : myDatas.get(dataId);
}
@Override
public void paint(Graphics g) {
UIUtil.applyRenderingHints(g);
super.paint(g);
}
}
public void setLocation(Point location) {
getFrame().setLocation(location);
}
public void setSize(Dimension size) {
getFrame().setSize(size);
}
private class MyProjectManagerListener extends ProjectManagerAdapter {
public void projectClosing(Project project) {
if (project == myProject) {
close();
}
}
}
}