com.intellij.ui.mac.MacMessagesImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.ui.mac.MacMessagesImpl.java

Source

/*
 * Copyright 2000-2014 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 com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.impl.ModalityHelper;
import com.intellij.ui.mac.foundation.ID;
import com.intellij.ui.mac.foundation.MacUtil;
import com.intellij.util.ui.UIUtil;
import com.sun.jna.Callback;
import com.sun.jna.Pointer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import static com.intellij.ui.mac.foundation.Foundation.*;

/**
 * @author pegov
 */
public class MacMessagesImpl extends MacMessages {
    private static final Logger LOG = Logger.getInstance("#com.intellij.ui.mac.MacMessages");

    private static class MessageResult {
        MessageResult(int returnCode, boolean suppress) {
            myReturnCode = returnCode;
            mySuppress = suppress;
        }

        private final int myReturnCode;
        private final boolean mySuppress;
    }

    private static final Map<Window, MessageResult> resultsFromDocumentRoot = new HashMap<Window, MessageResult>();
    private static final Map<Window, MacMessagesQueue<Runnable>> queuesFromDocumentRoot = new HashMap<Window, MacMessagesQueue<Runnable>>();

    private static final Callback SHEET_DID_END = new Callback() {
        @SuppressWarnings("UnusedDeclaration")
        public void callback(ID self, String selector, ID alert, ID returnCode, ID contextInfo) {
            synchronized (lock) {
                Window documentRoot = windowFromId.get(contextInfo.longValue());
                processResult(documentRoot);
                ID suppressState = invoke(invoke(alert, "suppressionButton"), "state");
                resultsFromDocumentRoot.put(documentRoot,
                        new MessageResult(returnCode.intValue(), suppressState.intValue() == 1));
                queuesFromDocumentRoot.get(windowFromId.get(contextInfo.longValue())).runFromQueue();
            }
            JDK7WindowReorderingWorkaround.enableReordering();
            cfRelease(self);
        }
    };

