runWriteActionWithProgress

This commit is contained in:
peter
2016-12-01 16:27:11 +01:00
parent 69721c6a13
commit a8800a9a04
7 changed files with 261 additions and 2 deletions
@@ -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();
}
}
}
@@ -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<ProgressIndicator> 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<? extends Runnable> clazz = action.getClass();
@@ -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;
}
}
@@ -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();
@@ -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) {
@@ -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;
}
@@ -747,6 +747,7 @@
<action id="AddTestProcessActionIndefinte" internal="true" class="com.intellij.openapi.wm.impl.status.AddTestProcessActionIndefinite" text="Add Test Process Indefinite"/>
<action id="AddManyTestProcesses" internal="true" class="com.intellij.openapi.wm.impl.status.AddManyTestProcesses" text="Add Many Test Processes"/>
<action id="ShowProgressTestDialogAction" internal="true" class="com.intellij.openapi.wm.impl.status.ShowProgressTestDialogAction" text="Show Progress Test Dialog"/>
<action id="WriteActionUnderProgress" internal="true" class="com.intellij.internal.TestWriteActionUnderProgress" text="Test Write Action Under Progress"/>
<separator/>
<action id="TestGestureAction" class="com.intellij.openapi.keymap.impl.ui.TestGestureAction" text="Test Gesture Action"/>
<action id="TestDndAction" class="com.intellij.internal.validation.TestDnd" text="Test Dnd"/>