/* * Copyright 2000-2015 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.RemoteDesktopService; 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.ui.popup.StackingPopupDispatcher; import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.wm.IdeFocusManager; 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.util.ui.JBUI; 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; /** * @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; private String myTitle; 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 = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); if (!(active instanceof JDialog) && owner instanceof IdeFrame) { Component glassPane; // Not all successor of IdeFrame are frames if (owner instanceof JFrame) { glassPane = ((JFrame)owner).getGlassPane(); } else if (owner instanceof JDialog) { glassPane = ((JDialog)owner).getGlassPane(); } else { throw new IllegalStateException("Cannot find glass pane for " + owner.getClass().getName()); } assert glassPane instanceof IdeGlassPaneEx : "GlassPane should be instance of IdeGlassPane!"; myDialog = new MyDialog((IdeGlassPaneEx) glassPane, myWrapper, myProject); } else { throw new GlasspanePeerUnavailableException(); } } @Override public FocusTrackback getFocusTrackback() { if (myDialog != null) { return myDialog.getFocusTrackback(); } return null; } @Override public void setUndecorated(final boolean undecorated) { LOG.assertTrue(undecorated, "Decorated dialogs are not supported!"); } @Override public void addMouseListener(final MouseListener listener) { throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName()); } @Override public void addMouseListener(final MouseMotionListener listener) { throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName()); } @Override public void addKeyListener(final KeyListener listener) { throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName()); } @Override public void toFront() { throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName()); } @Override public void toBack() { throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName()); } @Override 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; } } @Override public Container getContentPane() { return myDialog.getContentPane(); } @Override public Window getOwner() { throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName()); } @Override public Window getWindow() { return null; } @Override public JRootPane getRootPane() { if (myDialog == null) { return null; } return myDialog.getRootPane(); } @Override public Dimension getSize() { return myDialog.getSize(); } @Override public String getTitle() { return ""; } @Override public Dimension getPreferredSize() { return myDialog.getPreferredSize(); } @Override public void setModal(final boolean modal) { LOG.assertTrue(modal, "Can't be non modal!"); } @Override public boolean isModal() { return true; } @Override public boolean isVisible() { return myDialog != null && myDialog.isVisible(); } @Override public boolean isShowing() { return myDialog != null && myDialog.isShowing(); } @Override public void setSize(final int width, final int height) { myDialog.setSize(width, height); } @Override public void setTitle(final String title) { myTitle = title; } @Override public boolean isResizable() { return false; } @Override public void setResizable(final boolean resizable) { throw new UnsupportedOperationException("Not implemented in " + getClass().getCanonicalName()); } @NotNull @Override public Point getLocation() { return myDialog.getLocation(); } @Override public void setLocation(@NotNull final Point p) { setLocation(p.x, p.y); } @Override 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); } @Override public ActionCallback show() { LOG.assertTrue(EventQueue.isDispatchThread(), "Access is allowed from event dispatch thread only"); hidePopupsIfNeeded(); myDialog.setVisible(true); return ActionCallback.DONE; } @Override public void setContentPane(final JComponent content) { myDialog.setContentPane(content); } @Override public void centerInParent() { if (myDialog != null) { myDialog.center(); } } @Override public void validate() { if (myDialog != null) { myDialog.resetSizeCache(); myDialog.invalidate(); } } @Override public void repaint() { if (myDialog != null) { myDialog.repaint(); } } @Override public void pack() { } @Override 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; StackingPopupDispatcher.getInstance().hidePersistentPopups(); Disposer.register(myDialog, new Disposable() { @Override public void dispose() { StackingPopupDispatcher.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) { IdeFocusManager.getGlobalInstance().doForceFocusWhenFocusSettlesDown(myPreviouslyFocusedComponent); myPreviouslyFocusedComponent = null; } if (myTransparentPane.getComponentCount() == 0) { myTransparentPane.setVisible(false); myPane.remove(myTransparentPane); } } } @Override public void paint(final Graphics g) { UISettings.setupAntialiasing(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 (!RemoteDesktopService.isRemoteSession() && !JBUI.isHiDPI()) { shadow = ShadowBorderPainter.createShadow(this, getWidth(), getHeight()); } } @Override public void dispose() { remove(getContentPane()); repaint(); final Runnable disposer = new Runnable() { @Override 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; } @Override public JRootPane getRootPane() { return myRootPane; } @Override public DialogWrapper getDialogWrapper() { return myDialogWrapper.get(); } @Override 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; } @Override 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); } @Override 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; } @Override 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); } @Override public void setContentPane(Container contentPane) { super.setContentPane(contentPane); if (contentPane != null) { contentPane.addMouseMotionListener(new MouseMotionAdapter() {}); // listen to mouse motion events for a11y } } } 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; } } }