From a8800a9a042ddb851328875d3d7461752b1f2045 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 1 Dec 2016 16:26:39 +0100 Subject: [PATCH] runWriteActionWithProgress --- .../TestWriteActionUnderProgress.java | 86 +++++++++++ .../application/impl/ApplicationImpl.java | 14 ++ .../progress/util/PotemkinProgress.java | 144 ++++++++++++++++++ .../openapi/progress/util/ProgressDialog.java | 11 +- .../openapi/progress/util/ProgressWindow.java | 5 + .../openapi/wm/impl/IdeGlassPaneImpl.java | 2 +- .../src/idea/PlatformActions.xml | 1 + 7 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/internal/TestWriteActionUnderProgress.java create mode 100644 platform/platform-impl/src/com/intellij/openapi/progress/util/PotemkinProgress.java diff --git a/platform/platform-impl/src/com/intellij/internal/TestWriteActionUnderProgress.java b/platform/platform-impl/src/com/intellij/internal/TestWriteActionUnderProgress.java new file mode 100644 index 000000000000..28d47b5753b1 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/internal/TestWriteActionUnderProgress.java @@ -0,0 +1,86 @@ +/* + * 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.internal; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.impl.ApplicationImpl; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.util.TimeoutUtil; +import com.intellij.util.concurrency.EdtExecutorService; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.concurrent.TimeUnit; + +/** + * @author peter + */ +public class TestWriteActionUnderProgress extends DumbAwareAction { + @Override + public void actionPerformed(AnActionEvent e) { + ApplicationImpl app = (ApplicationImpl)ApplicationManager.getApplication(); + app.runWriteActionWithProgress("Progress in main frame", null, null, TestWriteActionUnderProgress::runIndeterminateProgress); + + showDialog(app); + } + + private static void showDialog(ApplicationImpl app) { + DialogWrapper dialog = new DialogWrapper(null) { + { + setTitle("Some Modal Dialog"); + init(); + } + @Nullable + @Override + protected JComponent createCenterPanel() { + return new JTextField("Waiting for the progress..."); + } + }; + EdtExecutorService.getScheduledExecutorInstance().schedule(() -> { + app.runWriteActionWithProgress("Progress in modal dialog", null, dialog.getRootPane(), TestWriteActionUnderProgress::runDeterminateProgress); + dialog.close(0); + }, 2500, TimeUnit.MILLISECONDS); + app.invokeLater(() -> { + dialog.setSize(100, 100); + dialog.setLocation(100, 100); + }, ModalityState.any()); + dialog.show(); + } + + private static void runDeterminateProgress(ProgressIndicator indicator) { + int iterations = 3000; + for (int i = 0; i < iterations; i++) { + TimeoutUtil.sleep(1); + indicator.setFraction(((double)i + 1) / ((double)iterations)); + indicator.setText(String.valueOf(i)); + ProgressManager.checkCanceled(); + } + } + + private static void runIndeterminateProgress(ProgressIndicator indicator) { + indicator.setIndeterminate(true); + indicator.setText("Indeterminate"); + for (int i = 0; i < 200; i++) { + TimeoutUtil.sleep(10); + indicator.checkCanceled(); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 35c0d231d485..8b87ae8b170a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -47,6 +47,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.*; import com.intellij.openapi.progress.impl.CoreProgressManager; import com.intellij.openapi.progress.util.ProgressWindow; +import com.intellij.openapi.progress.util.PotemkinProgress; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ex.ProjectManagerEx; @@ -941,6 +942,19 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App myLock.readUnlock(); } + public void runWriteActionWithProgress(@NotNull String title, @Nullable Project project, @Nullable JComponent parentComponent, + @NotNull Consumer action) { + Class clazz = action.getClass(); + startWrite(clazz); + try { + PotemkinProgress indicator = new PotemkinProgress(title, project, parentComponent); + ProgressManager.getInstance().runProcess(() -> action.consume(indicator), indicator); + } + finally { + endWrite(clazz); + } + } + @Override public void runWriteAction(@NotNull final Runnable action) { Class clazz = action.getClass(); diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/PotemkinProgress.java b/platform/platform-impl/src/com/intellij/openapi/progress/util/PotemkinProgress.java new file mode 100644 index 000000000000..6319649ab8be --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/PotemkinProgress.java @@ -0,0 +1,144 @@ +/* + * 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.progress.util; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapperDialog; +import com.intellij.openapi.wm.IdeFrame; +import com.intellij.openapi.wm.IdeGlassPaneUtil; +import com.intellij.openapi.wm.impl.IdeGlassPaneImpl; +import com.intellij.util.io.storage.HeavyProcessLatch; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; + +/** + * A progress indicator for processes running in EDT. Paints itself in checkCanceled calls. + * + * @author peter + */ +public class PotemkinProgress extends ProgressWindow { + private final BufferedImage myScreenshot; + private long myLastUiUpdate = System.currentTimeMillis(); + + public PotemkinProgress(@NotNull String title, @Nullable Project project, @Nullable JComponent parentComponent) { + super(false,false, project, parentComponent, null); + setTitle(title); + + ProgressDialog dialog = getDialog(); + Window parentWindow = dialog == null ? null : dialog.getParentWindow(); + myScreenshot = parentWindow instanceof IdeFrame ? takeScreenshot(((IdeFrame)parentWindow).getComponent()) : null; + + installCheckCanceledPaintingHook(); + } + + /** + * Remember how everything looked. We must not call custom paint methods during write action, + * because they might access the model which might be inconsistent at that moment. + */ + @NotNull + private static BufferedImage takeScreenshot(@NotNull JComponent component) { + BufferedImage image = UIUtil.createImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics2D graphics = image.createGraphics(); + component.paint(graphics); + graphics.dispose(); + return image; + } + + private void installCheckCanceledPaintingHook() { + // make ProgressManager#checkCanceled actually delegate to the current indicator + HeavyProcessLatch.INSTANCE.prioritizeUiActivity(); + + // isCanceled is final, so using a nonstandard way of plugging into it + addStateDelegate(new AbstractProgressIndicatorExBase() { + @Override + public boolean isCanceled() { + updateUI(); + return super.isCanceled(); + } + }); + } + + private void updateUI() { + ProgressDialog dialog = getDialog(); + if (!ApplicationManager.getApplication().isDispatchThread() || dialog == null) return; + + JRootPane rootPane = dialog.getPanel().getRootPane(); + if (rootPane == null) { + rootPane = considerShowingDialog(dialog); + } + + if (rootPane != null && timeToPaint()) { + paintProgress(rootPane, dialog); + } + } + + @Nullable + private JRootPane considerShowingDialog(@NotNull ProgressDialog dialog) { + if (System.currentTimeMillis() - myLastUiUpdate > DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS) { + dialog.myRepaintRunnable.run(); + showDialog(); + return dialog.getPanel().getRootPane(); + } + return null; + } + + private boolean timeToPaint() { + long now = System.currentTimeMillis(); + if (now - myLastUiUpdate <= ProgressDialog.UPDATE_INTERVAL) { + return false; + } + myLastUiUpdate = now; + return true; + } + + private void paintProgress(@NotNull JRootPane rootPane, @NotNull ProgressDialog dialog) { + dialog.myRepaintRunnable.run(); + + JPanel dialogPanel = dialog.getPanel(); + if (myScreenshot != null) { + IdeGlassPaneImpl glassPane = (IdeGlassPaneImpl)IdeGlassPaneUtil.find(rootPane); + glassPane.activateIfNeeded(); + glassPane.validate(); + + rootPane.getGraphics().drawImage(paintToBuffer(rootPane, dialogPanel), 0, 0, null); + } else { + dialogPanel.validate(); + dialogPanel.paintImmediately(dialogPanel.getBounds()); + } + } + + @NotNull + private Image paintToBuffer(JRootPane rootPane, JPanel dialogPanel) { + Image buffer = rootPane.createVolatileImage(rootPane.getWidth(), rootPane.getHeight()); + + Graphics g = buffer.getGraphics(); + assert myScreenshot != null; + UIUtil.drawImage(g, myScreenshot, null, 0, 0); + + Container container = SwingUtilities.getAncestorOfClass(DialogWrapperDialog.class, dialogPanel); + assert container instanceof JComponent; + g.translate(container.getX() - rootPane.getX(), container.getY() - rootPane.getY()); + container.paint(g); + g.dispose(); + return buffer; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressDialog.java b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressDialog.java index f0b95ebeda5b..0917d8f61e5c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressDialog.java @@ -18,6 +18,7 @@ package com.intellij.openapi.progress.util; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.progress.ProgressIndicatorProvider; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.DialogWrapperPeer; @@ -242,7 +243,7 @@ class ProgressDialog implements Disposable { ); } - private static final int UPDATE_INTERVAL = 50; //msec. 20 frames per second. + static final int UPDATE_INTERVAL = 50; //msec. 20 frames per second. synchronized void update() { if (myRepaintedFlag) { @@ -280,6 +281,11 @@ class ProgressDialog implements Disposable { }); } + @Nullable + Window getParentWindow() { + return myParentWindow; + } + void show() { myWasShown = true; if (ApplicationManager.getApplication().isHeadlessEnvironment()) return; @@ -299,6 +305,9 @@ class ProgressDialog implements Disposable { } if (myPopup.getPeer() instanceof DialogWrapperPeerImpl) { ((DialogWrapperPeerImpl)myPopup.getPeer()).setAutoRequestFocus(false); + if (ProgressIndicatorProvider.getGlobalProgressIndicator() instanceof PotemkinProgress) { + myPopup.setModal(false); // display the dialog and continue with EDT execution, don't block it forever + } } myPopup.pack(); diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWindow.java b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWindow.java index 6427b453a717..98f3c8920aa0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWindow.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWindow.java @@ -304,6 +304,11 @@ public class ProgressWindow extends ProgressIndicatorBase implements BlockingPro return myDialog != null && myDialog.getPanel() != null && myDialog.getPanel().isShowing(); } + @Nullable + protected ProgressDialog getDialog() { + return myDialog; + } + public void background() { final Runnable backgroundHandler = myBackgroundHandler; if (backgroundHandler != null) { diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeGlassPaneImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeGlassPaneImpl.java index 0f29a8691dd4..edd0da09a733 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeGlassPaneImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeGlassPaneImpl.java @@ -483,7 +483,7 @@ public class IdeGlassPaneImpl extends JPanel implements IdeGlassPaneEx, IdeEvent applyActivationState(); } - private void activateIfNeeded() { + public void activateIfNeeded() { if (!myPreprocessorActive && !myMouseListeners.isEmpty()) { myPreprocessorActive = true; } diff --git a/platform/platform-resources/src/idea/PlatformActions.xml b/platform/platform-resources/src/idea/PlatformActions.xml index 1c9bf311d7cc..4e3bf0da9cfb 100644 --- a/platform/platform-resources/src/idea/PlatformActions.xml +++ b/platform/platform-resources/src/idea/PlatformActions.xml @@ -747,6 +747,7 @@ +