| /* |
| * 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); |
| } |
| } |