diff --git a/platform/core-api/src/com/intellij/openapi/application/AcceptNestedTransactions.java b/platform/core-api/src/com/intellij/openapi/application/AcceptNestedTransactions.java new file mode 100644 index 000000000000..404a54897eaf --- /dev/null +++ b/platform/core-api/src/com/intellij/openapi/application/AcceptNestedTransactions.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.application; + +import java.lang.annotation.*; + +/** + * Add this annotation to modal dialogs (DialogWrapper inheritors) shown from within transactions, + * to allow nested transactions inside those dialogs. + * + * @see TransactionGuard#acceptNestedTransactions(TransactionKind...) + * @since 146.* + * @author peter + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface AcceptNestedTransactions { + + /** + * @return the kinds of transaction to allow inside the dialog + */ + TransactionKind.Common[] value(); +} diff --git a/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java b/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java index 890f9289420d..9134e9c58fbd 100644 --- a/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java +++ b/platform/core-api/src/com/intellij/openapi/application/TransactionGuard.java @@ -60,7 +60,10 @@ import org.jetbrains.annotations.NotNull; * * Having said all that, it's still advisable that the transactions be as short as possible and preferably exclude any modal dialogs * for which transaction-ness is not critical. So a better overall strategy would be to either make the dialogs non-modal, - * or at least make them and the code that shows them prepared for possible model changes while the dialog is shown. + * or at least make them and the code that shows them prepared for possible model changes while the dialog is shown.

+ * + * Dialogs that have per-project modality must never be shown from a transaction, because this would disallow making changes in another + * project: they'd be blocked by the running transaction. * * Q: I've got "Write access is allowed from model transactions only" exception, what do I do?
* A: Add a transaction somewhere into the call stack, to the outermost callee where having read/write model consistency is needed. @@ -177,8 +180,10 @@ public abstract class TransactionGuard { /** * Allow incoming transactions of the specified kinds to be executed immediately, instead of being queued until the current transaction is finished.

* - * Example: outer transaction has shown a dialog with an editor, and typing into that editor (which requires a transaction for changing document) - * should be allowed. + * Example: outer transaction has shown a dialog with an editor, and typing into that editor (which requires a transaction for changing document). + * should be allowed.

+ * + * For dialogs, consider using {@link AcceptNestedTransactions} annotation instead of explicit call to this method. * @param kinds kinds of transactions to allow * @return a token object for this session. Please call {@link AccessToken#finish()} (inside finally clause) when you don't want * nested transactions anymore. diff --git a/platform/core-api/src/com/intellij/openapi/application/TransactionKind.java b/platform/core-api/src/com/intellij/openapi/application/TransactionKind.java index 6a666b98b0ad..b4db511c2b63 100644 --- a/platform/core-api/src/com/intellij/openapi/application/TransactionKind.java +++ b/platform/core-api/src/com/intellij/openapi/application/TransactionKind.java @@ -18,8 +18,8 @@ package com.intellij.openapi.application; import com.intellij.openapi.editor.Document; /** - * A kind of transaction used in {@link TransactionGuard#submitMergeableTransaction(TransactionKind, Runnable)} - * and {@link TransactionGuard#acceptNestedTransactions(TransactionKind...)}. + * A kind of transaction used in {@link TransactionGuard#submitMergeableTransaction(TransactionKind, Runnable)}, + * {@link TransactionGuard#acceptNestedTransactions(TransactionKind...)} and {@link AcceptNestedTransactions}. */ public interface TransactionKind { /** 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 f66a87c3c9d0..b39ef3d91bb1 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java @@ -24,9 +24,7 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.MnemonicHelper; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionUtil; -import com.intellij.openapi.application.ApplicationInfo; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.help.HelpManager; import com.intellij.openapi.keymap.KeymapUtil; @@ -1641,6 +1639,16 @@ public abstract class DialogWrapper { @NotNull private AsyncResult invokeShow() { + Window window = myPeer.getWindow(); + if (window instanceof JDialog && ((JDialog)window).getModalityType() == Dialog.ModalityType.DOCUMENT_MODAL) { + if (ApplicationManager.getApplication().isWriteAccessAllowed()) { + LOG.error("Project-modal dialogs should not be shown under a write action."); + } + if (TransactionGuard.getInstance().isInsideTransaction()) { + LOG.error("Project-modal dialogs should not be shown inside a transaction. See TransactionGuard documentation."); + } + } + final AsyncResult result = new AsyncResult(); ensureEventDispatchThread(); @@ -1658,7 +1666,10 @@ public abstract class DialogWrapper { } }); - myPeer.show(); + AcceptNestedTransactions anno = getClass().getAnnotation(AcceptNestedTransactions.class); + try (AccessToken ignore = anno == null ? null : TransactionGuard.getInstance().acceptNestedTransactions((TransactionKind[])anno.value())) { + myPeer.show(); + } return result; }