From ec0ce0adf341f856d74fa71adf36df77737575c7 Mon Sep 17 00:00:00 2001 From: Vassiliy Date: Thu, 27 Jun 2013 17:08:36 +0400 Subject: [PATCH] mac sheets emulation --- .../intellij/openapi/ui/DialogWrapper.java | 22 ++- .../src/com/intellij/openapi/ui/Messages.java | 125 ++++++++++++++++++ .../intellij/openapi/wm/WindowManager.java | 2 + .../ui/impl/DialogWrapperPeerImpl.java | 68 ++++++---- .../openapi/wm/ex/WindowManagerEx.java | 8 ++ 5 files changed, 196 insertions(+), 29 deletions(-) diff --git a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java index 9453c135a92d..873fce9e21be 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java @@ -100,6 +100,8 @@ public abstract class DialogWrapper { @NonNls public static final String FOCUSED_ACTION = "FocusedAction"; + @NonNls private static final String NO_AUTORESIZE = "NoAutoResizeAndFit"; + private static final KeyStroke SHOW_OPTION_KEYSTROKE = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.ALT_MASK | InputEvent.SHIFT_MASK); @@ -771,6 +773,18 @@ public abstract class DialogWrapper { myPeer.toBack(); } + protected boolean setAutoAdjustable(boolean autoAdjustable) { + JRootPane rootPane = getRootPane(); + if (rootPane == null) return false; + rootPane.putClientProperty(NO_AUTORESIZE, autoAdjustable? null : Boolean.TRUE); + return true; + } + //true by default + public boolean isAutoAdjustable() { + JRootPane rootPane = getRootPane(); + return rootPane == null || rootPane.getClientProperty(NO_AUTORESIZE) == null; + } + /** * Dispose the wrapped and releases all resources allocated be the wrapper to help * more effecient garbage collection. You should never invoke this method twice or @@ -793,7 +807,7 @@ public abstract class DialogWrapper { // if rootPane = null, dialog has already been disposed if (rootPane != null) { unregisterKeyboardActions(rootPane); - if (myActualSize != null) { + if (myActualSize != null && isAutoAdjustable()) { setSize(myActualSize.width, myActualSize.height); } myPeer.dispose(); @@ -1102,7 +1116,7 @@ public abstract class DialogWrapper { myErrorText = new ErrorText(); myErrorText.setVisible(false); - final JPanel root = new JPanel(new BorderLayout()); + final JPanel root = new JPanel(createRootLayout()); //{ // @Override // public void paint(Graphics g) { @@ -1168,6 +1182,10 @@ public abstract class DialogWrapper { } } + LayoutManager createRootLayout() { + return new BorderLayout(); + } + private static void installEnterHook(JComponent root) { new AnAction() { @Override diff --git a/platform/platform-api/src/com/intellij/openapi/ui/Messages.java b/platform/platform-api/src/com/intellij/openapi/ui/Messages.java index 16898abf3cf9..4ce6c80d60e5 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/Messages.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/Messages.java @@ -18,17 +18,21 @@ package com.intellij.openapi.ui; import com.intellij.CommonBundle; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.WindowManager; import com.intellij.ui.*; import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.mac.MacMessages; +import com.intellij.util.Alarm; import com.intellij.util.Function; import com.intellij.util.PairFunction; import com.intellij.util.execution.ParametersListUtil; @@ -44,8 +48,10 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; public class Messages { public static final int OK = 0; @@ -138,6 +144,10 @@ public class Messages { && !DialogWrapper.isMultipleModalDialogs(); } + public static boolean isMacSheetEmulation() { + return SystemInfo.isMac && !Registry.is("ide.mac.message.dialogs.as.sheets"); + } + public static int showDialog(Project project, String message, String title, String moreInfo, String[] options, int defaultOptionIndex, int focusedOptionIndex, Icon icon) { if (isApplicationInUnitTestOrHeadless()) { return ourTestImplementation.show(message); @@ -944,6 +954,7 @@ public class Messages { protected int myDefaultOptionIndex; protected int myFocusedOptionIndex; protected Icon myIcon; + private MyBorderLayout myLayout; public MessageDialog(@Nullable Project project, String message, String title, String[] options, int defaultOptionIndex, @Nullable Icon icon, boolean canBeParent) { this(project, message, title, options, defaultOptionIndex, -1, icon, canBeParent); @@ -1003,6 +1014,9 @@ public class Messages { protected void _init(String title, String message, String[] options, int defaultOptionIndex, int focusedOptionIndex, @Nullable Icon icon, @Nullable DoNotAskOption doNotAskOption) { setTitle(title); + if (isMacSheetEmulation()) { + setUndecorated(true); + } myMessage = message; myOptions = options; myDefaultOptionIndex = defaultOptionIndex; @@ -1061,6 +1075,90 @@ public class Messages { return doCreateCenterPanel(); } + @Override + LayoutManager createRootLayout() { + return isMacSheetEmulation() ? myLayout = new MyBorderLayout() : super.createRootLayout(); + } + + @Override + protected void dispose() { + if (isMacSheetEmulation()) { + animate(); + } else { + super.dispose(); + } + } + + @Override + public void show() { + if (isMacSheetEmulation()) { + setInitialLocationCallback(new Computable() { + @Override + public Point compute() { + JRootPane rootPane = SwingUtilities.getRootPane(getWindow().getParent()); + if (rootPane == null) { + rootPane = SwingUtilities.getRootPane(getWindow().getOwner()); + } + + Point p = rootPane.getLocationOnScreen(); + p.x += (rootPane.getWidth() - getWindow().getWidth()) / 2; + return p; + } + }); + animate(); + if (SystemInfo.isJavaVersionAtLeast("1.7")) { + try { + Method method = Class.forName("java.awt.Window").getDeclaredMethod("setOpacity", float.class); + if (method != null) method.invoke(getPeer().getWindow(), .8f); + } + catch (Exception ignored) { + } + } + setAutoAdjustable(false); + setSize(getPreferredSize().width, 0);//initial state before animation, zero height + } + super.show(); + } + + private void animate() { + final int height = getPreferredSize().height; + final int frameCount = 10; + final boolean toClose = isShowing(); + + + final AtomicInteger i = new AtomicInteger(-1); + final Alarm animator = new Alarm(myDisposable); + final Runnable runnable = new Runnable() { + @Override + public void run() { + int state = i.addAndGet(1); + + double linearProgress = (double)state / frameCount; + if (toClose) { + linearProgress = 1 - linearProgress; + } + myLayout.myPhase = (1 - Math.cos(Math.PI * linearProgress)) / 2; + Window window = getPeer().getWindow(); + Rectangle bounds = window.getBounds(); + bounds.height = (int)(height * myLayout.myPhase); + + window.setBounds(bounds); + + if (state == 0 && !toClose && window.getOwner() instanceof IdeFrame) { + WindowManager.getInstance().requestUserAttention((IdeFrame)window.getOwner(), true); + } + + if (state < frameCount) { + animator.addRequest(this, 10); + } + else if (toClose) { + MessageDialog.super.dispose(); + } + } + }; + animator.addRequest(runnable, 10, ModalityState.stateForComponent(getRootPane())); + } + protected JComponent doCreateCenterPanel() { JPanel panel = new JPanel(new BorderLayout(15, 0)); if (myIcon != null) { @@ -1105,6 +1203,33 @@ public class Messages { } } + private static class MyBorderLayout extends BorderLayout { + private double myPhase = 0;//it varies from 0 (hidden state) to 1 (fully visible) + + private MyBorderLayout() { + } + + @Override + public void layoutContainer(Container target) { + final Dimension realSize = target.getSize(); + target.setSize(target.getPreferredSize()); + + super.layoutContainer(target); + + target.setSize(realSize); + + synchronized (target.getTreeLock()) { + int yShift = (int)((1 - myPhase) * target.getPreferredSize().height); + Component[] components = target.getComponents(); + for (Component component : components) { + Point point = component.getLocation(); + point.y -= yShift; + component.setLocation(point); + } + } + } + } + public static void installHyperlinkSupport(JTextPane messageComponent) { configureMessagePaneUi(messageComponent, ""); } diff --git a/platform/platform-api/src/com/intellij/openapi/wm/WindowManager.java b/platform/platform-api/src/com/intellij/openapi/wm/WindowManager.java index 74cc611c163a..e4252f104ed1 100644 --- a/platform/platform-api/src/com/intellij/openapi/wm/WindowManager.java +++ b/platform/platform-api/src/com/intellij/openapi/wm/WindowManager.java @@ -127,4 +127,6 @@ public abstract class WindowManager { * @return true if full screen mode is supported in current OS. */ public abstract boolean isFullScreenSupportedInCurrentOS(); + + public abstract void requestUserAttention(@NotNull IdeFrame frame, boolean critical); } diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/impl/DialogWrapperPeerImpl.java b/platform/platform-impl/src/com/intellij/openapi/ui/impl/DialogWrapperPeerImpl.java index 1f93689af18d..b3645a494bf3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/impl/DialogWrapperPeerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/ui/impl/DialogWrapperPeerImpl.java @@ -112,6 +112,15 @@ public class DialogWrapperPeerImpl extends DialogWrapperPeer implements FocusTra window = focusedWindow; } } + if (window == null) { + IdeFrame[] frames = myWindowManager.getAllProjectFrames(); + for (IdeFrame frame : frames) { + if (frame instanceof IdeFrameImpl && ((IdeFrameImpl)frame).isActive()) { + window = (IdeFrameImpl)frame; + break; + } + } + } } Window owner; @@ -648,32 +657,34 @@ public class DialogWrapperPeerImpl extends DialogWrapperPeer implements FocusTra myFocusTrackback = new FocusTrackback(getDialogWrapper(), getParent(), true); final DialogWrapper dialogWrapper = getDialogWrapper(); - - pack(); - - Dimension packedSize = getSize(); - Dimension minSize = getMinimumSize(); - setSize(Math.max(packedSize.width, minSize.width), Math.max(packedSize.height, minSize.height)); - - setSize((int)(getWidth() * dialogWrapper.getHorizontalStretch()), (int)(getHeight() * dialogWrapper.getVerticalStretch())); - - // Restore dialog's size and location - - myDimensionServiceKey = dialogWrapper.getDimensionKey(); + boolean isAutoAdjustable = dialogWrapper.isAutoAdjustable(); Point location = null; + if (isAutoAdjustable) { + pack(); - if (myDimensionServiceKey != null) { - final Project projectGuess = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(this)); - location = DimensionService.getInstance().getLocation(myDimensionServiceKey, projectGuess); - Dimension size = DimensionService.getInstance().getSize(myDimensionServiceKey, projectGuess); - if (size != null) { - myInitialSize = new Dimension(size); - _setSizeForLocation(myInitialSize.width, myInitialSize.height, location); + Dimension packedSize = getSize(); + Dimension minSize = getMinimumSize(); + setSize(Math.max(packedSize.width, minSize.width), Math.max(packedSize.height, minSize.height)); + + setSize((int)(getWidth() * dialogWrapper.getHorizontalStretch()), (int)(getHeight() * dialogWrapper.getVerticalStretch())); + + // Restore dialog's size and location + + myDimensionServiceKey = dialogWrapper.getDimensionKey(); + + if (myDimensionServiceKey != null) { + final Project projectGuess = PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(this)); + location = DimensionService.getInstance().getLocation(myDimensionServiceKey, projectGuess); + Dimension size = DimensionService.getInstance().getSize(myDimensionServiceKey, projectGuess); + if (size != null) { + myInitialSize = new Dimension(size); + _setSizeForLocation(myInitialSize.width, myInitialSize.height, location); + } } - } - if (myInitialSize == null) { - myInitialSize = getSize(); + if (myInitialSize == null) { + myInitialSize = getSize(); + } } if (location == null) { @@ -687,10 +698,11 @@ public class DialogWrapperPeerImpl extends DialogWrapperPeer implements FocusTra setLocationRelativeTo(getOwner()); } - final Rectangle bounds = getBounds(); - ScreenUtil.fitToScreen(bounds); - setBounds(bounds); - + if (isAutoAdjustable) { + final Rectangle bounds = getBounds(); + ScreenUtil.fitToScreen(bounds); + setBounds(bounds); + } addWindowListener(new WindowAdapter() { @Override public void windowActivated(WindowEvent e) { @@ -1052,7 +1064,9 @@ public class DialogWrapperPeerImpl extends DialogWrapperPeer implements FocusTra @Override @SuppressWarnings({"RefusedBequest"}) public void componentResized(ComponentEvent e) { - UIUtil.adjustWindowToMinimumSize(getWindow()); + if (getDialogWrapper().isAutoAdjustable()) { + UIUtil.adjustWindowToMinimumSize(getWindow()); + } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/ex/WindowManagerEx.java b/platform/platform-impl/src/com/intellij/openapi/wm/ex/WindowManagerEx.java index 4adfdb93b319..36dd63951a41 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/ex/WindowManagerEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/ex/WindowManagerEx.java @@ -21,6 +21,7 @@ import com.intellij.openapi.wm.WindowManager; import com.intellij.openapi.wm.impl.CommandProcessor; import com.intellij.openapi.wm.impl.DesktopLayout; import com.intellij.openapi.wm.impl.IdeFrameImpl; +import com.intellij.ui.AppIcon; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,6 +42,13 @@ public abstract class WindowManagerEx extends WindowManager { public abstract IdeFrameImpl getFrame(@Nullable Project project); + @Override + public void requestUserAttention(@NotNull IdeFrame frame, boolean critical) { + Project project = frame.getProject(); + if (project != null) + AppIcon.getInstance().requestAttention(project, critical); + } + public abstract IdeFrameImpl allocateFrame(Project project); public abstract void releaseFrame(IdeFrameImpl frame);