blob: b538143773c73db6ffd2a02af2f366cb2a1a5c80 [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.impl;
import com.intellij.ide.DataManager;
import com.intellij.ide.impl.TypeSafeDataProviderAdapter;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.TypeSafeDataProvider;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.DialogWrapperDialog;
import com.intellij.openapi.ui.DialogWrapperPeer;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.intellij.openapi.wm.impl.IdeFrameImpl;
import com.intellij.openapi.wm.impl.IdeGlassPaneEx;
import com.intellij.ui.FocusTrackback;
import com.intellij.ui.ScreenUtil;
import com.intellij.ui.components.JBLayeredPane;
import com.intellij.ui.popup.StackingPopupDispatcherImpl;
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.awt.image.BufferedImage;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* @author spleaner
*/
public class GlassPaneDialogWrapperPeer extends DialogWrapperPeer implements FocusTrackbackProvider {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.ui.impl.GlassPaneDialogWrapperPeer");
private DialogWrapper myWrapper;
private WindowManagerEx myWindowManager;
private Project myProject;
private MyDialog myDialog;
private boolean myCanBeParent;
public GlassPaneDialogWrapperPeer(DialogWrapper wrapper, Project project, boolean canBeParent) throws GlasspanePeerUnavailableException {
myWrapper = wrapper;
myCanBeParent = canBeParent;
myWindowManager = null;
Application application = ApplicationManager.getApplication();
if (application != null && application.hasComponent(WindowManager.class)) {
myWindowManager = (WindowManagerEx) WindowManager.getInstance();
}
Window window = null;
if (myWindowManager != null) {
if (project == null) {
project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext());
}
myProject = project;
window = myWindowManager.suggestParentWindow(project);
if (window == null) {
Window focusedWindow = myWindowManager.getMostRecentFocusedWindow();
if (focusedWindow instanceof IdeFrameImpl) {
window = focusedWindow;
}
}
}
Window owner;
if (window != null) {
owner = window;
} else {
owner = JOptionPane.getRootFrame();
}
createDialog(owner);
}
public GlassPaneDialogWrapperPeer(DialogWrapper wrapper, boolean canBeParent) throws GlasspanePeerUnavailableException {
this(wrapper, (Project) null, canBeParent);
}
public GlassPaneDialogWrapperPeer(DialogWrapper wrapper, @NotNull Component parent, boolean canBeParent)
throws GlasspanePeerUnavailableException {
myWrapper = wrapper;
myCanBeParent = canBeParent;
if (!parent.isShowing() && parent != JOptionPane.getRootFrame()) {
throw new IllegalArgumentException("parent must be showing: " + parent);
}
myWindowManager = null;
Application application = ApplicationManager.getApplication();
if (application != null && application.hasComponent(WindowManager.class)) {
myWindowManager = (WindowManagerEx) WindowManager.getInstance();
}
Window owner = parent instanceof Window ? (Window) parent : (Window) SwingUtilities.getAncestorOfClass(Window.class, parent);
if (!(owner instanceof Dialog) && !(owner instanceof Frame)) {
owner = JOptionPane.getRootFrame();
}
createDialog(owner);
}
private void createDialog(final Window owner) throws GlasspanePeerUnavailableException {
Window active = DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
if (!(active instanceof JDialog) && owner instanceof IdeFrame) {
final JFrame frame = (JFrame) owner;
final JComponent glassPane = (JComponent) frame.getGlassPane();
assert glassPane instanceof IdeGlassPaneEx : "GlassPane should be instance of IdeGlassPane!";
myDialog = new MyDialog((IdeGlassPaneEx) glassPane, myWrapper, myProject);
} else {
throw new GlasspanePeerUnavailableException();
}
}
public FocusTrackback getFocusTrackback() {
if (myDialog != null) {
return myDialog.getFocusTrackback();
}
return null;
}
public void setUndecorated(final boolean undecorated) {
LOG.assertTrue(undecorated, "Decorated dialogs are not supported!");
}
public void addMouseListener(final MouseListener listener) {
throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName());
}
public void addMouseListener(final MouseMotionListener listener) {
throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName());
}
public void addKeyListener(final KeyListener listener) {
throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName());
}
public void toFront() {
throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName());
}
public void toBack() {
throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName());
}
public void dispose() {
LOG.assertTrue(EventQueue.isDispatchThread(), "Access is allowed from event dispatch thread only");
if (myDialog != null) {
Disposer.dispose(myDialog);
myDialog = null;
myProject = null;
myWindowManager = null;
}
}
public Container getContentPane() {
return myDialog.getContentPane();
}
public Window getOwner() {
throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName());
}
public Window getWindow() {
return null;
}
public JRootPane getRootPane() {
if (myDialog == null) {
return null;
}
return myDialog.getRootPane();
}
public Dimension getSize() {
return myDialog.getSize();
}
public String getTitle() {
return "";
}
public Dimension getPreferredSize() {
return myDialog.getPreferredSize();
}
public void setModal(final boolean modal) {
LOG.assertTrue(modal, "Can't be non modal!");
}
@Override
public boolean isModal() {
return true;
}
public boolean isVisible() {
return myDialog != null && myDialog.isVisible();
}
public boolean isShowing() {
return myDialog != null && myDialog.isShowing();
}
public void setSize(final int width, final int height) {
myDialog.setSize(width, height);
}
public void setTitle(final String title) {
throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName());
}
// TODO: WTF?! VOID?!!!
public void isResizable() {
}
public void setResizable(final boolean resizable) {
throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName());
}
public Point getLocation() {
return myDialog.getLocation();
}
public void setLocation(final Point p) {
setLocation(p.x, p.y);
}
public void setLocation(final int x, final int y) {
if (myDialog == null || !myDialog.isShowing()) {
return;
}
final Point _p = new Point(x, y);
final JRootPane pane = SwingUtilities.getRootPane(myDialog);
SwingUtilities.convertPointFromScreen(_p, pane);
final Insets insets = myDialog.getInsets();
// todo: fix coords to include shadow (border) paddings
// todo: reimplement dragging in every client to calculate window position properly
int _x = _p.x - insets.left;
int _y = _p.y - insets.top;
final Container container = myDialog.getTransparentPane();
_x = _x > 0 ? (_x + myDialog.getWidth() < container.getWidth() ? _x : container.getWidth() - myDialog.getWidth()) : 0;
_y = _y > 0 ? (_y + myDialog.getHeight() < container.getHeight() ? _y : container.getHeight() - myDialog.getHeight()) : 0;
myDialog.setLocation(_x, _y);
}
public ActionCallback show() {
LOG.assertTrue(EventQueue.isDispatchThread(), "Access is allowed from event dispatch thread only");
hidePopupsIfNeeded();
myDialog.setVisible(true);
return new ActionCallback.Done();
}
public void setContentPane(final JComponent content) {
myDialog.setContentPane(content);
}
public void centerInParent() {
if (myDialog != null) {
myDialog.center();
}
}
public void validate() {
if (myDialog != null) {
myDialog.resetSizeCache();
myDialog.invalidate();
}
}
public void repaint() {
if (myDialog != null) {
myDialog.repaint();
}
}
public void pack() {
}
public void setIconImages(final List<Image> image) {
throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName());
}
public void setAppIcons() {
throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName());
}
@Override
public boolean isHeadless() {
return DialogWrapperPeerImpl.isHeadlessEnv();
}
//[kirillk] for now it only deals with the TaskWindow under Mac OS X: modal dialogs are shown behind JBPopup
//hopefully this whole code will go away
private void hidePopupsIfNeeded() {
if (!SystemInfo.isMac) return;
StackingPopupDispatcherImpl.getInstance().hidePersistentPopups();
Disposer.register(myDialog, new Disposable() {
public void dispose() {
StackingPopupDispatcherImpl.getInstance().restorePersistentPopups();
}
});
}
private static class MyDialog extends JPanel implements Disposable, DialogWrapperDialog, DataProvider, FocusTrackback.Provider {
private final WeakReference<DialogWrapper> myDialogWrapper;
private final IdeGlassPaneEx myPane;
private JComponent myContentPane;
private MyRootPane myRootPane;
private BufferedImage shadow;
private final JLayeredPane myTransparentPane;
private JButton myDefaultButton;
private Dimension myShadowSize = null;
private final Container myWrapperPane;
private Component myPreviouslyFocusedComponent;
private Dimension myCachedSize = null;
private MyDialog(IdeGlassPaneEx pane, DialogWrapper wrapper, Project project) {
setLayout(new BorderLayout());
setOpaque(false);
setBorder(BorderFactory.createEmptyBorder(ShadowBorderPainter.TOP_SIZE, ShadowBorderPainter.SIDE_SIZE,
ShadowBorderPainter.BOTTOM_SIZE, ShadowBorderPainter.SIDE_SIZE));
myPane = pane;
myDialogWrapper = new WeakReference<DialogWrapper>(wrapper);
// myProject = new WeakReference<Project>(project);
myRootPane = new MyRootPane(this); // be careful with DialogWrapper.dispose()!
Disposer.register(this, myRootPane);
myContentPane = new JPanel();
myContentPane.setOpaque(true);
add(myContentPane, BorderLayout.CENTER);
myTransparentPane = createTransparentPane();
myWrapperPane = createWrapperPane();
myWrapperPane.add(this);
setFocusCycleRoot(true);
}
public void resetSizeCache() {
myCachedSize = null;
}
private Container createWrapperPane() {
final JPanel result = new JPanel() {
@Override
public void doLayout() {
synchronized (getTreeLock()) {
final Container container = getParent();
if (container != null) {
final Component[] components = getComponents();
LOG.assertTrue(components.length == 1);
for (Component c : components) {
Point location;
if (myCachedSize == null) {
myCachedSize = c.getPreferredSize();
location = getLocationInCenter(myCachedSize, c.getLocation());
} else {
location = c.getLocation();
}
final double _width = myCachedSize.getWidth();
final double _height = myCachedSize.getHeight();
final DialogWrapper dialogWrapper = myDialogWrapper.get();
if (dialogWrapper != null) {
final int width = (int) (_width * dialogWrapper.getHorizontalStretch());
final int height = (int) (_height * dialogWrapper.getVerticalStretch());
c.setBounds((int) location.getX(), (int) location.getY(), width, height);
} else {
c.setBounds((int) location.getX(), (int) location.getY(), (int) _width, (int) _height);
}
}
}
}
super.doLayout();
}
};
result.setLayout(null);
result.setOpaque(false);
// to not pass events through transparent pane
result.addMouseListener(new MouseAdapter() {
});
result.addMouseMotionListener(new MouseMotionAdapter() {
});
return result;
}
public Container getTransparentPane() {
return myTransparentPane;
}
private TransparentLayeredPane getExistingTransparentPane() {
for (int i = 0; i < myPane.getComponentCount(); i++) {
Component c = myPane.getComponent(i);
if (c instanceof TransparentLayeredPane) {
return (TransparentLayeredPane) c;
}
}
return null;
}
private boolean isTransparentPaneExist() {
for (int i = 0; i < myPane.getComponentCount(); i++) {
Component c = myPane.getComponent(i);
if (c instanceof TransparentLayeredPane) {
return true;
}
}
return false;
}
@Override
public void setVisible(final boolean show) {
if (show) {
if (!isTransparentPaneExist()) {
myPane.add(myTransparentPane);
} else {
myPreviouslyFocusedComponent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
}
myTransparentPane.add(myWrapperPane);
myTransparentPane.setLayer(myWrapperPane, myTransparentPane.getComponentCount() - 1);
if (!myTransparentPane.isVisible()) {
myTransparentPane.setVisible(true);
}
}
super.setVisible(show);
if (show) {
myTransparentPane.revalidate();
myTransparentPane.repaint();
} else {
myTransparentPane.remove(myWrapperPane);
myTransparentPane.revalidate();
myTransparentPane.repaint();
if (myPreviouslyFocusedComponent != null) {
myPreviouslyFocusedComponent.requestFocus();
myPreviouslyFocusedComponent = null;
}
if (myTransparentPane.getComponentCount() == 0) {
myTransparentPane.setVisible(false);
myPane.remove(myTransparentPane);
}
}
}
@Override
public void paint(final Graphics g) {
UIUtil.applyRenderingHints(g);
super.paint(g);
}
private JLayeredPane createTransparentPane() {
JLayeredPane pane = getExistingTransparentPane();
if (pane == null) {
pane = new TransparentLayeredPane();
}
return pane;
}
@Override
protected void paintComponent(final Graphics g) {
final Graphics2D g2 = (Graphics2D) g;
if (shadow != null) {
UIUtil.drawImage(g2, shadow, 0, 0, null);
}
super.paintComponent(g);
}
@Override
public void setBounds(final int x, final int y, final int width, final int height) {
super.setBounds(x, y, width, height);
if (myShadowSize == null || !myShadowSize.equals(getSize())) {
createShadow();
myShadowSize = getSize();
}
}
@Override
public void setLocation(final int x, final int y) {
final Container p = myTransparentPane;
if (p != null) {
final Dimension s = p.getSize();
final int _x = (int) (x + getWidth() > s.getWidth() ? s.getWidth() - getWidth() : x);
final int _y = (int) (y + getHeight() > s.getHeight() ? s.getHeight() - getHeight() : y);
super.setLocation(_x, _y);
} else {
super.setLocation(x, y);
}
}
private void createShadow() {
if (!UISettings.isRemoteDesktopConnected()) {
shadow = ShadowBorderPainter.createShadow(this, getWidth(), getHeight());
}
}
public void dispose() {
remove(getContentPane());
repaint();
final Runnable disposer = new Runnable() {
public void run() {
setVisible(false);
}
};
if (EventQueue.isDispatchThread()) {
disposer.run();
} else {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(disposer);
}
myRootPane = null;
}
public void setContentPane(JComponent content) {
if (myContentPane != null) {
remove(myContentPane);
myContentPane = null;
}
myContentPane = content;
myContentPane.setOpaque(true); // should be opaque
add(myContentPane, BorderLayout.CENTER);
}
public JComponent getContentPane() {
return myContentPane;
}
public JRootPane getRootPane() {
return myRootPane;
}
public DialogWrapper getDialogWrapper() {
return myDialogWrapper.get();
}
public Object getData(@NonNls final String dataId) {
final DialogWrapper wrapper = myDialogWrapper.get();
if (wrapper instanceof DataProvider) {
return ((DataProvider) wrapper).getData(dataId);
} else if (wrapper instanceof TypeSafeDataProvider) {
TypeSafeDataProviderAdapter adapter = new TypeSafeDataProviderAdapter((TypeSafeDataProvider) wrapper);
return adapter.getData(dataId);
}
return null;
}
public void setSize(int width, int height) {
Point location = getLocation();
Rectangle rect = new Rectangle(location.x, location.y, width, height);
ScreenUtil.fitToScreen(rect);
if (location.x != rect.x || location.y != rect.y) {
setLocation(rect.x, rect.y);
}
super.setSize(rect.width, rect.height);
}
public FocusTrackback getFocusTrackback() {
return null;
}
@Nullable
private Point getLocationInCenter(Dimension size, @Nullable Point _default) {
if (myTransparentPane != null) {
final Dimension d = myTransparentPane.getSize();
return new Point((d.width - size.width) / 2, (d.height - size.height) / 2);
}
return _default;
}
public void center() {
final Point location = getLocationInCenter(getSize(), null);
if (location != null) {
setLocation(location);
repaint();
}
}
public void setDefaultButton(final JButton defaultButton) {
//((JComponent)myPane).getRootPane().setDefaultButton(defaultButton);
myDefaultButton = defaultButton;
}
}
private static class MyRootPane extends JRootPane implements Disposable {
private MyDialog myDialog;
private MyRootPane(final MyDialog dialog) {
myDialog = dialog;
}
protected JLayeredPane createLayeredPane() {
JLayeredPane p = new JBLayeredPane();
p.setName(this.getName()+".layeredPane");
return p;
}
@Override
public void dispose() {
myDialog = null;
}
@Override
public void registerKeyboardAction(final ActionListener anAction,
final String aCommand,
final KeyStroke aKeyStroke,
final int aCondition) {
myDialog.registerKeyboardAction(anAction, aCommand, aKeyStroke, aCondition);
}
@Override
public void unregisterKeyboardAction(final KeyStroke aKeyStroke) {
myDialog.unregisterKeyboardAction(aKeyStroke);
}
@Override
public void setDefaultButton(final JButton defaultButton) {
myDialog.setDefaultButton(defaultButton);
}
}
public static class GlasspanePeerUnavailableException extends Exception {
}
public static class TransparentLayeredPane extends JBLayeredPane {
private TransparentLayeredPane() {
setLayout(new BorderLayout());
setOpaque(false);
// to not pass events through transparent pane
addMouseListener(new MouseAdapter() {
});
addMouseMotionListener(new MouseMotionAdapter() {
});
}
@Override
public void addNotify() {
final Container container = getParent();
if (container != null) {
setBounds(0, 0, container.getWidth(), container.getHeight());
}
super.addNotify();
}
@Override
public boolean isOptimizedDrawingEnabled() {
return getComponentCount() <= 1;
}
}
}