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;
}