From 324d12891c1930df21fb461822b8b9b71f107bd2 Mon Sep 17 00:00:00 2001 From: Denis Fokin Date: Fri, 24 May 2013 19:53:47 +0400 Subject: [PATCH] IDEA-107366 Messages.show... does not add an entity to modality state --- .../TestMacMessagesSequencesAction.java | 40 ++ .../com/intellij/ui/mac/MacMessagesImpl.java | 584 +++++++++--------- .../com/intellij/ui/mac/MacMessagesQueue.java | 49 ++ .../src/idea/PlatformActions.xml | 16 + 4 files changed, 400 insertions(+), 289 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/internal/validation/TestMacMessagesSequencesAction.java create mode 100644 platform/platform-impl/src/com/intellij/ui/mac/MacMessagesQueue.java diff --git a/platform/platform-impl/src/com/intellij/internal/validation/TestMacMessagesSequencesAction.java b/platform/platform-impl/src/com/intellij/internal/validation/TestMacMessagesSequencesAction.java new file mode 100644 index 000000000000..1d4392d799c6 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/internal/validation/TestMacMessagesSequencesAction.java @@ -0,0 +1,40 @@ +/* + * 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.internal.validation; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.ui.Messages; + +/** + * @author Denis Fokin + */ +public class TestMacMessagesSequencesAction extends AnAction { + + @Override + public void actionPerformed(AnActionEvent e) { + for (int i=0;i<10;i++){ + final int k = i; + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + Messages.showDialog("Message # " + k, "Title of " + k, new String[] { "Option one", "Option two" }, 0, Messages.getQuestionIcon()); + } + }); + } + } + +} diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesImpl.java b/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesImpl.java index 6f7a2ffcd7f9..179f19a637a4 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * 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. @@ -17,7 +17,6 @@ package com.intellij.ui.mac; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.IdeFocusManager; @@ -26,13 +25,16 @@ import com.intellij.ui.FocusTrackback; import com.intellij.ui.mac.foundation.Foundation; import com.intellij.ui.mac.foundation.ID; import com.intellij.ui.mac.foundation.MacUtil; -import com.intellij.util.PairFunction; import com.intellij.util.ui.UIUtil; import com.sun.jna.Callback; import org.jetbrains.annotations.Nullable; +import sun.awt.SunToolkit; import javax.swing.*; import java.awt.*; +import java.awt.event.InputEvent; +import java.lang.reflect.Method; +import java.util.HashMap; import static com.intellij.ui.mac.foundation.Foundation.*; @@ -42,34 +44,19 @@ import static com.intellij.ui.mac.foundation.Foundation.*; public class MacMessagesImpl extends MacMessages { private static final Logger LOG = Logger.getInstance("#com.intellij.ui.mac.MacMessages"); + private static final HashMap resultsFromDocumentRoot = new HashMap (); + private static final HashMap> queuesFromDocumentRoot = + new HashMap>(); + private static final Callback SHEET_DID_END = new Callback() { public void callback(ID self, String selector, ID alert, ID returnCode, ID contextInfo) { - String fakeDialogTitle = toStringViaUTF8(contextInfo); - Window[] windows = Window.getWindows(); - - ID suppressState = invoke(invoke(alert, "suppressionButton"), "state"); - - for (Window window : windows) { - if (window instanceof JFrame) { - JFrame frame = (JFrame)window; - JRootPane rootPane = frame.getRootPane(); - if (rootPane.getClientProperty(MacUtil.MAC_NATIVE_WINDOW_SHOWING) == Boolean.TRUE && - fakeDialogTitle.equals(rootPane.getClientProperty(MAC_SHEET_ID))) { - processResult(rootPane, returnCode.intValue(), suppressState.intValue()); - break; - } - } - else if (window instanceof JDialog) { - JDialog dialog = (JDialog)window; - JRootPane rootPane = dialog.getRootPane(); - if (rootPane.getClientProperty(MacUtil.MAC_NATIVE_WINDOW_SHOWING) == Boolean.TRUE && - fakeDialogTitle.equals(rootPane.getClientProperty(MAC_SHEET_ID))) { - processResult(rootPane, returnCode.intValue(), suppressState.intValue()); - } - } - } - cfRelease(self); + synchronized (lock) { + Window documentRoot = windowFromId.get(contextInfo.longValue()); + processResult(documentRoot); + resultsFromDocumentRoot.put(documentRoot, returnCode.intValue()); + queuesFromDocumentRoot.get(windowFromId.get(contextInfo.longValue())).runFromQueue(); + } } }; @@ -78,7 +65,6 @@ public class MacMessagesImpl extends MacMessages { ID title = invoke(params, "objectAtIndex:", 0); ID message = invoke(params, "objectAtIndex:", 1); ID focusedWindow = invoke(params, "objectAtIndex:", 2); - ID fakeId = invoke(params, "objectAtIndex:", 3); ID alertStyle = invoke(params, "objectAtIndex:", 4); ID doNotAskText = invoke(params, "objectAtIndex:", 5); int defaultOptionIndex = Integer.parseInt(toStringViaUTF8(invoke(params, "objectAtIndex:", 6))); @@ -90,11 +76,11 @@ public class MacMessagesImpl extends MacMessages { invoke(alert, "setMessageText:", title); invoke(alert, "setInformativeText:", message); - + if ("error".equals(toStringViaUTF8(alertStyle))) { invoke(alert, "setAlertStyle:", 2); // NSCriticalAlertStyle = 2 } - + final ID buttonEnumerator = invoke(buttons, "objectEnumerator"); while (true) { final ID button = invoke(buttonEnumerator, "nextObject"); @@ -103,20 +89,20 @@ public class MacMessagesImpl extends MacMessages { } if (defaultOptionIndex != -1) { - invoke(invoke(alert, "window"), "setDefaultButtonCell:", + invoke(invoke(alert, "window"), "setDefaultButtonCell:", invoke(invoke(invoke(alert, "buttons"), "objectAtIndex:", defaultOptionIndex), "cell")); } - + // it seems like asking for focus will cause java to go and query focus owner too, which may cause dead locks on main-thread //if (focusedOptionIndex != -1) { - // invoke(invoke(alert, "window"), "makeFirstResponder:", + // invoke(invoke(alert, "window"), "makeFirstResponder:", // invoke(invoke(alert, "buttons"), "objectAtIndex:", focusedOptionIndex)); //} else { // int count = invoke(buttons, "count").intValue(); - // invoke(invoke(alert, "window"), "makeFirstResponder:", + // invoke(invoke(alert, "window"), "makeFirstResponder:", // invoke(invoke(alert, "buttons"), "objectAtIndex:", count == 1 ? 0 : 1)); //} - + String doNotAsk = toStringViaUTF8(doNotAskText); if (!"-1".equals(doNotAsk)) { invoke(alert, "setShowsSuppressionButton:", 1); @@ -125,7 +111,7 @@ public class MacMessagesImpl extends MacMessages { } invoke(alert, "beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:", focusedWindow, self, - createSelector("alertDidEnd:returnCode:contextInfo:"), fakeId); + createSelector("alertDidEnd:returnCode:contextInfo:"), focusedWindow); } }; @@ -137,7 +123,6 @@ public class MacMessagesImpl extends MacMessages { ID alternateText = invoke(params, "objectAtIndex:", 3); ID message = invoke(params, "objectAtIndex:", 4); ID focusedWindow = invoke(params, "objectAtIndex:", 5); - ID fakeId = invoke(params, "objectAtIndex:", 6); ID alertStyle = invoke(params, "objectAtIndex:", 7); ID doNotAskText = invoke(params, "objectAtIndex:", 8); ID doNotAskChecked = invoke(params, "objectAtIndex:", 9); @@ -154,15 +139,14 @@ public class MacMessagesImpl extends MacMessages { // it seems like asking for focus will cause java to go and query focus owner too, which may cause dead locks on main-thread //ID window = invoke(alert, "window"); - //invoke(window, "makeFirstResponder:", + //invoke(window, "makeFirstResponder:", // invoke(invoke(alert, "buttons"), "objectAtIndex:", alternateExist ? 2 : otherExist ? 1 : 0)); - - - // it is impossible to override ESCAPE key behavior -> key should be named "Cancel" to be bound to ESC + // + ////it is impossible to override ESCAPE key behavior -> key should be named "Cancel" to be bound to ESC //if (!alternateExist) { - //invoke(invoke(invoke(alert, "buttons"), "objectAtIndex:", 1), "setKeyEquivalent:", nsString("\\e")); + // invoke(invoke(invoke(alert, "buttons"), "objectAtIndex:", 1), "setKeyEquivalent:", nsString("\\e")); //} - + String doNotAsk = toStringViaUTF8(doNotAskText); if (!"-1".equals(doNotAsk)) { invoke(alert, "setShowsSuppressionButton:", 1); @@ -171,23 +155,33 @@ public class MacMessagesImpl extends MacMessages { } invoke(alert, "beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:", focusedWindow, self, - createSelector("alertDidEnd:returnCode:contextInfo:"), fakeId); + createSelector("alertDidEnd:returnCode:contextInfo:"), focusedWindow); } }; - private static void processResult(JRootPane rootPane, int returnCode, int suppressDialog) { - rootPane.putClientProperty(MAC_SHEET_RESULT, returnCode); - rootPane.putClientProperty(MAC_SHEET_SUPPRESS, suppressDialog == 1 ? Boolean.TRUE : Boolean.FALSE); - rootPane.putClientProperty(MAC_SHEET_ID, null); - rootPane.putClientProperty(MacUtil.MAC_NATIVE_WINDOW_SHOWING, null); + private static void processResult(Window w) { + synchronized (lock) { + if (!blockedDocumentRoots.keySet().contains(w)) { + throw new RuntimeException("Window should be in th list."); + } + + int openedSheetsForWindow = blockedDocumentRoots.get(w); + + if (openedSheetsForWindow < 1) { + throw new RuntimeException("We should have at least one window in the list"); + } + + if (openedSheetsForWindow == 1) { + // The last sheet + blockedDocumentRoots.remove(w); + } else { + blockedDocumentRoots.put(w, openedSheetsForWindow - 1); + } + + } } - private static final String MAC_SHEET_RESULT = "mac_sheet_result"; - private static final String MAC_SHEET_SUPPRESS = "mac_sheet_suppress"; - private static final String MAC_SHEET_ID = "mac_sheet_id"; - - private MacMessagesImpl() { - } + private MacMessagesImpl() {} static { if (SystemInfo.isMac) { @@ -242,165 +236,174 @@ public class MacMessagesImpl extends MacMessages { return showAlertDialog(title, defaultButton, alternateButton, otherButton, message, window, false, doNotAskOption); } - public int showMessageDialog(final String title, final String message, final String[] buttons, final boolean errorStyle, - @Nullable Window window, final int defaultOptionIndex, - final int focusedOptionIndex, @Nullable final DialogWrapper.DoNotAskOption doNotAskDialogOption) { - return doForWindowAndTitle(new PairFunction, JRootPane, Integer>() { - @Override - public Integer fun(Pair windowAndTitle, JRootPane pane) { - String _windowTitle = windowAndTitle.getSecond(); - Window _window = windowAndTitle.getFirst(); - final ID focusedWindow = MacUtil.findWindowForTitle(_windowTitle); - if (focusedWindow != null) { - String fakeTitle = null; - final FocusTrackback[] focusTrackback = {new FocusTrackback(new Object(), _window, true)}; - ID pool = invoke("NSAutoreleasePool", "new"); - try { - final ID delegate = invoke(Foundation.getObjcClass("NSAlertDelegate_"), "new"); - cfRetain(delegate); + final private static Object lock = new Object(); - fakeTitle = String.format("MacSheetDialog-%d", delegate.intValue()); + final private static HashMap blockedDocumentRoots = new HashMap(); - final ID buttonsArray = invoke("NSMutableArray", "array"); - for (String s : buttons) { - ID s1 = nsString(UIUtil.removeMnemonic(s)); - invoke(buttonsArray, "addObject:", s1); - cfRelease(s1); + final private static HashMap windowFromId = new HashMap(); + + public static void pumpEventsDocumentExclusively (Window documentRoot) { + + Integer messageNumber = blockedDocumentRoots.get(documentRoot); + + EventQueue theQueue = documentRoot.getToolkit().getSystemEventQueue(); + + AWTEvent event; + do { + try { + event = theQueue.getNextEvent(); + boolean eventOk = true; + if (event instanceof InputEvent) { + final Object s = event.getSource(); + if (s instanceof Component) { + Component c = (Component)s; + + Window w = findDocumentRoot(c); + if (w == documentRoot) { + eventOk = false; + ((InputEvent)event).consume(); } + } + } - ID paramsArray = invoke("NSArray", "arrayWithObjects:", nsString(title), + if (eventOk) { + Class[] paramString = new Class[1]; + paramString[0] = AWTEvent.class; + Method method = theQueue.getClass().getDeclaredMethod("dispatchEvent",paramString); + method.setAccessible(true); + method.invoke(theQueue, event); + } + } + catch (Throwable e) { + LOG.error(e); + } + } + while (isBlockedDocumentRoot(documentRoot, messageNumber)); + } + + private static boolean isBlockedDocumentRoot(Window documentRoot, Integer messageNumber) { + synchronized (lock) { + return messageNumber.equals(blockedDocumentRoots.get(documentRoot)); + } + } + + + private static Window findDocumentRoot (final Component c) { + Window w = SunToolkit.getContainingWindow(c); + synchronized (c.getTreeLock()) { + while (w.getOwner() != null) { + w = w.getOwner(); + } + } + return w; + } + + private static void startModal(final Window w, ID windowId) { + synchronized (lock) { + windowFromId.put(windowId.longValue(), w); + if (blockedDocumentRoots.keySet().contains(w)) { + blockedDocumentRoots.put(w, blockedDocumentRoots.get(w) + 1); + } else { + blockedDocumentRoots.put(w, 1); + } + } + + pumpEventsDocumentExclusively(w); + } + + + public int showMessageDialog(final String title, final String message, final String[] buttons, final boolean errorStyle, + @Nullable Window window, final int defaultOptionIndex, + final int focusedOptionIndex, @Nullable final DialogWrapper.DoNotAskOption doNotAskDialogOption) { + + + Window documentRoot = getDocumetnRootFromWindow(window); + String documentRootTitle; + + documentRootTitle = getdocumentRootTitle(documentRoot); + + final ID focusedWindow = MacUtil.findWindowForTitle(documentRootTitle); + + if (focusedWindow != null) { + + final FocusTrackback[] focusTrackback = {new FocusTrackback(new Object(), documentRoot, true)}; + + final ID delegate = invoke(Foundation.getObjcClass("NSAlertDelegate_"), "new"); + invoke(delegate, "autorelease"); + cfRetain(delegate); + + final ID buttonsArray = invoke("NSMutableArray", "array"); + for (String s : buttons) { + ID s1 = nsString(UIUtil.removeMnemonic(s)); + invoke(buttonsArray, "addObject:", s1); + cfRelease(s1); + } + + final ID paramsArray = invoke("NSArray", "arrayWithObjects:", nsString(title), // replace % -> %% to avoid formatted parameters (causes SIGTERM) nsString(StringUtil.stripHtml(message == null ? "" : message, true).replace("%", "%%")), - focusedWindow, nsString(fakeTitle), nsString(errorStyle ? "error" : "-1"), + focusedWindow, nsString(""), nsString(errorStyle ? "error" : "-1"), nsString(doNotAskDialogOption == null || !doNotAskDialogOption.canBeHidden() // TODO: state=!doNotAsk.shouldBeShown() ? "-1" - : doNotAskDialogOption.getDoNotShowMessage()), - nsString(Integer.toString(defaultOptionIndex)), + : doNotAskDialogOption.getDoNotShowMessage()), + nsString(Integer.toString(defaultOptionIndex)), nsString(Integer.toString(focusedOptionIndex)), buttonsArray, nsString(doNotAskDialogOption != null && !doNotAskDialogOption.isToBeShown() ? "checked" : "-1"), null); - IdeFocusManager.getGlobalInstance().setTypeaheadEnabled(false); + IdeFocusManager.getGlobalInstance().setTypeaheadEnabled(false); - invoke(delegate, "performSelectorOnMainThread:withObject:waitUntilDone:", - Foundation.createSelector("showVariableButtonsSheet:"), paramsArray, false); - } - finally { - invoke(pool, "release"); - } - - if (fakeTitle != null) { - pane.putClientProperty(MacUtil.MAC_NATIVE_WINDOW_SHOWING, Boolean.TRUE); - pane.putClientProperty(MAC_SHEET_ID, fakeTitle); - - MacUtil.startModal(pane); - - IdeFocusManager.getGlobalInstance().setTypeaheadEnabled(true); - - Integer code = (Integer)pane.getClientProperty(MAC_SHEET_RESULT) - 1000; // see NSAlertFirstButtonReturn for more info - boolean suppress = Boolean.TRUE == pane.getClientProperty(MAC_SHEET_SUPPRESS); - - final int cancelCode = buttons.length - 1; - - if (doNotAskDialogOption != null && doNotAskDialogOption.canBeHidden()) { - if (cancelCode != code || doNotAskDialogOption.shouldSaveOptionsOnCancel()) { - doNotAskDialogOption.setToBeShown(!suppress, code); - } - } - - pane.putClientProperty(MAC_SHEET_RESULT, null); - pane.putClientProperty(MAC_SHEET_SUPPRESS, null); - - if (focusTrackback[0] != null && - !(focusTrackback[0].isSheduledForRestore() || focusTrackback[0].isWillBeSheduledForRestore())) { - focusTrackback[0].setWillBeSheduledForRestore(); - - IdeFocusManager mgr = IdeFocusManager.findInstanceByComponent(_window); - Runnable r = new Runnable() { - public void run() { - if (focusTrackback[0] != null) focusTrackback[0].restoreFocus(); - focusTrackback[0] = null; - } - }; - mgr.doWhenFocusSettlesDown(r); - } - - return code; - } + runOrPostponeForWindow(documentRoot, new Runnable() { + @Override + public void run() { + invoke(delegate, "performSelectorOnMainThread:withObject:waitUntilDone:", + createSelector("showVariableButtonsSheet:"), paramsArray, false); } + }); - return -1; + startModal(documentRoot, focusedWindow); + + IdeFocusManager.getGlobalInstance().setTypeaheadEnabled(true); + + + if (focusTrackback[0] != null && + !(focusTrackback[0].isSheduledForRestore() || focusTrackback[0].isWillBeSheduledForRestore())) { + focusTrackback[0].setWillBeSheduledForRestore(); + + IdeFocusManager mgr = IdeFocusManager.findInstanceByComponent(documentRoot); + Runnable r = new Runnable() { + public void run() { + if (focusTrackback[0] != null) focusTrackback[0].restoreFocus(); + focusTrackback[0] = null; + } + }; + mgr.doWhenFocusSettlesDown(r); } - }, window); + return convertReturnCodeFromNativeMessageDialog(documentRoot); + } + + return -1; + + } - private static int doForWindowAndTitle(PairFunction, JRootPane, Integer> fun, @Nullable Window window) { - LOG.assertTrue(SwingUtilities.isEventDispatchThread()); + private static int convertReturnCodeFromNativeMessageDialog(Window documentRoot) { + return resultsFromDocumentRoot.remove(documentRoot) - 1000; + } - JRootPane pane = null; - String _windowTitle = null; - - Window _window = window == null ? KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() : window; - if (_window == null) { - Component focusOwner = IdeFocusManager.findInstance().getFocusOwner(); - if (focusOwner != null) { - _window = SwingUtilities.getWindowAncestor(focusOwner); - } - - if (_window == null) { - _window = WindowManager.getInstance().findVisibleFrame(); - } + private static String getdocumentRootTitle(Window documentRoot) { + String documentRootTitle; + if (documentRoot instanceof Frame) { + documentRootTitle = ((Frame)documentRoot).getTitle(); + } else if (documentRoot instanceof Dialog) { + documentRootTitle = ((Dialog)documentRoot).getTitle(); + } else { + throw new RuntimeException("The window is not a frame and not a dialog!"); } - - LOG.assertTrue(_window != null); - - if (!_window.isShowing()) { - Container parent = _window.getParent(); - if (parent != null && parent instanceof Window) { - _window = (Window)parent; - } - - if (!_window.isShowing()) { - Component focusOwner = IdeFocusManager.findInstance().getFocusOwner(); - if (focusOwner != null) { - _window = SwingUtilities.getWindowAncestor(focusOwner); - } - } - } - - LOG.assertTrue(_window.isShowing(), "Window MUST BE showing in screen!"); - - if (_window instanceof JFrame) { - JFrame frame = (JFrame)_window; - pane = frame.getRootPane(); - _windowTitle = frame.getTitle(); - } - else if (_window instanceof JDialog) { - JDialog dialog = (JDialog)_window; - pane = dialog.getRootPane(); - _windowTitle = dialog.getTitle(); - } - - if (_windowTitle == null) { - _window = SwingUtilities.getWindowAncestor(_window); - if (_window instanceof JFrame) { - JFrame frame = (JFrame)_window; - pane = frame.getRootPane(); - _windowTitle = frame.getTitle(); - } - else if (_window instanceof JDialog) { - JDialog dialog = (JDialog)_window; - pane = dialog.getRootPane(); - _windowTitle = dialog.getTitle(); - } - } - - LOG.assertTrue(_windowTitle != null && _windowTitle.length() > 0 && pane != null, "Window MUST have a title and a root pane!"); - - return fun.fun(Pair.create(_window, _windowTitle), pane); + return documentRootTitle; } public static int showAlertDialog(final String title, @@ -408,137 +411,140 @@ public class MacMessagesImpl extends MacMessages { @Nullable final String alternateText, @Nullable final String otherText, final String message, - @Nullable Window window, + @Nullable Window window , final boolean errorStyle, @Nullable final DialogWrapper.DoNotAskOption doNotAskDialogOption) { - return doForWindowAndTitle(new PairFunction, JRootPane, Integer>() { - @Override - public Integer fun(Pair windowAndTitle, JRootPane pane) { - String _windowTitle = windowAndTitle.getSecond(); - Window _window = windowAndTitle.getFirst(); - final ID focusedWindow = MacUtil.findWindowForTitle(_windowTitle); - if (focusedWindow != null) { - String fakeTitle = null; + Window documentRoot = getDocumetnRootFromWindow(window); + String documentRootTitle = getdocumentRootTitle(documentRoot); - final FocusTrackback[] focusTrackback = {new FocusTrackback(new Object(), _window, true)}; + final ID focusedWindow = MacUtil.findWindowForTitle(documentRootTitle); - ID pool = invoke("NSAutoreleasePool", "new"); - try { - final ID delegate = invoke(Foundation.getObjcClass("NSAlertDelegate_"), "new"); - cfRetain(delegate); + ID pool = invoke("NSAutoreleasePool", "new"); + try { - fakeTitle = String.format("MacSheetDialog-%d", delegate.intValue()); + final ID delegate = invoke(Foundation.getObjcClass("NSAlertDelegate_"), "new"); + cfRetain(delegate); - ID paramsArray = invoke("NSArray", "arrayWithObjects:", nsString(title), nsString(UIUtil.removeMnemonic(defaultText)), + final ID paramsArray = invoke("NSArray", "arrayWithObjects:", nsString(title), nsString(UIUtil.removeMnemonic(defaultText)), nsString(otherText == null ? "-1" : UIUtil.removeMnemonic(otherText)), nsString(alternateText == null ? "-1" : UIUtil.removeMnemonic(alternateText)), // replace % -> %% to avoid formatted parameters (causes SIGTERM) nsString(StringUtil.stripHtml(message == null ? "" : message, true).replace("%", "%%")), - focusedWindow, nsString(fakeTitle), nsString(errorStyle ? "error" : "-1"), + focusedWindow, nsString(""), nsString(errorStyle ? "error" : "-1"), nsString(doNotAskDialogOption == null || !doNotAskDialogOption.canBeHidden() // TODO: state=!doNotAsk.shouldBeShown() ? "-1" : doNotAskDialogOption.getDoNotShowMessage()), nsString(doNotAskDialogOption != null && !doNotAskDialogOption.isToBeShown() ? "checked" : "-1"), null); - IdeFocusManager.getGlobalInstance().setTypeaheadEnabled(false); - - invoke(delegate, "performSelectorOnMainThread:withObject:waitUntilDone:", - Foundation.createSelector("showSheet:"), paramsArray, false); - } - finally { - invoke(pool, "release"); - } - if (fakeTitle != null) { - pane.putClientProperty(MacUtil.MAC_NATIVE_WINDOW_SHOWING, Boolean.TRUE); - pane.putClientProperty(MAC_SHEET_ID, fakeTitle); - MacUtil.startModal(pane); - - IdeFocusManager.getGlobalInstance().setTypeaheadEnabled(true); - - Integer result = (Integer)pane.getClientProperty(MAC_SHEET_RESULT); - boolean suppress = Boolean.TRUE == pane.getClientProperty(MAC_SHEET_SUPPRESS); + IdeFocusManager.getGlobalInstance().setTypeaheadEnabled(false); - // DEFAULT = 1 - // ALTERNATE = 0 - // OTHER = -1 (cancel) - int cancelCode = 1; - int code; - if (alternateText != null) { - // DEFAULT = 0 - // ALTERNATE = 1 - // CANCEL = 2 - cancelCode = 2; + runOrPostponeForWindow(documentRoot, new Runnable() { + @Override + public void run() { + invoke(delegate, "performSelectorOnMainThread:withObject:waitUntilDone:", + Foundation.createSelector("showSheet:"), paramsArray, false); + } + }); + startModal(documentRoot, focusedWindow); + IdeFocusManager.getGlobalInstance().setTypeaheadEnabled(true); - if (result == null) result = 2; - - switch (result) { - case 1: - code = 0; - break; - case 0: - code = 1; - break; - case -1: // cancel - default: - code = 2; - break; - } - } - else { - // DEFAULT = 0 - // CANCEL = 1 + } + finally { + invoke(pool, "release"); + } + return convertRetunCodeFromNativeAlertDialog(documentRoot, alternateText); - cancelCode = 1; + } - if (result == null) result = -1; + private static int convertRetunCodeFromNativeAlertDialog(Window documentRoot, String alternateText) { + Integer result = resultsFromDocumentRoot.remove(documentRoot); - switch (result) { - case 1: - code = 0; - break; - case -1: // cancel - default: - code = 1; - break; - } - } + // DEFAULT = 1 + // ALTERNATE = 0 + // OTHER = -1 (cancel) - if (doNotAskDialogOption != null && doNotAskDialogOption.canBeHidden()) { - if (cancelCode != code || doNotAskDialogOption.shouldSaveOptionsOnCancel()) { - doNotAskDialogOption.setToBeShown(!suppress, code); - } - } + int cancelCode = 1; + int code; + if (alternateText != null) { + // DEFAULT = 0 + // ALTERNATE = 1 + // CANCEL = 2 - pane.putClientProperty(MAC_SHEET_RESULT, null); - pane.putClientProperty(MAC_SHEET_SUPPRESS, null); + cancelCode = 2; - if (focusTrackback[0] != null && - !(focusTrackback[0].isSheduledForRestore() || focusTrackback[0].isWillBeSheduledForRestore())) { - focusTrackback[0].setWillBeSheduledForRestore(); + if (result == null) result = 2; - IdeFocusManager mgr = IdeFocusManager.findInstanceByComponent(_window); - Runnable r = new Runnable() { - public void run() { - if (focusTrackback[0] != null) focusTrackback[0].restoreFocus(); - focusTrackback[0] = null; - } - }; - mgr.doWhenFocusSettlesDown(r); - } + switch (result) { + case 1: + code = 0; + break; + case 0: + code = 1; + break; + case -1: // cancel + default: + code = 2; + break; + } + } + else { + // DEFAULT = 0 + // CANCEL = 1 - return code; - } + cancelCode = 1; + + if (result == null) result = -1; + + switch (result) { + case 1: + code = 0; + break; + case -1: // cancel + default: + code = 1; + break; + } + } + return code; + } + + private static void runOrPostponeForWindow(Window documentRoot, Runnable task) { + synchronized (lock) { + MacMessagesQueue queue = queuesFromDocumentRoot.get(documentRoot); + + if (queue == null) { + queue = new MacMessagesQueue(); + queuesFromDocumentRoot.put(documentRoot, queue); + } + + queue.runOrEnqueue(task); + } + } + + private static Window getDocumetnRootFromWindow(Window window) { + + if (window == null) { + Window _window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow(); + if (_window == null) { + Component focusOwner = IdeFocusManager.findInstance().getFocusOwner(); + if (focusOwner != null) { + _window = SwingUtilities.getWindowAncestor(focusOwner); } - return -1; + if (_window == null) { + _window = WindowManager.getInstance().findVisibleFrame(); + } } - }, window); + window = _window; + } + + return findDocumentRoot(window); } public static int showMessageDialog(String title, diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesQueue.java b/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesQueue.java new file mode 100644 index 000000000000..ce4646a653b3 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesQueue.java @@ -0,0 +1,49 @@ +/* + * 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.ui.mac; + +import java.util.LinkedList; + +/** + * @author Denis Fokin + */ +class MacMessagesQueue { + + private boolean waitingForAppKit = false; + private LinkedList queueModel = new LinkedList(); + + synchronized void runOrEnqueue (final T runnable) { + if (waitingForAppKit) { + enqueue(runnable); + } else { + runnable.run(); + waitingForAppKit = true; + } + } + + private void enqueue (final T runnable) { + queueModel.add(runnable); + } + + synchronized void runFromQueue () { + if (!queueModel.isEmpty()) { + queueModel.remove().run(); + waitingForAppKit = true; + } else { + waitingForAppKit = false; + } + } +} diff --git a/platform/platform-resources/src/idea/PlatformActions.xml b/platform/platform-resources/src/idea/PlatformActions.xml index ebbe12e90dba..a039d5bdb69a 100644 --- a/platform/platform-resources/src/idea/PlatformActions.xml +++ b/platform/platform-resources/src/idea/PlatformActions.xml @@ -1,3 +1,18 @@ + @@ -578,6 +593,7 @@ +