    private static final Callback VARIABLE_BUTTONS_SHEET_PANEL = new Callback() {
        @SuppressWarnings("UnusedDeclaration")
        public void callback(ID self, String selector, ID params) {
            ID title = invoke(params, "objectAtIndex:", 0);
            ID message = invoke(params, "objectAtIndex:", 1);
            ID focusedWindow = invoke(params, "objectAtIndex:", 2);
            ID alertStyle = invoke(params, "objectAtIndex:", 4);
            ID doNotAskText = invoke(params, "objectAtIndex:", 5);
            int defaultOptionIndex = Integer.parseInt(toStringViaUTF8(invoke(params, "objectAtIndex:", 6)));
            int focusedOptionIndex = Integer.parseInt(toStringViaUTF8(invoke(params, "objectAtIndex:", 7)));
            ID buttons = invoke(params, "objectAtIndex:", 8);
            ID doNotAskChecked = invoke(params, "objectAtIndex:", 9);

            ID alert = invoke(invoke("NSAlert", "alloc"), "init");

            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");
                if (0 == button.intValue())
                    break;
                invoke(alert, "addButtonWithTitle:", button);
            }

            if (defaultOptionIndex != -1) {
                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, "buttons"), "objectAtIndex:", focusedOptionIndex));
            //} else {
            //  int count = invoke(buttons, "count").intValue();
            //  invoke(invoke(alert, "window"), "makeFirstResponder:",
            //         invoke(invoke(alert, "buttons"), "objectAtIndex:", count == 1 ? 0 : 1));
            //}

            enableEscapeToCloseTheMessage(alert);

            String doNotAsk = toStringViaUTF8(doNotAskText);
            if (!"-1".equals(doNotAsk)) {
                invoke(alert, "setShowsSuppressionButton:", 1);
                invoke(invoke(alert, "suppressionButton"), "setTitle:", doNotAskText);
                invoke(invoke(alert, "suppressionButton"), "setState:",
                        "checked".equals(toStringViaUTF8(doNotAskChecked)));
            }

            invoke(alert, "beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:", focusedWindow, self,
                    createSelector("alertDidEnd:returnCode:contextInfo:"), focusedWindow);
            cfRelease(alert);
        }
    };

    private static final Callback SIMPLE_SHEET_PANEL = new Callback() {
        @SuppressWarnings("UnusedDeclaration")
        public void callback(ID self, String selector, ID params) {
            ID title = invoke(params, "objectAtIndex:", 0);
            ID defaultText = invoke(params, "objectAtIndex:", 1);
            ID otherText = invoke(params, "objectAtIndex:", 2);
            ID alternateText = invoke(params, "objectAtIndex:", 3);
            ID message = invoke(params, "objectAtIndex:", 4);
            ID focusedWindow = invoke(params, "objectAtIndex:", 5);
            ID alertStyle = invoke(params, "objectAtIndex:", 7);
            ID doNotAskText = invoke(params, "objectAtIndex:", 8);
            ID doNotAskChecked = invoke(params, "objectAtIndex:", 9);

            boolean alternateExist = !"-1".equals(toStringViaUTF8(alternateText));
            boolean otherExist = !"-1".equals(toStringViaUTF8(otherText));

            final ID alert = invoke("NSAlert",
                    "alertWithMessageText:defaultButton:alternateButton:otherButton:informativeTextWithFormat:",
                    title, defaultText, alternateExist ? alternateText : null, otherExist ? otherText : null,
                    message);

            if ("error".equals(toStringViaUTF8(alertStyle))) {
                invoke(alert, "setAlertStyle:", 2); // NSCriticalAlertStyle = 2
            }

            // 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(invoke(alert, "buttons"), "objectAtIndex:", alternateExist ? 2 : otherExist ? 1 : 0));
            //

            if (!alternateExist) {
                enableEscapeToCloseTheMessage(alert);
            }

            String doNotAsk = toStringViaUTF8(doNotAskText);
            if (!"-1".equals(doNotAsk)) {
                invoke(alert, "setShowsSuppressionButton:", 1);
                invoke(invoke(alert, "suppressionButton"), "setTitle:", doNotAskText);
                invoke(invoke(alert, "suppressionButton"), "setState:",
                        "checked".equals(toStringViaUTF8(doNotAskChecked)));
            }

            invoke(alert, "beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:", focusedWindow, self,
                    createSelector("alertDidEnd:returnCode:contextInfo:"), focusedWindow);
        }
    };

    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 void enableEscapeToCloseTheMessage(ID alert) {
        int buttonsNumber = invoke(invoke(alert, "buttons"), "count").intValue();
        if (buttonsNumber < 2)
            return;
        invoke(invoke(invoke(alert, "buttons"), "objectAtIndex:", buttonsNumber - 1), "setKeyEquivalent:",
                nsString("\033"));
    }

    private MacMessagesImpl() {
    }

    private static final Callback windowDidBecomeMainCallback = new Callback() {
        @SuppressWarnings("UnusedDeclaration") // this is a native up-call
        public void callback(ID self, ID nsNotification) {
            synchronized (lock) {
                if (!windowFromId.keySet().contains(self.longValue())) {
                    return;
                }
            }
            invoke(self, "oldWindowDidBecomeMain:", nsNotification);
        }
    };

    static {
        if (SystemInfo.isMac) {
            final ID delegateClass = allocateObjcClassPair(getObjcClass("NSObject"), "NSAlertDelegate_");
            if (!addMethod(delegateClass, createSelector("alertDidEnd:returnCode:contextInfo:"), SHEET_DID_END,
                    "v*")) {
                throw new RuntimeException("Unable to add method to objective-c delegate class!");
            }
            if (!addMethod(delegateClass, createSelector("showSheet:"), SIMPLE_SHEET_PANEL, "v*")) {
                throw new RuntimeException("Unable to add method to objective-c delegate class!");
            }
            if (!addMethod(delegateClass, createSelector("showVariableButtonsSheet:"), VARIABLE_BUTTONS_SHEET_PANEL,
                    "v*")) {
                throw new RuntimeException("Unable to add method to objective-c delegate class!");
            }
            registerObjcClassPair(delegateClass);

            if (SystemInfo.isJavaVersionAtLeast("1.7")) {

                ID awtWindow = getObjcClass("AWTWindow");

                Pointer windowWillEnterFullScreenMethod = createSelector("windowDidBecomeMain:");
                ID originalWindowWillEnterFullScreen = class_replaceMethod(awtWindow,
                        windowWillEnterFullScreenMethod, windowDidBecomeMainCallback, "v@::@");

                addMethodByID(awtWindow, createSelector("oldWindowDidBecomeMain:"),
                        originalWindowWillEnterFullScreen, "v@::@");

            }
        }
    }

    @Override
    public void showOkMessageDialog(@NotNull String title, String message, @NotNull String okText,
            @Nullable Window window) {
        showAlertDialog(title, okText, null, null, message, window);
    }

    @Override
    public void showOkMessageDialog(@NotNull String title, String message, @NotNull String okText) {
        showAlertDialog(title, okText, null, null, message, null);
    }

    @Override
    @Messages.YesNoResult
    public int showYesNoDialog(@NotNull String title, String message, @NotNull String yesButton,
            @NotNull String noButton, @Nullable Window window) {
        return showAlertDialog(title, yesButton, null, noButton, message, window) == Messages.YES ? Messages.YES
                : Messages.NO;
    }

    @Override
    @Messages.YesNoResult
    public int showYesNoDialog(@NotNull String title, String message, @NotNull String yesButton,
            @NotNull String noButton, @Nullable Window window,
            @Nullable DialogWrapper.DoNotAskOption doNotAskDialogOption) {
        return showAlertDialog(title, yesButton, null, noButton, message, window, false,
                doNotAskDialogOption) == Messages.YES ? Messages.YES : Messages.NO;
    }

    @Override
    public void showErrorDialog(@NotNull String title, String message, @NotNull String okButton,
            @Nullable Window window) {
        showAlertDialog(title, okButton, null, null, message, window, true, null);
    }

    @Override
    @Messages.YesNoCancelResult
    public int showYesNoCancelDialog(@NotNull String title, String message, @NotNull String defaultButton,
            String alternateButton, String otherButton, Window window,
            @Nullable DialogWrapper.DoNotAskOption doNotAskOption) {
        return showAlertDialog(title, defaultButton, alternateButton, otherButton, message, window, false,
                doNotAskOption);
    }

    private static final Object lock = new Object();

    private static final HashMap<Window, Integer> blockedDocumentRoots = new HashMap<Window, Integer>();

    private static final HashMap<Long, Window> windowFromId = new HashMap<Long, Window>();

    public static void pumpEventsDocumentExclusively(Window documentRoot) {

        Integer messageNumber = blockedDocumentRoots.get(documentRoot);

        EventQueue theQueue = documentRoot.getToolkit().getSystemEventQueue();

        do {
            try {
                AWTEvent 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();
                        }
                    }
                }

                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 (MacMessageException mme) {
                throw mme;
            } 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) {
        if (c == null)
            return null;
        Window w = c instanceof Window ? (Window) c : getContainingWindow(c);
        synchronized (c.getTreeLock()) {
            while (w.getOwner() != null) {
                w = w.getOwner();
            }
        }
        return w;
    }

    // This method is not available in jdk 1.6.0_6. Should be changed to the JDK implementation
    // as soon as we will have switched on JDK 7.
    private static Window getContainingWindow(Component comp) {
        while (comp != null && !(comp instanceof Window)) {
            comp = comp.getParent();
        }
        return (Window) comp;
    }

    private static void startModal(final Window w, ID windowId) {
        long windowPtr = windowId.longValue();
        synchronized (lock) {
            JDK7WindowReorderingWorkaround.disableReordering();
            windowFromId.put(windowPtr, w);
            if (blockedDocumentRoots.keySet().contains(w)) {
                blockedDocumentRoots.put(w, blockedDocumentRoots.get(w) + 1);
            } else {
                blockedDocumentRoots.put(w, 1);
            }
        }

        pumpEventsDocumentExclusively(w);
        synchronized (lock) {
            windowFromId.remove(windowPtr);
        }
    }

    private enum COMMON_DIALOG_PARAM_TYPE {
        title, message, errorStyle, doNotAskDialogOption1, doNotAskDialogOption2, nativeFocusedWindow
    }

    private enum MESSAGE_DIALOG_PARAM_TYPE {
        buttonsArray, defaultOptionIndex, focusedOptionIndex
    }

    private enum ALERT_DIALOG_PARAM_TYPE {
        defaultText, alternateText, otherText
    }

    private static class DialogParamsWrapper {
        private ID window = null;
        private final Map<Enum, Object> params;
        private final DialogType dialogType;

        private enum DialogType {
            alert, message
        }

        private DialogParamsWrapper(@NotNull DialogType t, @NotNull Map<Enum, Object> p) {
            dialogType = t;
            params = p;
        }

        private void setNativeWindow(final ID w) {
            window = w;
        }

        private ID getParamsAsID() {
            if (window == null) {
                throw new MacMessageException("Window should be in the list.");
            }
            params.put(COMMON_DIALOG_PARAM_TYPE.nativeFocusedWindow, window);

            ID paramsAsID = null;

            switch (dialogType) {
            case alert:
                paramsAsID = getParamsForAlertDialog(params);
                break;
            case message:
                paramsAsID = getParamsForMessageDialog(params);
                break;
            }
            return paramsAsID;
        }

        private static ID getParamsForAlertDialog(@NotNull Map<Enum, Object> params) {
            return invoke("NSArray", "arrayWithObjects:", params.get(COMMON_DIALOG_PARAM_TYPE.title),
                    params.get(ALERT_DIALOG_PARAM_TYPE.defaultText),
                    params.get(ALERT_DIALOG_PARAM_TYPE.alternateText),
                    params.get(ALERT_DIALOG_PARAM_TYPE.otherText), params.get(COMMON_DIALOG_PARAM_TYPE.message),
                    params.get(COMMON_DIALOG_PARAM_TYPE.nativeFocusedWindow), nsString(""),
                    params.get(COMMON_DIALOG_PARAM_TYPE.errorStyle),
                    params.get(COMMON_DIALOG_PARAM_TYPE.doNotAskDialogOption1),
                    params.get(COMMON_DIALOG_PARAM_TYPE.doNotAskDialogOption2), null);
        }

        private static ID getParamsForMessageDialog(@NotNull Map<Enum, Object> params) {
            return invoke("NSArray", "arrayWithObjects:", params.get(COMMON_DIALOG_PARAM_TYPE.title),
                    params.get(COMMON_DIALOG_PARAM_TYPE.message),
                    params.get(COMMON_DIALOG_PARAM_TYPE.nativeFocusedWindow), nsString(""),
                    params.get(COMMON_DIALOG_PARAM_TYPE.errorStyle),
                    params.get(COMMON_DIALOG_PARAM_TYPE.doNotAskDialogOption1),
                    params.get(MESSAGE_DIALOG_PARAM_TYPE.defaultOptionIndex),
                    params.get(MESSAGE_DIALOG_PARAM_TYPE.focusedOptionIndex),
                    params.get(MESSAGE_DIALOG_PARAM_TYPE.buttonsArray),
                    params.get(COMMON_DIALOG_PARAM_TYPE.doNotAskDialogOption2), null);
        }
    }

    @Messages.YesNoCancelResult
    public static int showAlertDialog(@NotNull String title, @NotNull String defaultText,
            @Nullable final String alternateText, @Nullable final String otherText, final String message,
            @Nullable Window window, final boolean errorStyle,
            @Nullable final DialogWrapper.DoNotAskOption doNotAskDialogOption) {

        Map<Enum, Object> params = new HashMap<Enum, Object>();

        ID pool = invoke(invoke("NSAutoreleasePool", "alloc"), "init");
        try {
            params.put(COMMON_DIALOG_PARAM_TYPE.title, nsString(title));
            params.put(ALERT_DIALOG_PARAM_TYPE.defaultText, nsString(UIUtil.removeMnemonic(defaultText)));
            params.put(ALERT_DIALOG_PARAM_TYPE.alternateText,
                    nsString(otherText == null ? "-1" : UIUtil.removeMnemonic(otherText)));
            params.put(ALERT_DIALOG_PARAM_TYPE.otherText,
                    nsString(alternateText == null ? "-1" : UIUtil.removeMnemonic(alternateText)));
            // replace % -> %% to avoid formatted parameters (causes SIGTERM)
            params.put(COMMON_DIALOG_PARAM_TYPE.message,
                    nsString(StringUtil.stripHtml(message == null ? "" : message, true).replace("%", "%%")));
            params.put(COMMON_DIALOG_PARAM_TYPE.errorStyle, nsString(errorStyle ? "error" : "-1"));
            params.put(COMMON_DIALOG_PARAM_TYPE.doNotAskDialogOption1,
                    nsString(doNotAskDialogOption == null || !doNotAskDialogOption.canBeHidden()
                            // TODO: state=!doNotAsk.shouldBeShown()
                            ? "-1"
                            : doNotAskDialogOption.getDoNotShowMessage()));
            params.put(COMMON_DIALOG_PARAM_TYPE.doNotAskDialogOption2, nsString(
                    doNotAskDialogOption != null && !doNotAskDialogOption.isToBeShown() ? "checked" : "-1"));
            MessageResult result = resultsFromDocumentRoot.remove(showDialog(window, "showSheet:",
                    new DialogParamsWrapper(DialogParamsWrapper.DialogType.alert, params)));

            int convertedResult = convertReturnCodeFromNativeAlertDialog(result.myReturnCode, alternateText);

            if (doNotAskDialogOption != null && doNotAskDialogOption.canBeHidden()) {
                boolean operationCanceled = convertedResult == Messages.CANCEL;
                if (!operationCanceled || doNotAskDialogOption.shouldSaveOptionsOnCancel()) {
                    doNotAskDialogOption.setToBeShown(!result.mySuppress, convertedResult);
                }
            }

            return convertedResult;
        } finally {
            invoke(pool, "release");
        }
    }

    @Override
    public int showMessageDialog(@NotNull final String title, final String message, @NotNull final String[] buttons,
            final boolean errorStyle, @Nullable Window window, final int defaultOptionIndex,
            final int focusedOptionIndex, @Nullable final DialogWrapper.DoNotAskOption doNotAskDialogOption) {
        ID pool = invoke(invoke("NSAutoreleasePool", "alloc"), "init");
        try {
            final ID buttonsArray = invoke("NSMutableArray", "array");
            for (String s : buttons) {
                ID s1 = nsString(UIUtil.removeMnemonic(s));
                invoke(buttonsArray, "addObject:", s1);
            }

            Map<Enum, Object> params = new HashMap<Enum, Object>();

            params.put(COMMON_DIALOG_PARAM_TYPE.title, nsString(title));
            // replace % -> %% to avoid formatted parameters (causes SIGTERM)
            params.put(COMMON_DIALOG_PARAM_TYPE.message,
                    nsString(StringUtil.stripHtml(message == null ? "" : message, true).replace("%", "%%")));

            params.put(COMMON_DIALOG_PARAM_TYPE.errorStyle, nsString(errorStyle ? "error" : "-1"));
            params.put(COMMON_DIALOG_PARAM_TYPE.doNotAskDialogOption1,
                    nsString(doNotAskDialogOption == null || !doNotAskDialogOption.canBeHidden()
                            // TODO: state=!doNotAsk.shouldBeShown()
                            ? "-1"
                            : doNotAskDialogOption.getDoNotShowMessage()));
            params.put(COMMON_DIALOG_PARAM_TYPE.doNotAskDialogOption2, nsString(
                    doNotAskDialogOption != null && !doNotAskDialogOption.isToBeShown() ? "checked" : "-1"));
            params.put(MESSAGE_DIALOG_PARAM_TYPE.defaultOptionIndex,
                    nsString(Integer.toString(defaultOptionIndex)));
            params.put(MESSAGE_DIALOG_PARAM_TYPE.focusedOptionIndex,
                    nsString(Integer.toString(focusedOptionIndex)));
            params.put(MESSAGE_DIALOG_PARAM_TYPE.buttonsArray, buttonsArray);

            MessageResult result = resultsFromDocumentRoot.remove(showDialog(window, "showVariableButtonsSheet:",
                    new DialogParamsWrapper(DialogParamsWrapper.DialogType.message, params)));

            final int code = convertReturnCodeFromNativeMessageDialog(result.myReturnCode);

            final int cancelCode = buttons.length - 1;

            if (doNotAskDialogOption != null && doNotAskDialogOption.canBeHidden()) {
                if (cancelCode != code || doNotAskDialogOption.shouldSaveOptionsOnCancel()) {
                    doNotAskDialogOption.setToBeShown(!result.mySuppress, code);
                }
            }

            return code;
        } finally {
            invoke(pool, "release");
        }
    }

    //title, message, errorStyle, window, paramsArray, doNotAskDialogOption, "showVariableButtonsSheet:"
    private static Window showDialog(@Nullable Window window, final String methodName,
            final DialogParamsWrapper paramsWrapper) {

        final Window foremostWindow = getForemostWindow(window);

        final Window documentRoot = getDocumentRootFromWindow(foremostWindow);

        final ID nativeFocusedWindow = MacUtil.findWindowFromJavaWindow(foremostWindow);

        paramsWrapper.setNativeWindow(nativeFocusedWindow);

        final ID paramsArray = paramsWrapper.getParamsAsID();

        foremostWindow.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                super.windowClosed(e);
                //if (blockedDocumentRoots.get(documentRoot) != null) {
                //   LOG.assertTrue(blockedDocumentRoots.get(documentRoot) < 2);
                //}
                queuesFromDocumentRoot.remove(documentRoot);
                if (blockedDocumentRoots.remove(documentRoot) != null) {
                    throw new MacMessageException("Owner window has been removed");
                }
            }
        });

        final ID delegate = invoke(invoke(getObjcClass("NSAlertDelegate_"), "alloc"), "init");

        IdeFocusManager.getGlobalInstance().setTypeaheadEnabled(false);

        runOrPostponeForWindow(documentRoot, new Runnable() {
            @Override
            public void run() {
                invoke(delegate, "performSelectorOnMainThread:withObject:waitUntilDone:",
                        createSelector(methodName), paramsArray, false);
            }
        });

        startModal(documentRoot, nativeFocusedWindow);

        IdeFocusManager.getGlobalInstance().setTypeaheadEnabled(true);
        return documentRoot;
    }

    private static int convertReturnCodeFromNativeMessageDialog(int result) {
        return result - 1000;
    }

    @Messages.YesNoCancelResult
    private static int convertReturnCodeFromNativeAlertDialog(int returnCode, String alternateText) {
        // DEFAULT = 1
        // ALTERNATE = 0
        // OTHER = -1 (cancel)

        int cancelCode;
        int code;
        if (alternateText != null) {
            // DEFAULT = 0
            // ALTERNATE = 1
            // CANCEL = 2

            cancelCode = Messages.CANCEL;

            switch (returnCode) {
            case 1:
                code = Messages.YES;
                break;
            case 0:
                code = Messages.NO;
                break;
            case -1: // cancel
            default:
                code = Messages.CANCEL;
                break;
            }
        } else {
            // DEFAULT = 0
            // CANCEL = 1

            cancelCode = 1;

            switch (returnCode) {
            case 1:
                code = Messages.YES;
                break;
            case -1: // cancel
            default:
                code = Messages.NO;
                break;
            }
        }

        if (cancelCode == code) {
            code = Messages.CANCEL;
        }
        LOG.assertTrue(code == Messages.YES || code == Messages.NO || code == Messages.CANCEL, code);
        return code;
    }

    private static void runOrPostponeForWindow(Window documentRoot, Runnable task) {
        synchronized (lock) {
            MacMessagesQueue<Runnable> queue = queuesFromDocumentRoot.get(documentRoot);

            if (queue == null) {
                queue = new MacMessagesQueue<Runnable>();
                queuesFromDocumentRoot.put(documentRoot, queue);
            }

            queue.runOrEnqueue(task);
        }
    }

    private static Window getForemostWindow(final Window window) {
        Window _window = null;
        IdeFocusManager ideFocusManager = IdeFocusManager.getGlobalInstance();

        Component focusOwner = IdeFocusManager.findInstance().getFocusOwner();
        // Let's ask for a focused component first
        if (focusOwner != null) {
            _window = SwingUtilities.getWindowAncestor(focusOwner);
        }

        if (_window == null) {
            // Looks like ide lost focus, let's ask about the last focused component
            focusOwner = ideFocusManager.getLastFocusedFor(ideFocusManager.getLastFocusedFrame());
            if (focusOwner != null) {
                _window = SwingUtilities.getWindowAncestor(focusOwner);
            }
        }

        if (_window == null) {
            _window = WindowManager.getInstance().findVisibleFrame();
        }

        if (_window == null && window != null) {
            // It might be we just has not opened a frame yet.
            // So let's ask AWT
            focusOwner = window.getMostRecentFocusOwner();
            if (focusOwner != null) {
                _window = SwingUtilities.getWindowAncestor(focusOwner);
            }
        }

        if (_window != null) {
            // We have successfully found the window
            // Let's check that we have not missed a blocker
            if (ModalityHelper.isModalBlocked(_window)) {
                _window = ModalityHelper.getModalBlockerFor(_window);
            }
        }

        if (SystemInfo.isAppleJvm && MacUtil.getWindowTitle(_window) == null) {
            // With Apple JDK we cannot find a window if it does not have a title
            // Let's show a dialog instead of the message.
            throw new MacMessageException("MacMessage parent does not have a title.");
        }
        while (_window != null && MacUtil.getWindowTitle(_window) == null) {
            _window = _window.getOwner();
            //At least our frame should have a title
        }

        while (Registry.is("skip.untitled.windows.for.mac.messages") && _window != null
                && _window instanceof JDialog && !((JDialog) _window).isModal()) {
            _window = _window.getOwner();
        }

        return _window;
    }

    /**
     * Document root is intended to queue messages per a document root
     */
    private static Window getDocumentRootFromWindow(Window window) {
        return findDocumentRoot(window);
    }

    @Messages.YesNoCancelResult
    private static int showAlertDialog(@NotNull String title, @NotNull String okText,
            @Nullable String alternateText, @Nullable String cancelText, String message, @Nullable Window window) {
        return showAlertDialog(title, okText, alternateText, cancelText, message, window, false, null);
    }
}